2010-11-25 3 views
1

Я создаю редактор настроек, где разработчики плагинов могут определять свой собственный пользовательский интерфейс для настройки своих плагинов. Я реализую функцию, чтобы скрыть определенные «продвинутые» элементы, если флажок снят.Стиль WPF в зависимости от состояния флажка

Флажок XAML тривиальна:

<CheckBox Name="isAdvanced">_Advanced</CheckBox> 

В идеале (подробнее об этом позже), реализаторы просто добавить флаг передовых элементов управления (которые должны быть скрыты, когда «продвинутым» флажок снят), как так :

<Button library:MyLibraryControl.IsAdvanced="True">My Button</Button> 

проблема заключается в создании магии в сокрытии IsAdvanced="True" элементов при isAdvanced.IsChecked == false. У меня есть желаемое поведение с помощью этого стиля на оконном элементе:

<Window.Resources> 
    <Style TargetType="Button"> 
     <Style.Triggers> 
      <MultiDataTrigger> 
       <MultiDataTrigger.Conditions> 
        <Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" /> 
        <Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" /> 
       </MultiDataTrigger.Conditions> 

       <Setter Property="UIElement.Visibility" Value="Collapsed" /> 
      </MultiDataTrigger> 
     </Style.Triggers> 
    </Style> 
</Window.Resources> 

Однако, этот метод представляет две проблемы:

  1. Это только добавляет функциональность кнопок и ничего другого. Флаг IsAdvanced может (должен быть способен) быть добавлен к любым визуальный элемент.
  2. Он заменяет/отменяет стили, которые в противном случае были бы на кнопке.

Есть ли какой-либо другой способ создания функциональности, которую я хочу? Я не боюсь работать в коде, но элегантное решение XAML идеально (так как это просто изменение пользовательского интерфейса, кроме сохранения состояния флажка в настройках пользователя).


Приходят в голову некоторые другие способы обозначения продвинутых элементов. Они включают использование динамического ресурса и непосредственное связывание:

<Button Visibility="{DynamicResource IsAdvancedVisibility}">My Button</Button> 
<Button Visibility="{Binding IsChecked, RelativeSource={...}, ValueConverter={...}}">My Button</Button> 

Используя словарь ресурсов, вероятно, работать, но это кажется очень плохим решением, как состояние UI не кажется, что она должна принадлежать в словаре. Связывание вручную - это беспорядок, потому что состояние флажка должно быть отправлено каким-то образом, а помимо значений жесткого кодирования я не вижу, чтобы это не стало беспорядком.

Оба этих альтернативных решения связывают семантику («это расширенный вариант») по внешнему виду («расширенные параметры должны быть свернуты»). Исходя из мира HTML, я знаю, что это очень плохо, и я отказываюсь подчиняться этим методам, если не обязательно.

ответ

0

Я решил немного изменить проблему, и это сработало хорошо.

Вместо того, чтобы иметь дело со стилями, я использовал привязку свойств, как предложено Gishu. Однако вместо размещения пользовательского интерфейса в виртуальной машине (где свойства будут распространять несколько слоев вручную), я использовал присоединенное свойство с именем ShowAdvanced, которое распространяется через свойство наследования.

Создание этого свойства тривиально:

public static readonly DependencyProperty ShowAdvancedProperty; 

ShowAdvancedProperty = DependencyProperty.RegisterAttached(
    "ShowAdvanced", 
    typeof(bool), 
    typeof(MyLibraryControl), 
    new FrameworkPropertyMetadata(
     false, 
     FrameworkPropertyMetadataOptions.Inherits | FrameworkPropertyMetadataOptions.OverridesInheritanceBehavior 
    ) 
); 

Флажок устанавливает ShowAdvanced свойство выше по всему окну. Он мог бы установить его в другом месте (например,на сетке), но положить его в окно имеет смысл IMO:

<CheckBox Grid.Column="0" 
    IsChecked="{Binding (library:MyLibraryControl.ShowAdvanced), ElementName=settingsWindow}" 
    Content="_Advanced" /> 

