【发布时间】:2015-08-14 21:01:51
【问题描述】:
这是关于 wpf 主题化,更具体地说是换肤的最佳实践问题。 这更像是一个基于意见的问题,因为我在进行这项工作时没有问题,但更多的是一般想知道我的结论是否涵盖所有场景,以及是否有人对这个问题有相同的想法,他们的想法是什么接近。
一些背景知识,我们的团队需要定义一种方法,让我们的系统能够主题化。
我们将此能力分为 2 类:
1) 控件的样式,我们简称为“Theme”。
2) 他们用于自定义外观的资源称为“Skin”,其中包括 Brushes 和各种尺寸结构,如 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);
}
}
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) “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 方法的上述缺点中描述了丑陋的侧面步骤,同时提供了启用主题的“hack”。
缺点:
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>
结论:
上面文本框样式中描述的混合是要走的路。
1) 依赖属性将仅针对与控件逻辑相关的控件方面引入,包括特定模板部分的 .
2) 资源名称将由明确的命名约定组成,并根据它们相关的控件和视图中的常见用法在文件中分隔,就像我们应用程序中视图中使用的常用画笔一样。
3) 控制模板应该追求简约并使用现有的依赖属性。像背景、前景、BorderBrush 等。
非常感谢您对此事的意见和想法,在此先感谢。
【问题讨论】:
-
由于这个问题的性质,您最好将其发布在Code Review 网站上。
标签: wpf resources styles themes