这是关于wpf 主题的最佳做法问题,更具体地说是皮肤。 这更像是一个基于意见的问题,因为我没有问题做这个工作,但更多的是一般想知道我的结论是否涵盖了所有情景,以及是否有其他人在这个问题上遇到同样的想法和什么是他们的方法。
有些背景,我们的团队需要定义一种方法,使我们的系统能够主题。
我们将此功能分为两类:
1)我们简单称为' 主题'。
的控件样式2)他们用来定制外观的资源称为皮肤'这包括画笔,以及各种尺寸结构,如CornerRadius,BorderThickness等。
为系统设置皮肤的方法是将皮肤字典最后合并到我们应用程序的资源中的简单情况。
<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,它们从资源中获取它们的值。
最好的方法是什么?
使用相关的依赖项属性创建派生控件 它将引用相关资源,然后将控件模板中的元素绑定到它们。
或
让模板中的元素自己引用这些资源。
使用依赖属性方法:
优势:
1)清晰度,我们有一个更清晰的API用于控制和更好地理解我们的控件的外观和行为方式。2)模板无需更改即可自定义。一切都是通过风格控制的。
3)触发器也可以更改控件的外观,而无需覆盖控件模板,无需ControlTemplate触发器。
4)&#34; Blendabilty&#34;使用混合我可以很容易地自定义我的控制。
5)样式本身是可继承的。因此,如果我只想改变控件的一个方面,我需要做的就是继承默认样式。
缺点:
1)实现另一个自定义控件。
2)实现了许多依赖属性,其中一些与控件没有太大关系,只是为了满足我们模板中的内容。
如果我的模板中有许多必须可自定义的元素,这将会加剧。
像这样的怪物:
<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
无论哪种方式,我都会有大量的资源。但是依赖属性。 我还需要指定需要在每一个小部分中找到意义,有时候不会超过&#34;它看起来很好&#34;并且与控件没有多大关系,或者如果明天我想要更改模板怎么办?
使用从控件模板中引用资源的方法。
优势:
1)易于使用,边缘步骤丑陋描述了Dp方法中的上述缺点,同时提供了一个&#34; hack&#34;这使主题。
缺点:
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>
结论:
上面的TextBox Style中描述的混合是可行的方法。
1)依赖属性仅用于与控件逻辑相关的控件方面,包括特定模板部分。
2)资源名称将由一个明确的命名约定组成,并根据它们所涉及的控件和视图中的常见用法分隔在文件中,就像在我们的应用程序中的视图中使用的Common Brush一样。
3)控件模板应该追求简约,并使用现有的依赖属性。像背景,前景,BorderBrush等。
我非常感谢您对此事的意见和建议。
答案 0 :(得分:3)
正如Xavier所说,对于Code Review来说,这可能是一个更好的问题。但是我会就你的问题传达一些关键的想法,尽管其中很多都会涉及到个人(或团队)的风格和要求。
创建了几十个主题之后,我建议尽可能不使用自定义控件。随着时间的推移,可维护性会下降很多。
如果您需要对样式进行少量修改,最好在情境允许的情况下使用DataTemplates和Data Triggers。这样你就可以用干净的方式改变风格。
此外,您可以利用BasedOn属性。创建“基础”样式并具有多个样式,这些样式具有属性BasedOn =“{myBaseStyle}。这将为您提供许多选项,而不会使代码混乱。
根据经验,我总是建议使用更多画笔/颜色/资源而不是更多样式或模板。我们通常为colors-&gt; brushes-&gt; styles-&gt;模板设置层次结构。这有助于重复使用颜色,同时仍然通过刷子保持分离。
在动态加载资源的某些情况下,使用DynamicResource而不是StaticResource也很有用。
希望这会有所帮助。愿意写更多,但编写一个可靠主题的一些参数是非常特定于上下文的。如果您有其他示例,我很乐意添加更多信息。