XAML 了解
介绍
触发器能够根据属性的值更改属性值或执行操作,动态地更改控制的外观/行为,而无需创建新的控件。
XAML-触发器
使用触发器,可以自动完成简单的样式改变,需要使用样板事件处理逻辑。触发器通过Style.Triggers集合连接到样式。每个样式都可以有任意多个触发器,并且每个触发器都是System.Windows.TriggerBase的派生类的实例。
名称 | 说明 |
---|---|
Trigger | 这是一种最简单的触发器。它监测依赖项属性的变化,然后使用设置器改变样式 |
MultiTrigger | 与Trigger类类似,但是这种触发器联合了多个条件。只有满足了所有这些条件,才会启动触发器 |
DataTrigger | 这种触发器使用数据绑定。它与Trigger类似,只不过它监视的是所有绑定数据的变化 |
MultiDataTrigger | 联合多个数据触发器 |
EventTrigger | 这是最复杂的触发器。当一个事件发生时,这种触发器应用一个动画 |
-
属性触发器;
- 简单触发器; 可以为任何依赖项属性关联一个简单触发器,每个简触发器都指定了正在监视的属性,以及正在等待的属性值。当该属性值出现时,将自己动应用存储在Trigger.Setters集合中的设置器(但不能使用更复杂的触发器逻辑,(比较、范围、计算等))
- 为了理解触发器的工作原理,需要记住依赖性系统。本质上,触发器是众多覆盖从依赖项属性返回的值的属性提供者之一。但原始属性值(不管是在本地设置的还是通过样式设置的)仍会保留。只要触发器被禁用,触发之前的属性值就会再次可用。
- 当自定义元素时为了得到最佳结果,需要使用控件模板。然而控件板不能代替触发器—实际上,控件模板经常使用触发器,以充分利用这两个特征:可以完全地自定义控件,并且可以响应鼠标悬停、单击以及其他事件来改变它们可视化外观某些方面。
-
数据触发器;
-
事件触发器;
- 普通触发器等待一个属性发生变化,而事件触发器等待特定的事件被激发。
- XAML中必须在故事板中定义动画对象。
WPF-5大准则
- 硬件加速
- 分辨离无关性
- 控件无固定外观
- 声明式用户界面
- 基于对象的绘图
WPF-样式和行为
-
样式基础
- 与CSS类似,定义通用的格式化特性集合,并且为了保证一致性,在整个应用程序中应用它们,并且能够设置任何依赖属性,可以使用它们标准化示格式化的的特性如控件的行。WPF样式支持触发器(triggers)当属性发生变化时可以通过触发器改变控件样,还可以使用模板重新定义控件内置外观。
-
样式属性
- Setters, 设置属性值以及自动关联事件处理程序的Setter对象或EventSetter对象的集合
- Triggers,继承自TriggerBase类并具能够自动改变样式设置的对象的集合。例如,当另一个属性改变时,或者当发生某个事件时,可以修改样式
- Resources 希望用于样式的资源集合。例如,可能需要使用一个对象设置多个属性。这时作为资源创建对象然后再Setter对象中使用该资源,这样会更高效(而不是使用嵌套的标签作为每个setter对象的一部分创建对象)
- BasedOn 通过该属性可以创建继承自(并且可以有选择地进行重写)其它样式设置的更复杂样式
- TargetType 该属性标识应用样式的元素的类型。通过该属性可以创建只影响特定类型元素的设置器,并且还可以创建能够为恰当的元素类型自动起作用设置器
-
设置属性
- 每个Style对象包装了一个Setter对象的集合。每个Setter对象设置元素的单个属性,唯一的限制是设置只能改变依赖项属性不能修改其它属性
-
关联事件处理程序
- 如果希望将工具栏上的所有按钮连接到同一个Click事件处理程序,最好的方法是为包含所有按钮的Toolbar元素关联单个件处理程序对于这种情况没必要使用事件设置器
在许多情况下,明确地定义所有事件并完全避免使用事件设置器会更加清晰。如果需要为几个元素连接同一个事件处理程序,可以手动进行。还可以使用在容器级别上关联事件处理程序以及通过命令集中逻辑等技巧。
- 如果希望将工具栏上的所有按钮连接到同一个Click事件处理程序,最好的方法是为包含所有按钮的Toolbar元素关联单个件处理程序对于这种情况没必要使用事件设置器
-
多层样式
- 可以使用BasedOn属性创建一条完整的样式继承链。唯一年规则是,如果两次设置了同一个属性,最后的属性设置器(在继承链中最远的派生类中的设置器)会覆盖其它以前的定义。
- 除非有特殊原因要求一个样式继承自另一个样式(例如,第二个样式是第一样式的特例,并且只改变了继承来的大量设置中的几个特征),否则不要使用样式继承。
-
通过类型自动应用样式
- x:Key=“{x:Type Button}” 尽管自动样式非常方便,但是它们会让设计变小得更复杂。下面是几条原因
-
- 在具有许多样式和多层样式的复杂窗口中,跟跑完是否通过属性值继承或通过样式设置了某个特定属性有些困难(如果是通过样式设置的,那么是通过哪个样式设置的呢?).因此,如果希望改变一个简单的细节,需要查看整个窗口的全部标记。
-
- 窗口中的格式化操作在开始时通常更一般并且会逐渐变得越来越详细。如果刚才开始为窗口应用了自己动样式,在许多地方可能需要使用显式的样式覆盖自动样式。这会使整个设计变得复杂为每个希望设置的格式化特征的组合创建命名的样式,并根据名称应用它们会更直观。
-
- 再比如,如果为TextBlock元素创建一个自动样式,会同时修改使用TextBlock的其它控件(如模板驱动的ListBox控件)。
为避免出现这一问题最好果断地使用自动样式。如果决定使用自动样式为整个用户界面提供一个单一的一致的外观,可以尝试为特例使用明确的样式。
- 再比如,如果为TextBlock元素创建一个自动样式,会同时修改使用TextBlock的其它控件(如模板驱动的ListBox控件)。
-
行为
- 行为;自定义换完件是另一个在一个应用程序中(或多个应用程序之间)重用用户界面功能的技术。然而,自定义控件必需作为可视化内容和代码的紧密链接包进行创建。尽管自己定义控件非常强大,但是它不能适应需要大量具有类似功能的不同控件的情况(例如,为一组不同的元素添加鼠标悬停效果)因此、行为以及自定义控件都是互补的。
- 获取行为支持, 重用用户界面代码通用块的基础结构不是WPF的一部分,它被捆绑到Expression Blend需手动编写标记:
- System.Windows.InterActivity.dll 这个程序集定义了支持行的基本类。它是行为特征的基础。
- Microsoft.Expression.Interactions.dll这个程序集通过添加可选的以核心行为类为基础的动作和触发器,增加了一些有用的扩展。
- 理解行为模型;指导原则:
-
- 行为模型不是WPF的核心部分,所以行为不像样式和模板那样确定。换句话说,可以编写不使用行为的WPF应用程序,但是如果不使用样式和模板,不能创建比"Hello World" 深示更复杂的WPF应用程序
-
- 如果在Expressioon Blend上耗费大量时间,或希望为其Expression Blend用户开发组件,可能会对Expression Blend中的触发器特性感兴趣。尽管和WPF中的触发器系统使用相同的名称,但是它们不是相互重叠的,并且可以同时使用这两者。
-
- 如果不使用Expression Blend,可以完全略过其触发器特征----但是合肥市然应当分析ExpressionBlend提供的功能完整行为类。这是因为行为比Expression Blend的触发器更强大也更常用最终,你将准备查找那些提供了可以在您 自己的应用程序中使用的好看的、整洁的行为的第三方组件。
- 创建行为 ,行为旨在封装一些UI功能,从而可惟不用编写代码就能够将其应用到元素上。tinyurl.com/y922enn
数据绑定
数据绑定是从一个对象中提取信息,并在应用程的用户界面中显示所提取的信息的过程。
- 单向绑定
- 双向绑定
尽管数据绑定基础没有发生变化,WPF改进了对虚拟化和容器再循环的支持----对于确保超大列表的性能这两个特性是很关键的
1. 使用自定义对象绑定到数据库
当创建数据类时,应当遵循下面给出的几条基本指导原则:
- 快速打开和关闭连接,在每个方法调用中打开数据库连接,并在方法结束之前关闭连接。这样,连接就不会无意中保持打开状态。确保存在适当的时间关闭数据库连接的一种方法是使用using代码块。
- 实现错误 处理。使用错误 处理确保连接被 关闭,即使已经引发了一个异常。
- 遵循无状态的设计规则。通过参数收方法需要的所有信息,并通过返回值返回检测索到的所有数据。这样,在许多情况下可以避免复杂化(例如,如果需要创建多线程应用程序或者在一个服务器上驻留数据库组件)。
- 在一个位置保存连接字符串理想的情况是,保存在应用程序的配置文件中。
- 在应用程序中为了使窗口可以使用storeDB类,有如下几种选择:
- 当需要访问数据库时,窗口可以随时创建StoreDB类的一个实例。
- 可以将StoreDB类中的方法改变成静态方法。
- 可以创建StoreDB类的单一实例,并通过另一个类的静态属性使用该实例(遵循“工厂”模式)。
前两种选择是合理的,但是这两种选择都限制了灵活性,第一种选择不能用于缓存在多个窗口中使用的数据对象。第二种方法假定在StoreDB类中不需要保存任何特定于实例的状态。将StoreDB类中的方法转变为静态方法,会使得访问存储在不同后台数据存储中的Store数据库的实例变得更加困难。第三种方法最灵活,这种方法通过强制所有窗口通过一个属性进行工作,保存了交换台设计
更改通知
- 三种方法解决更新问题
- 将Product类中的每个属性都改成依赖项属性(对于这种情况,Product类必须继承自己DependencyObject类)。尽管这种方法可以让WPF自动执行相应的工作,但是最合理的做法是将期用于元素—在窗口中具有可视化外观的类。对于像Product这样的数据类,这并不是最自然的方法
- 可以为每个属性引发一个事件。对于这种情况,事件必须以porpertyNameChanged的形式进行命名(如UnitCostChanged)。当属性发生变化时,由你负责引发这个事件。
- 可以实现System.ComponentModel.INotifyPropertyChanged接口,该接口需要一个名为PropertyChanged的事件。无论何时属性发生变化都必须引发PropertyChanged事件,并且驼过将恬性名称作为字符串提供来指示哪个属性发生变化。当属性发生变化时仍然由你引发事件,但是不需要为每个属性定义一个单独的事件。
第一种方法依赖于WPF的依赖项属性基础架构,而第二种和第三种方法依赖于事件。通常当创建数据对象时,会使用第三种方法,对于非元素类而言这是最简单的选择。
注意
实际上还可以使用另一种方法,如果怀疑绑定对象已经发生了变化,并且绑定对象不支持任何恰当方式的更改通知,这时可以检测索BindingExpression 对象(使用FrameworkElement.GetBindingExpression()方法),半调用BingdingExpression.UpdateTarget()方法触发一个更新。显然,这是最笨拙的解决方案。
2.绑定到对象集合
所有派生自ItemsControl的类都能显示条目的完整列表。能够支持集合数据绑定的元素包括ListBox/ComboBox/ListView和DataGrid(以及用于显示层次化数据的Menu和TreeView).
看起来WPF只提供了少数抽个列表控件,但实际上可以使用这些控件以任意不同的方式显示数据这是因为列表控件支持数据模板,通过数据模板可以完全控制数据项的显示方式。
ItemsControl类中用于数据绑定的属性
名称 | 说明 |
---|---|
ItemsSource | 指向一个集合,该集合包含将在列表中显示的所有对象 |
DisplayMenberPath | 确定用于为每个项创建显示文本属性 |
ItemTemplate | 接受一个数据模板,用于为每个项创建可视化外观。这个属性比DisplayMenmberPath属性的功能更加强大 |
2.1 显示和编辑保合项
显示 可以将其他元素绑定到列表的SelectedItem属性,以显示更多与当前循名考实 择项相关的细节。有趣的是,可以使用类似的技术构建数据的“主-详细信息”显示。例如,可以创建一个显示一系列目录和一系列产品的窗口。当用户在第一个列表中选择一个目录时,可以在第二个列表中显示只属于当前目录的产品。
为了实现这种效果,需要一个父数据对象,该父数据对象通过一个属性提供相关子数据对象的集合。例如,可以构建一个Category产品,该产品具有一个名为Category.Products的属性,该属性包含属于该目录的产品。然后可以构建一个具有两个列表的“主-详细信息”显示。使用Category对象填 充第一个列表。为了显示相关的产品,将第二个列表-----该列表显示产口------绑定到第一个列表的selectedItem.products属性,这会告诉第二个列表获取当前Category对象,提取连接Product对象的集合,并显示链接的Product对象
2.2 插入和移除集合项
注意: 如果有一个从Windows窗体导入的对象模型,可以使用Windows窗体中与ObservableCollection类等效的BindingList集合。BindingList集合实现了IBindingList接口,而不是INotifyCollectionChanged接口,该接口包含的ListChanged事件与INotifyCollectionChanged.CollectionChanged事件扮演相同的角色。
2.3 绑定到ADO.NET对象
注意: 这并不是什么新的限制。即使在Windows窗体应用程序中,所有DataTable数据绑定也是通过DataView进行的。区别是在Windows窗体中可以隐藏这一实际过程。它充许用户编写看似直接绑定到DataTable对象的代码,而代码实际运行时使用的却是DataTable.DefaultView属性提供的DataView对象
2.4 绑定至LINQ表达式
注意: 完整讨论LINQ超出了本书的范围。ToList()是一个扩展方法,这意味着定义它的类并不是使用它的类。从技术角度讲,ToList()方法是在System.linq.Enumerable辅助类中定义的,并且所有IEnumerable对象都可以使用该方法。然后,如果Enumerable类超出了范围,就不能使用该方法,这意味着如果没有导入System.Linq名称空间,此处给出的代码就不能正常运行。
提高大列表的性能
3.1 虚拟化
UI虚拟化是列表公为当前显示项创建容器对象的一种技术。
- 有许多因素可能会破坏UI虚拟化支持,而且有时是意想不到的:
- 在ScrollViewer中放置列表控件 ScrollViewer为其子内容提供了一个窗口。问题是为子内容提供了无限的“虚拟”空间。在这个虚拟空间中,ListBox以完整尺寸渲染自身,显示所有子项。副作用是,每项在内存中都有其自己的ListBoxItem对象。只要将ListBox控件放入到不会试图限制其尺寸的容器中,都会发生这一问题;例如,如果将ListBox控件放到一个StackPanel面板页不是Grid面板中,也会生类似问题。
- ***改变列表控件的模板并且没有使用ItemsPresenter *** ItemsPresenter使用ItemsPanelTemplate,该模板指定了VirtualizingStackPanel面板,将会丢失虚拟化特性。
- 使用分组 分组自动配置列表使其使用基于像素的滚动而不是基于项的滚动,当切换到基于像素的滚动时,不可能支持虚拟化-----至少在当前WPF发布版本不可能。
- 不使用数据绑定 这应当是很显然的,但是如果通过编程填 充列表—例如,通过动态创建需要的ListBoxItem对象-----不会发生虚拟化。当然,可以考虑使用自己的优化策略,例如创建所需要的对象并且只在需要时创建。
3.2 项目容器再循环
项目容器再循环提高了滚动性能并且降低了内存消耗量,因为垃圾收集器不需要查找旧对象并释放它们,通常,为了确保向后兼容,对于除了DataGrid这外的所有控件,该特征默认是禁用的。如果一个大列表,应当总是启动用该特性。
<ListBox VirtualizingStackPanel.VirtualizationMode=“Recycling” …>
3.3 延迟滚动
为了提高滚动性能,可以开启延迟滚动(deferred scrolling).使用延迟滚动,当用户在滚动条上拖动滚动的同时会刷新列表显示
<ListBox ScrollViewer.IsDeferredScrollingEnabled=“True” … />
3.4验证
- 验证提供了以下两种选择用于捕获非法值:
- 可在数据对象中引发错误 为了告知WPF发生了错误,简单地从属性设置过程中抛出一个导常。通常,WPF会忽略所有在设置属性时抛出异常,但是可以进行配置,从而显示更有帮助的可视化指示。别外一种选择是在自定义的数据类中实现IDataErrorInfo接口,从而可以得到指示错误功能,而且不会抛出异常。
- 可以在绑定级别上定义验证 这种选择可以获得使用相同验证灵活性,而不用管使用的哪个输入控件。更好的是,因为是在不同的类中定义验证,所以可以很容易地在存储类似数据类型的多绑定中重用验证。