WPF主题最佳实践

era*_*zap 12 wpf resources themes styles

这是一个最佳实践关于WPF问题更换主题皮肤,更具体剥皮.这更像是一个基于意见的问题,因为我没有问题使这项工作,但更多的是一般想知道我的结论是否涵盖所有情景,以及是否有其他人在这个问题上遇到相同的想法,他们是什么方法.

一些背景,我们的团队需要定义一种方法,使我们的系统能够成为主题.

我们将此功能分为两类:

1)我们的控件的样式,我们简称为" 主题 ".

2)用于定制其外观的资源称为" 皮肤 ",包括画笔,以及各种尺寸结构,如CornerRadius,BorderThickness等.

为系统设置外观的方法是将皮肤字典最后合并到应用程序资源中的简单情况.

  <Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="Default.skin.xaml" />
            <ResourceDictionary Source="Theme.xaml" />
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>
Run Code Online (Sandbox Code Playgroud)

一个不同的皮肤最后合并到我们的应用程序.

  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);
       }
  }
Run Code Online (Sandbox Code Playgroud)

Inside 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 -->
Run Code Online (Sandbox Code Playgroud)

在TextBox ControlTemplate中,有一些元素使用TemplateBinding绑定到DependencyProperties,有些像是CornerRadius和InnerCornerRadius,InnerBorderThickness和InnerBorderBrush,它们从资源中获得它们的值.

什么是最好的方法?

使用相关的依赖项属性创建派生控件,该属性将引用相关资源,然后将控件模板中的元素绑定到它们.

要么

让模板中的元素引用这些资源本身.

使用依赖属性方法:

好处 :

1)清晰度,我们有一个更清晰的API用于控制和更好地理解我们的控件的外观和行为方式.

2)模板不必更改以便可自定义.一切都是通过风格控制的.

3)触发器也可以更改控件的外观,而无需覆盖控件模板,无需ControlTemplate触发器.

4)使用混合物的"Blendabilty"可以很容易地定制我的控制.

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>
Run Code Online (Sandbox Code Playgroud)

这将需要大量资源:

 <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 
Run Code Online (Sandbox Code Playgroud)

无论哪种方式,我都会有大量的资源.但是依赖属性.我还需要指定需要在每个小部分中找到意义,有时候并不是"它看起来不错"并且与控件没什么关系,或者如果明天我想要更改模板怎么办.

使用从控件模板中引用资源的方法.

好处 :

1)易于使用,边缘步骤丑陋描述了上述Dp方法中描述的缺点,同时提供了能够实现主题的"黑客".

缺点:

1)如果我想进一步自定义我的控件,比如添加一个影响我TextBox内边框的触发器,我只需要创建一个新的控件模板.

2)不是一个明确的API,让我说我想在特定视图中更改内边框的BorderBrush.

         <TextBox>
             <TextBox.Resources>
                  <SolidColorBrush x:Key="InnerBorderBrush" Color="Red" />
             </TextBox.Resources>
          </TextBox> 
Run Code Online (Sandbox Code Playgroud)

考虑到这一点并不是那么糟糕......我们有时会使用Selector实现来执行此操作,这些实现在内部使用特定资源时会消除非活动选择和高亮颜色,如下所示:

   <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>
Run Code Online (Sandbox Code Playgroud)

结论:

上面的TextBox Style中描述的混合是可行的方法.

1)仅针对与控件逻辑相关的控件方面(包括特定模板部件)引入依赖属性.

2)资源名称将由一个明确的命名约定组成,并根据它们所涉及的控件和视图中的常见用法分隔在文件中,就像在我们的应用程序中的视图中使用的Common Brush一样.

3)控件模板应该追求简约,并使用现有的依赖属性.像背景,前景,BorderBrush等.

我非常感谢您对此事的意见和建议,并提前感谢.

Dax*_*dhi 4

正如 Xavier 所说,这对于代码审查来说可能是一个更好的问题。但我会传达关于你的问题的一些关键想法,尽管其中很多会涉及个人(或团队)的风格和要求。

创建几十个主题后,我建议尽可能不要使用自定义控件。随着时间的推移,可维护性会下降很多。

如果您需要对样式进行少量修改,那么在情况允许的情况下最好使用数据模板和数据触发器。这样你就能以一种干净的方式改变风格。

此外,您可以利用 BasedOn 属性。创建您的“基本”样式并拥有多个具有属性 BasedOn="{myBaseStyle} 的样式。这将为您提供很多选项,而不会使您的代码混乱。

根据经验,我总是建议拥有更多的画笔/颜色/资源,而不是更多的样式或模板。我们通常为颜色->画笔->样式->模板设置层次结构。这有助于重复使用颜色,同时仍然通过画笔保持分离。

在某些动态加载资源的情况下,使用 DynamicResource 相对于 StaticResource 也很有用。

希望这可以帮助。很想写更多,但编写坚实主题的一些参数是非常特定于上下文的。如果您有更多示例,我很乐意添加更多信息。