WPF--模板化

Posted X·3

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF--模板化相关的知识,希望对你有一定的参考价值。

WPF之模板化

数据模板

在本示例应用程序中,有一个绑定到照片列表的ListBox控件。

此ListBox当前的外观如下所示:

大多数控件都具有某种类型的内容,这些内容通常来自绑定到的数据。在本示例中,数据为照片列表。在WPF中,使用DataTemplate可以定义数据的可视表示形式。基本上,输入DataTemplate的内容决定了数据在呈现的应用程序中的外观。

在我们的示例应用程序中,每个自定义Photo对象都具有一个字符串类型的Source属性,该属性指定图像的文件路径。当前,照片对象显示为文件路径。

对于要显示为图像的照片,可以将DataTemplate作为资源创建:

注意,DataType属性与Style的TargetType属性非常相似。如果DataTemplate位于资源部分,并且将DataType属性指定为某个类型,也不为其分配x:Key,则只要该类型出现,便会应用DataTemplate。任何时候都可以为DataTemplate分配x:Key,然后将其设置为DataTemplate类型的属性(如ItemTemplate属性或ContentTemplate属性)的StaticResource。

实质上,上面示例的DataTemplate确定只要存在Photo对象,该对象就应作为Image显示在Border中。通过此DataTemplate,应用程序现在的外观如下:

数据模板化模型还提供其他功能。例如,如果要使用HeaderedItemsControl类型(如Menu或TreeView)显示包含其他集合的集合数据,则可以使用HierarchicalDataTemplate。另一个数据模板化功能是DataTemplateSelector,利用这一功能可以根据自定义逻辑选择要使用的DataTemplate。有关更多信息,请参见数据模板概述,该概述对不同的数据模板化功能进行了更加深入的讨论。

控件模板

注意,我们的照片显示为图片,我们要水平显示这些照片,而不是垂直显示;我们希望 ListBox 是水平的。

不使用 ControlTemplate

首先,要使ListBox水平,不一定要使用ControlTemplate,明确这一点很重要ListBox具有ItemsPanel属性,利用该属性可以设置ItemsPanelTemplate,即控制ListBox的项的布局的模板。一种方法是只创建ListBox样式,然后设置ItemsPanel属性,如下例所示:

例表明,除了替换ControlTemplate之外,可能还有其他方法,这取决于具体的情况。在本示例中,如果希望获得具有其他属性(如圆角)的水平ListBox,则需要使用ListBox的ControlTemplate。

在提供示例来演示具体操作之前,首先需要说明ControlTemplate的概念。

什么是 ControlTemplate?

大多数控件都具有外观和行为。以按钮为例:外观是可以按下的凸起区域,行为是在响应单击时所引发的Click事件。

有时,控件可以提供所需行为,但不具有所需外观。到目前为止,我们已经演示了可以使用样式setter来设置属性值,从而影响控件的外观。但是,若要更改控件的结构,或对组成控件的组件设置属性值,就需要使用ControlTemplate。

在WPF中,控件的ControlTemplate定义控件的外观。通过为控件定义新的ControlTemplate可以更改控件的结构和外观。很多情况下,这种方法都足够灵活,您不需要自己编写自定义控件。如果没有为控件定义自己的ControlTemplate,则可以获取与系统主题匹配的默认模板,该模板向Button控件提供默认外观。

注意:一旦为控件创建ControlTemplate,就会替换整个ControlTemplate。例如,可以通过以下方式定义Button ControlTemplate。

注意,ContentPresenter元素只标记Button的Content应出现在何处。后面有一节专门展开详细讨论。

应用此模板之后,Button显示为Ellipse:

请记住,当Button具有焦点或按下时,其外观都是将替换的按钮的默认外观的组成部分。因此,您可能希望定义按钮按下时的外观,这取决于您的具体需要。

如果要创建ControlTemplate,使用ControlTemplate示例 是最好的入门方法。如果确实需要查看控件的组成部分,可以查看位于主题的主题文件,也可以使用XAMLPad(注:随Windows软件开发工具包(SDK)安装的应用程序)的Show Visual Tree功能。

创建 ControlTemplate

现在,继续演示示例,我们创建一个ControlTemplate,它定义一个水平的圆角ListBox。若要替换控件的ControlTemplate,请将Template 属性设置为新的ControlTemplate。

以这种方式设置Template属性,实际上与使用Style设置其他控件属性没有区别:您将Style用作一个工具来帮助设置Template属性。也就是说,设置ControlTemplate的另一种方法是直接设置控件的 Template 属性。如果以这种方式设置,则会在Resources节中创建一个ControlTemplate,并为它提供x:Key,然后将它作为静态资源使用。

如上例所示,ControlTemplate类具有TargetType属性,该属性类似于Style类的TargetType属性。但要注意,与Style和DataTemplate不同ControlTemplate对象没有隐式键的概念。换言之,如果有一个独立ControlTemplate,其TargetType属性设置为某个类型,则ControlTemplate不会自动应用于该类型。另请注意,如果模板定义包含ContentPresenter,则ControlTemplate需要TargetType属性。

尝试使用ControlTemplate。例如,用WrapPanel替换StackPanel,将ScrollViewer的HorizontalScrollBarVisibility属性设置为Disabled,然后将ListBox的宽度设置为 300。(只有第一行空间不足时,WrapPanel才会将项放置到下一行。如果没有将ScrollViewer的HorizontalScrollBarVisibility属性设置为 Disabled,由于可以滚动到末尾,则第一行不会空间不足。因此,WrapPanel不会对项进行换行。)

IsItemsHost 属性

