Подтвердить что ты не робот

Рекомендации WPF по передовому опыту

Это лучший опыт вопрос о wpf тему и более конкретно скинирование. Это скорее вопрос, основанный на мнениях, так как у меня нет проблемы с этой работой, но больше из общего интереса, если мои выводы охватывают все сценарии, и если кто-то другой сталкивался с теми же мыслями по этому вопросу и какими были их подход.

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

Мы разделили эту способность до 2 категорий:

1) Стили наших элементов управления, которые мы просто называем Тема.

2) Ресурсы, которые они используют для настройки своего внешнего вида под названием " Кожа", включают в себя кисти и всевозможные строковые структуры, такие как CornerRadius, BorderThickness и т.д.

Способ, которым Skin устанавливается для системы, - простой случай слияния словаря skin last в наши ресурсы приложения.

  <Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Default.skin.xaml" />
            <ResourceDictionary Source="Theme.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

В наше приложение вложено другое соединение.

  protected override void OnStartup(StartupEventArgs e)
  {
       base.OnStartup(e);

       string skin = e.Args[0];
       if (skin == "Blue")
       {         .
            ResourceDictionary blueSkin = new ResourceDictionary();
            blueSkin.Source = new Uri("Blue.skin.xaml", UriKind.Relative);

            Application.Current.Resources.MergedDictionaries.Add(blueSkin);
       }
  }

Внутри Theme.xaml:

   <!-- Region TextBox ControlTemplate -->

<ControlTemplate TargetType="{x:Type TextBox}" x:Key="TextBoxTemplate">
    <Border  Background="{TemplateBinding Background}"  
         BorderBrush="{TemplateBinding BorderBrush}" 
         BorderThickness="{TemplateBinding BorderThickness}"
         CornerRadius="{StaticResource TextBoxCornerRadius}" >
      <Border x:Name="shadowBorder" BorderBrush="{StaticResource TextBoxShadowBrush}"                                   
        CornerRadius="{StaticResource TextBoxInnerShadowCornerRadius}" 
        BorderThickness="{StaticResource TextBoxInnerShadowBorderThickness}" 
        Margin="{StaticResource TextBoxInnerShadowNegativeMarginForShadowOverlap}" >
            <ScrollViewer x:Name="PART_ContentHost"  Padding="{TemplateBinding Padding}" 
                    VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                    HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
        </Border>
    </Border>

    <ControlTemplate.Triggers>
        <Trigger Property="BorderThickness" Value="0">
            <Setter TargetName="shadowBorder" Property="BorderThickness" Value="0" />
        </Trigger>
    </ControlTemplate.Triggers>
</ControlTemplate>

<!-- EndRegion -->

<!-- Region TextBox Style -->

<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">     
    <Setter Property="BorderBrush" Value="{StaticResource TextBoxBorderBrush}" />
    <Setter Property="Background" Value="{StaticResource TextBoxBackgroundBrush}" />
    <Setter Property="BorderThickness" Value="{StaticResource TextBoxBorderThickness}" />

    <Setter Property="Padding" Value="{StaticResource TextBoxPadding}" />
    <Setter Property="Template" Value="{StaticResource TextBoxTemplate}"/>
  <Style.Triggers>

        <Trigger Property="IsMouseOver" Value="True">
            <Setter Property="Background" Value="{StaticResource TextBoxIsMouseOverBackgroundBrush}" />
            <Setter Property="BorderBrush" Value="{StaticResource TextBoxIsMouseOverBorderBrush}" />
        </Trigger>
        <Trigger Property="IsFocused" Value="True">
            <Setter Property="Background" Value="{StaticResource TextBoxIsMouseWithinBackgroundBrush}" />
            <Setter Property="BorderBrush" Value="{StaticResource TextBoxIsMouseWithinBorderBrush}" />
        </Trigger>

    </Style.Triggers>
</Style>

<!-- EndRegion -->

В TextBox ControlTemplate есть элементы, связанные с DependencyProperties с использованием TemplateBinding, и некоторые, такие как CornerRadius и InnerCornerRadius, InnerBorderThickness и InnerBorderBrush, которые получают свое значение от ресурсов.

Каким будет лучший подход?

создание производного элемента управления с соответствующими свойствами Dependency который будет ссылаться на соответствующие ресурсы, а затем привязать к ним элементы в шаблоне управления.

Или

имеют элементы внутри шаблона, ссылки на эти ресурсы сами.

Использование подхода Dependency Property:

Преимущества:

1) Ясность, у нас есть более четкий API для нашего контроля и лучшего понимания того, как наш контроль выглядит и ведет себя так, как он делает.

2) Шаблон не должен изменяться, чтобы его можно было настраивать. Все контролируется стилем.

3) Триггеры также изменяют внешний вид элемента управления без необходимости переопределять шаблон управления, нет необходимости в триггерах ControlTemplate.

4) "Blendabilty" с использованием blend я может легко настроить мой элемент управления.

5) Стили сами наследуются. поэтому, если я хочу изменить только один аспект элемента управления, который мне нужно сделать, это наследовать стиль по умолчанию.

Недостатки:

1) Внедрение еще одного настраиваемого элемента управления.