Изменение видимости (или любые другие свойства, желательные) в зависимости от ShowAdvanced собственности становится легко:

<Foo.Resources> 
    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" /> 
</Foo.Resources> 

<Button Visibility="{Binding (library:MyLibraryControl.ShowAdvanced), RelativeSource={RelativeSource Self}, Converter={StaticResource BooleanToVisibilityConverter}}">I'm Advanced</Button> 

канав стили позволяет разработчикам плагина полностью изменять макет своих элементов управления, если это необходимо. Они также могут отображать расширенные элементы управления, но при необходимости отключать их. Стили вызвали массу проблем, и, как показал Мелеак, workarounds were messy.

Моя основная проблема с установкой «расширенной» логики отображения в виртуальной машине заключается в том, что теперь менее вероятно, что вы можете уйти с привязкой нескольких видов к одной и той же виртуальной машине, сохраняя при этом желаемую гибкость. Если «продвинутая» логика находится в виртуальной машине, необходимо отобразить расширенные элементы управления для все или нет просмотров; вы не можете показать их за одного и спрятать их для другого. Это, ИМО, нарушает принципы наличия виртуальной машины в первую очередь.

(Спасибо всем, кто писал здесь;! Это было полезно)

0

Как переместить это в ViewModel вместо XAML, потому что это похоже на поведение для меня.

Поведение, которое вы хотите, мне кажется - каждый плагин регистрирует кучу свойств (сопоставление с элементами управления пользовательского интерфейса) как расширенный. Существует глобальная настройка для включения/выключения расширенных свойств. Когда это произойдет, обновите все плагины, чтобы показать/скрыть их дополнительные свойства.

Имейте плагинов-писателей, реализующих интерфейс, содержащий только свойство set AreAdvancedControlsVisible. Пусть они позаботятся о скрытии/показе элементов управления в своем интерфейсе через обработчик изменения свойств. Расширенные элементы пользовательского интерфейса могут привязываться к флажку ShowAdvancedControls в pluginVM, который переключается в/из сменщика. Рамка может просто перебирать доступные плагины и устанавливать этот флаг всякий раз, когда установлен флажок ShowAdvanced.

+0

Это, безусловно, является жизнеспособным вариантом, и звучит довольно чистый. Проблема в том, что это, вероятно, приведет к тому, что код шаблона будет посыпать здесь и там, чтобы распространить поведение до пользовательского интерфейса. Однако это дает плагинам больше контроля. – strager 2010-11-25 08:28:17

0

Возможно, есть много способов решить эту проблему, но я попытался проделать два вопроса, которые были у вас с вашим решением. Небольшой пример проекта с этим можно загрузить here.

1.It добавляет функциональность только кнопкам и ничего больше. Флаг IsAdvanced может (должен иметь возможность ) быть добавлен к любому визуальному элементу.

Добавление прикрепленного свойства, которое делает все дочерние наследует значение, в самый верхний контейнер может это исправить.

2.It заменяет/отменяет стили, которые в противном случае были бы на кнопке .

Bea Stollnitz имеет хорошую статью в блоге о слиянии стилей here.
У этого есть метод расширения для стиля под названием Merge, который можно использовать.

Звучал довольно прямолинейно, но следующие проблемы сделали код более сложным.
1. Элементы Visual не имеют стиля, когда Attached Property наследуется. Обязательное загруженное событие.
2. Стиль не может быть изменен, когда он используется. Требуется метод копирования для стиля.

Итак, мы хотим, чтобы этот стиль был объединен с активным стилем для всех дочерних элементов в родительском контейнере.

