控件模板
理解逻辑树和可视化树
通过可视化树可以完成一下两项非常有用的工作
- 可使用样式改变可视化树中的元素
- 可为控件创建新模板
LogicalTreeHelper:
- FindLogicalNode()
- BringIntoView()
- GetParent()
- GetChildren()
VisualTreeHelper:
- GetChildrenCount()
- GetChild()
- GetParent()
Note:可以使用Snoop 深入研究其他应用程序的可视化树。snoopwpf.codeplex.com
简单的递归VisualTree窗口
1 | public partial class VisualTreeDisplay : System.Windows.Window |
对当前元素,添加它的子元素的对象,然后递归子元素及子元素的子元素的对象。
然后可以用其他窗口显示此树
1 | VisualTreeDisplay treeDisplay = new VisualTreeDisplay(); |
理解模板
每个控件都有一个内置的方法,用于确定如何渲染控件。改方法称为控件模板,是用XAML标记块定义的。
Button类的模板简化版本。
1 | <ControlTemplate ... > |
如果希望构建全新按钮,只需要创建新的模板控件。
Note:ButtonChrome类也是继承自Decorator类,和Border类似,是为了添加图形装饰使用。
按钮获得焦点/被单击以及被禁用时,触发器控制按钮如何进行变化。
1 | <Trigger Property="UIElement.IsKeyboardFocused"> |
触发器未必需要使用TargetName属性。如下
1 | <Trigger Property ="UIElement.IsEnabled"> |
模板类型
模板有三种类型,都继承自FrameworkTemplate基类。
- ControlTemplate
- DataTemplate
- HierarchicalDatatemplate
- ItemsControl
修饰类
ButtonChrome类,定义Microsoft.Windows.Themes. 其中还包括BulletChrome,ScrollChrome, ListBoxChrome, SystemDropShadowChrome.
稍高级别上System.Windows.Controls.Primitives命名空间中包含大量可独立使用的元素。这些元素包括ScrollBar, ResizeGrip, Thumb, TickBar等。
ResizeGrip 例
1 | <ControlTemplate TargetType="{x:Type ResizeGrip}"> |
剖析控件
可以通过编程获取所需的信息。基本思想是从Template属性中获取控件的模板,然后使用XamlWriter类,将该模板串行化到XAML文件中。
要构建这么一个程序的诀窍是使用反射(reflection)
1 | public partial class MainWindow : Window |
创建控件模板
简单按钮
基本框架
1 | <Window.Resources> |
其中,必须要有ContentPresenter.所有的内容控件都需要ContentPresenter元素,它是表示“在此插入内容的标记器”。
模板绑定
但是仅仅这样StackPanel忽略了Padding属性,通过使用模板绑定,模板可以从应用模板的控件中提取一个值。
1 | <ContentPresenter RecognizesAccessKey="True" |
就可以得到期望的效果.模板绑定只支持单向数据绑定。如果遇到模板绑定不生效,就可以改用数据绑定。
Note:模板绑定支持WPF的变化监测基础结构,所有依赖项属性都包含该基础结构。ContentPresenter元素之所以能够工作,是因为它有一个模板绑定将ContentPresenter.Content属性设置为Button.Content属性。
改变属性的触发器
上面的简单按钮令人胆寒,鼠标移上去没有任何反应。可以添加触发器让它活。
修改!
1 | <ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}"> |
在按钮和其他控件中,经常还要添加另外一个元素-焦点指示器。简单地使用Trigger根据Button.IsKeyboardFocused属性来显隐该元素。
1 | <Grid> |
1 | <Trigger Property="IsKeyboardFocused" Value="True"> |
最后加了个润色disable的灰色按钮。
Style and Template
样式功能没有Template强大,只能在原本的模板上调整
使用动画的触发器
下面是使用动画Trigger的简单按钮。
1 | <ControlTemplate.Triggers> |
可以使用两种等价的方法添加鼠标悬停动画-
- 创建相应MouseEnter和MouseLeave事件的事件触发器
- 创建当IsMouseOver 属性发生变化时添加进入或退出动作的属性触发器
EventTrigger驱动动画的其他任务:
- 显示或隐藏元素。 改变Opacity
- 改变形状和位置。 TranslateTransform. ScaleTransform RotateTransform
- 改变光照或着色。改变绘制背景的画刷的动画。ColorAnimation动画改变SolidBrush。LinearGradientBrush or RadialGradientBrush。
组织模板资源
在大型程序中,在单独资源字典中定义资源是一个更好的主意。
有经验的开发人员更愿意为每个控件模板创建单独的资源字典。为使用它,可使用MergedDictionaries集合完成工作。例如,假如按钮模板放在Resources文件夹的Button.xaml文件中,就可以在App.xaml中如下引用
1 | <ResourceDictionary> |
分解按钮控件模板
控件模板中可能封装了大量的不同细节,如形状、几何图形、画刷等。从控件模板中把细节提取并定义为单独的资源就可以更方便的重用。!
定义有顺序,包括控件模板使用的资源、控件模板以及每个按钮应用控件模板的样式规则。
1 | <RadialGradientBrush RadiusX="1" RadiusY="5" GradientOrigin="0.5,0.3" x:Key="HighlightBackground"> |
通过样式应用模板
上面的方式存在局限性,控件模板本质上硬编码了一些细节,如颜色方案。那么如果重新编写,使用模板绑定从控件属性中取出信息,这样就更方便定制。
1 | <ControlTemplate x:Key="CustomButtonTemplate" TargetType="{x:Type Button}"> |
为了使用这个新模板,需要设置按钮的Style属性而不是Template属性。
1 | <Button Margin="10" Padding="5" Style="{StaticResource CustomButtonStyle}">Cumsj</Button> |
自动应用模板
如果需要在整个应用程序中改变每个按钮的外观?
技巧是使用类型样式。
1 | <Style TargetType="{x:Type Button}"> |
如果对单个按钮不想使用该样式
<Button Style="{x:Null}" ...></Button>
Note:如果遵循正确的设计原则,单独定义按钮,这种技术效果更好。
包含基于类型的样式的组合的资源字典一般被称为主题theme 。使用面向搜索引擎编程的技术可以很容易地插入第三方的精彩界面。
由用户选择的皮肤
在使用时想换,可以啊
1 | private void chkGreen_Checked(object sender,RoutedEventArgs e) |
上面的代码加载了GradientButtonVariant资源字典并且放到MergedDictionaries[0]集合的第一个位置。
如果想要为整个应用更改皮肤
Application.Current.Resources.MergedDictionaries[0]=newDictionary;
还可以使用pack URI语法加载在另一个程序集中定义的资源字典。
1 | ResourceDictionary resourceDictionary = new ResourceDictionary(); |
还有一种方法–可以手动设置button对象的Template或者Style属性来选用新的模板,但是这种方法需要Dynamic Resource引用实时加载。
还有一种通过编写代码来加载资源字典的方法。此为强类型的,并且可以添加属性方法及其他功能。
Vs不能自动完成为资源字典创建代码隐藏类。手动如下
1 | public partial class GradientButtonVariant:ResourceDictionary |
然后在资源字典中开类
1 | <ResourceDictionary x:Class="ControlTemplates.GradientButtonVariant" ...> |
然后就可以在该命名空间下使用代码创建它了
1 | private void button_Click(object sender, RoutedEventArgs e) |
在Solution Explorer中,如果希望cs嵌套在xaml下面,可以notepad中修改.csproj项目文件。在
1 | <Compile Include="TestHer3.xaml.cs"> |
构建更复杂的模板
隐含约定,如果使用自定义控件模板替代控件的标准模板,新模板必须满足控件代码实现的所有需要。
嵌套的模板
假设修改熟悉的ListBox控件。这玩意相当的复杂。
1 | <SolidColorBrush x:Key="StandardBorderBrush" Color="#888" /> |
控件模板示例
ControlTemplateExamples.获取的方法是http://tinyurl.com/9jtk93x网站下载。
Note:SimpleStyles是隐藏的WPF宝藏。他们提供了比默认的控件模板更易于理解和增强的模板,如果需要使用自定义外观增强通用控件,应该使用它作为起点。
可视化状态
控件可以使用TemplatePart特性指示控件模板应当包含具有特定名称的元素,可使用TemplateVisualState特性指示他们支持的可视化状态。例如按钮应该提供如下可视化状态。
1 | [TemplateVisualState{Name="Normal" ,GroupName="CommonStates"] |
1 | public class Button : ButtonBase |
小结
学习了如何使用基本的模板构建技术为WPF核心控件更换皮肤,而不必重新实现任何核心按钮功能。可以在整个应用程序中重用按钮模板,并能立即使用全新的设计替换该模板。恭喜。