2) Реализация многочисленных свойств зависимостей, некоторые из которых не имеют большого отношения к элементу управления и только там, чтобы удовлетворить то, что у нас есть в нашем шаблоне.

  • Просто, чтобы прояснить это, наследуем от TextBox что-то вроде InnerShadowTextBox и реализуя свойства зависимостей с ним для всех вышеперечисленных.

Это будет усиливаться, если у меня есть множество элементов внутри моего шаблона, которые нужно настраивать.

Что-то вроде этого чудовища:

  <Style x:Key="{x:Type cc:ComplexControl}" TargetType="{x:Type cc:ComplexControl}">
    <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type cc:ComplexControl}">
                    <Grid>
                        <Ellipse Fill="Red" Margin="0" Stroke="Black" StrokeThickness="1"/>
                        <Ellipse Fill="Green"  Margin="6" Stroke="Red" StrokeThickness="1"/>
                        <Ellipse Fill="Blue" Margin="12"/>
                        <Ellipse Fill="Aqua" Margin="24" />
                        <Ellipse Fill="Beige" Margin="32"/>
                        <StackPanel Orientation="Horizontal" Width="25" Height="25"
                                    VerticalAlignment="Center" HorizontalAlignment="Center">
                            <Rectangle Fill="Black" Width="2" />
                            <Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/>
                            <Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/>
                            <Rectangle Fill="Black" Width="2" Margin="2,0,0,0"/>
                        </StackPanel>

                    </Grid>
                </ControlTemplate>
            </Setter.Value>
    </Setter>
</Style>

Для этого потребуется множество ресурсов:

 <SolidColorBrush x:Key="Ellipse1Fill">Red</SolidColorBrush>
 <SolidColorBrush x:Key="Ellipse2Fill">Green</SolidColorBrush>
 <SolidColorBrush x:Key="Ellipse3Fill">Blue</SolidColorBrush>
 <SolidColorBrush x:Key="Ellipse4Fill">Aqua</SolidColorBrush>
 <SolidColorBrush x:Key="Ellipse5Fill">Beige</SolidColorBrush>
 <SolidColorBrush x:Key="Ellipse1Stroke">Beige</SolidColorBrush>
 <sys:Double x:Key="Ellipse1StrokeThickness>1</sys:Double>
      ......... and many more 

У меня был бы большой список ресурсов в любом случае. Но с зависимостями. Мне также нужно будет назначить необходимость находить смысл в каждой маленькой части, которая иногда не намного больше, чем "хорошо выглядит", и не имеет никакого отношения к управлению или что, если завтра я захочу изменить шаблон.

Использование подхода, на который ссылаются ресурсы из шаблона управления.

Преимущества:

1) Простые в использовании, боковые шаги, которые уродство описывает в недостатках, описанных выше в подходе Dp, обеспечивая при этом "хак", который позволяет тему.

Недостатки:

1) Если я захочу еще настроить свой элемент управления, например добавить триггер, влияющий на внутреннюю границу моего TextBox, мне просто нужно будет создать новый шаблон управления.

2) Непонятный API. Предположим, я хотел бы изменить BorderBrush внутренней границы в определенном представлении.

         <TextBox>
             <TextBox.Resources>
                  <SolidColorBrush x:Key="InnerBorderBrush" Color="Red" />
             </TextBox.Resources>
          </TextBox> 

Что не так уж плохо, подумать об этом... мы иногда делаем это с помощью селекторных реализаций, которые внутренне используют определенные ресурсы, когда избавляются от неактивных цветов выделения и hightlight, например:

   <ListBox>
       <ListBox.Resources>
          <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
          <SolidColorBrush x:Key="{x:Static SystemColors.HighlightTextBrushKey}" Color="Transparent"/>
          <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent"/>
          <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}" Color="Transparent"/>
       </ListBox.Resources>
 </ListBox>

Выводы:

Гибрид, описанный в стиле TextBox выше, - это путь.

1) Свойства зависимостей будут введены только для аспектов управления, которые связаны с логикой управления, включая определенную часть шаблона.

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

3) Шаблоны управления должны стремиться быть минималистичными и использовать существующие свойства зависимостей. Как Background, Foreground, BorderBrush и т.д.

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

4b9b3361

Ответ 1

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

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

Если вам требуются незначительные изменения в стиле, лучше использовать DataTemplates и триггеры данных, если это позволяет ситуация. Таким образом вы меняете стиль в чистом виде.

Кроме того, вы можете использовать свойство BasedOn. Создайте свой "базовый" стиль и у вас есть несколько стилей, которые имеют атрибут BasedOn = "{myBaseStyle}. Это позволит вам много вариантов без загромождения кода.

Как правило, я всегда рекомендую иметь больше кистей/цветов/ресурсов, а не больше стилей или шаблонов. Обычно у нас есть иерархия для шаблонов colors- > brushes- > styles- > . Это помогает повторно использовать цвета, сохраняя при этом разделение с помощью кистей.

Использование DynamicResource в отличие от StaticResource также полезно в некоторых ситуациях, когда вы динамически загружаете ресурсы.

Надеюсь, это поможет. Хотелось бы написать больше, но некоторые параметры для написания твердой темы очень специфичны для контекста. Если у вас есть дополнительные примеры, я был бы рад добавить дополнительную информацию.