在此示例中,一个必需的重要属性是IsItemsHost属性。IsItemsHost属性用于指示在ItemsControl(如处理项列表的ListBox 控件)的模板中,生成的元素应放在什么位置。如果将StackPanel的这一属性设置为true,则添加到ListBox的所有项都将进入StackPanel。请注意,此属性只对Panel类型有效。

ItemsPresenter和ContentPresenter

请注意,如果以这种方式在ControlTemplate中指定一个面板并将其标记为IsItemsHost,控件的用户不使用ControlTemplate就无法替换ItemsPanel。因此,除非您确信必须使用模板才能替换面板,否则不要采用这种方式。此外,您也可以使用ItemsPresenter元素来标记项的位置,然后通过设置ItemsPanel属性来指定ItemsPanelTemplate。ItemsPanelTemplate页提供了一个示例,为您演示如何操作。

如果要创建ContentControl(如Button)的模板,则对应元素为ContentPresenter。同样,将此元素放置到ContentControl类型的ControlTemplate中,可以指示内容应在什么位置显示,如什么是ControlTemplate?一节中的示例所示。有关其他示例,请参见Label ControlTemplate示例和ListBoxItem ControlTemplate示例。

TemplateBinding

在上一示例中,需要注意的另一个重点是设置为TemplateBinding ListBox.Background的Background值。它只是指示Border的Background应与 ListBox上设置的Background值同步。TemplatBinding与Binding类似。实际上,TemplatBinding比Binding更有效,但功能更弱;使用TemplatBinding等效于使用Source属性设置weiRelativeSource.TemplatedParent的Binding。

若要使控件用户能够控制某些属性的值,可以在ControlTemplate中使用 TemplateBinding。TemplateBinding是一个由TemplateBindingExtension类表示的标记扩展。

您可能已经注意到,DataTemplate和ControlTemplate的相似之处在于它们的内容变成了对象的外观。通过ListBox ControlTemplate定义,应用程序现在的外观如下:

触发器

Style、ControlTemplate和DataTemplate都具有Triggers属性,该熟悉可以包含一组触发器。某个属性值更改时,或某个事件引发时,触发器会相应地设置属性或启动操作(如动画操作)。

属性触发器

为了演示如何使用触发器来设置属性,我们将每个ListBoxItem都设置为部分透明(除非它被选中)。

下面的样式将ListBoxItem的Opacity值设置为0.5。但是,当IsSelected属性为true时,Opacity设置为1.0:

此示例使用Trigger来设置属性值,但请注意,Trigger类还具有EnterActions和ExitActions属性,通过这两个属性,触发器可以执行操作。

注意,我们还将ListBoxItem和 MaxHeight属性设置为75。在下面的屏幕快照中,选中的项是第三项:

EventTrigger和Storyboard

我们刚刚演示了Trigger根据某个属性的值来设置属性值或启动操作。另一种类型的触发器是EventTrigger,它根据事件的引发来启动一组操作。例如,下面的EventTrigger对象指定当鼠标指针进入ListBoxItem时,MaxHeight属性在0.2秒时间内以动画方式增大为值90。当鼠标离开该项时,该属性在 1 秒时间内还原为原始值。请注意为何无需为MouseLeave动画指定To值。这是因为动画能够跟踪原始值。

在下面的屏幕快照中,鼠标指向第三项:

MultiTrigger、DataTrigger和MultiDataTrigger

除了Trigger和EventTrigger之外,还有其他类型的触发器。通过MultiTrigger,可以根据多个条件来设置属性值。如果条件的属性是经过数据绑定的,则可以使用DataTrigger和MultiDataTrigger。

共享的资源和主题

典型 Windows Presentation Foundation(WPF)应用程序可能具有多个在整个应用程序范围内应用的用户界面(UI)资源。概括地说,这组资源可视为应用程序的主题。通过使用封装为ResourceDictionary类的资源字典,Windows Presentation Foundation(WPF)支持将用户界面(UI)资源打包为主题。

Windows Presentation Foundation(WPF)主题是使用样式设置和模板化机制定义的,Windows Presentation Foundation(WPF)公开该机制,用于自定义任何元素的可视对象。

Windows Presentation Foundation(WPF)主题资源存储在嵌入式资源字典中。这些资源字典必须嵌入到已签名的程序集中,它们既可以嵌入到代码自身所在的程序集中,也可以嵌入到并行程序集中。对于包含Windows Presentation Foundation(WPF) 控件的程序集PresentationFramework.dll,主题资源在一系列并行程序集中。

搜索元素样式时,主题是最后查找的位置。通常,搜索首先沿元素树向上查找相应资源,然后在应用程序资源集合中查找,最后查询系统。这为应用程序的作者提供了机会,让他们可以在到达主题之前,在树或应用程序级重新定义任何对象的样式。

您可以将各资源字典分别定义为单个文件,这样就可以在多个应用程序中重用某个主题。通过定义多个提供相同类型资源、但具有不同值的资源字典,也可以创建可交换的主题。在设计应用程序外观时,建议在应用程序级重新定义这些样式或其他资源。

若要在多个应用程序中共享一组资源(包括样式和模板),可以创建一个 XAML 文件并定义一个 ResourceDictionary。例如,请看下面的屏幕快照,它显示了使用 ControlTemplates 设置样式的示例的一部分:

如果查看示例中的 XAML 文件,您会注意到所有文件都包含以下内容:

这是共享的 shared.xaml,该文件定义一个 ResourceDictionary,该资源字典包含一组样式和画笔资源,使示例中的控件具有了一致的外观。

以上是关于WPF--模板化的主要内容,如果未能解决你的问题,请参考以下文章

WPF-08 控件模板

WPF模板

拉伸不适用于 WPF 模板化按钮

WPF 控件模板

WPF学习第五十九章 理解控件模板

WPF:换出控制模板可以提高性能和效率?