首页 文章

WPF主题最佳实践

提问于
浏览
11

这是关于wpf themeingbest practices 问题,更具体地说是 skinning . 这更像是一个基于意见的问题,因为我没有问题使这项工作,但更多的是一般想知道我的结论是否涵盖所有情景,以及是否有其他人在这个问题上遇到同样的想法,他们是什么方法 .

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

我们将此功能分为两类:

1)我们的控件的样式我们简称为' Theme ' .

2)用于定制其外观的资源称为“ Skin ”,这包括画笔,以及各种大小调整结构,如CornerRadius,BorderThickness等 .

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

<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);
       }
  }

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 -->

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

什么是最好的方法?

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

Or

让模板中的元素自己引用这些资源 .

Using the Dependency Property approach :

Advantages :

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

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

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

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

5)样式本身是可继承的 . 因此,如果我只想改变控件的一个方面,我需要做的就是从默认样式继承 .

Disadvantages :

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

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

Using the approach where the resources are referenced from within the control template.

Advantages :

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

Disadvantages :

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

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

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

考虑到它并没有那么糟糕...我们有时会使用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>

Conclusions :

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

1)依赖性属性仅用于与控件逻辑相关的控件方面,包括特定模板部分 .

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

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

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

1 回答

  • 3

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

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

    如果您需要对样式进行少量修改,最好在情境允许的情况下使用DataTemplates和Data Triggers . 这样你就可以以干净的方式改变风格 .

    此外,您可以利用基于 properties . 创建“基础”样式并具有多个样式,这些样式具有属性BasedOn =“ . 这将允许您使用许多选项而不会使代码混乱 .

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

    在动态加载资源的某些情况下,使用DynamicResource而不是StaticResource也很有用 .

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

相关问题