<Style x:Key="IsAdvancedStyle"> 
    <Style.Triggers> 
     <MultiDataTrigger> 
      <MultiDataTrigger.Conditions> 
       <Condition Binding="{Binding (library:MyLibraryControl.IsAdvanced), RelativeSource={RelativeSource Mode=Self}}" Value="True" /> 
       <Condition Binding="{Binding IsChecked, ElementName=isAdvanced}" Value="False" /> 
      </MultiDataTrigger.Conditions> 
      <Setter Property="Control.Visibility" Value="Collapsed" /> 
     </MultiDataTrigger> 
    </Style.Triggers> 
</Style> 

Если корневой контейнер представляет собой StackPanel, мы добавим это. Стиль IsAdvancedStyle затем будет унаследован всеми дочерними элементами и объединен с активным стилем.

<StackPanel local:StyleChildsBehavior.StyleChilds="{StaticResource IsAdvancedStyle}"> 

StyleChildsBehavior.cs

public class StyleChildsBehavior 
{ 
    public static readonly DependencyProperty StyleChildsProperty = 
     DependencyProperty.RegisterAttached("StyleChilds", 
              typeof(Style), 
              typeof(StyleChildsBehavior), 
              new FrameworkPropertyMetadata(null, 
                FrameworkPropertyMetadataOptions.Inherits, 
                StyleChildsCallback)); 

    public static void SetStyleChilds(DependencyObject element, Style value) 
    { 
     element.SetValue(StyleChildsProperty, value); 
    } 
    public static Style GetStyleChilds(DependencyObject element) 
    { 
     return (Style)element.GetValue(StyleChildsProperty); 
    } 

    private static void StyleChildsCallback(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    { 
     if (DesignerProperties.GetIsInDesignMode(d) == true) 
     { 
      return; 
     } 
     Style isAdvancedStyle = e.NewValue as Style; 
     if (isAdvancedStyle != null) 
     { 
      FrameworkElement element = d as FrameworkElement; 
      if (element != null) 
      { 
       if (element.IsLoaded == false) 
       { 
        RoutedEventHandler loadedEventHandler = null; 
        loadedEventHandler = new RoutedEventHandler(delegate 
        { 
         element.Loaded -= loadedEventHandler; 
         MergeStyles(element, isAdvancedStyle); 
        }); 
        element.Loaded += loadedEventHandler; 
       } 
       else 
       { 
        MergeStyles(element, isAdvancedStyle); 
       } 
      } 
     } 
    } 
    private static void MergeStyles(FrameworkElement element, Style isAdvancedStyle) 
    { 
     if (element != null) 
     { 
      Style advancedStyle = GetStyleCopy(isAdvancedStyle); 
      advancedStyle.Merge(element.Style); 
      element.Style = advancedStyle; 
     } 
    } 
    private static Style GetStyleCopy(Style style) 
    { 
     string savedStyle = XamlWriter.Save(style); 
     using (MemoryStream memoryStream = new MemoryStream(Encoding.ASCII.GetBytes(savedStyle))) 
     { 
      ParserContext parserContext = new ParserContext(); 
      parserContext.XmlnsDictionary.Add("library", "clr-namespace:HideAll;assembly=HideAll"); 
      return XamlReader.Load(memoryStream, parserContext) as Style; 
     } 
    } 
} 

После этого IsAdvancedStyle будет объединен во всех детях StackPanel и это касается детей, которые добавляются в время выполнения, а также.

Модифицированный метод расширения слияния из ссылки в блоге.

public static void Merge(this Style style1, Style style2) 
{ 
    if (style1 == null || style2 == null) 
    { 
     return; 
    } 
    if (style1.TargetType.IsAssignableFrom(style2.TargetType)) 
    { 
     style1.TargetType = style2.TargetType; 
    } 
    if (style2.BasedOn != null) 
    { 
     Merge(style1, style2.BasedOn); 
    } 
    foreach (SetterBase currentSetter in style2.Setters) 
    { 
     style1.Setters.Add(currentSetter); 
    } 
    foreach (TriggerBase currentTrigger in style2.Triggers) 
    { 
     style1.Triggers.Add(currentTrigger); 
    } 
} 

 Смежные вопросы

  • Нет связанных вопросов^_^