导图社区 WPF开发系统总结学习笔记
WPF开发相关的笔记。WPF基本概念、XAML基本语法、控件与布局、Binding、依赖属性与附加属性、路由事件与附加事件、命令、资源、模板与样式、2D绘图与动画、3D绘图等内容。导图中的注释还有很多相关的详细说明与示例代码,希望能帮到大家!
编辑于2022-10-20 13:10:33介绍归纳 C Sharp 语音的基础重点知识。包括语言基础、字面常量、程序集、不安全代码、基础类、枚举、数组、泛型、字符串、正则表达式、委托与事件、文件、异常、多线程、异步、反射、网络、绘图、WinForm、Windows、跨平台调用等内容。思维导图示例中,有示例代码,方便学习与练习。
这份思维导图归纳了一些HTML基本的元素标签、布局、表单,以及 HTML5 API 如 WebSockets、Fetch API 等内容。CSS 主要是归纳了选择器。JavaScript 主要是包含了函数与箭头函数、this 关键字、Promise 异步对象。此外还有AJAX、jQuery 与 jQuery AJAX、JSONP 等内容。导图中的注释有很多相关的详细说明与示例代码,其中后端的测试代码是用的 PHP。希望能帮到大家!
WPF开发相关的笔记。WPF基本概念、XAML基本语法、控件与布局、Binding、依赖属性与附加属性、路由事件与附加事件、命令、资源、模板与样式、2D绘图与动画、3D绘图等内容。导图中的注释还有很多相关的详细说明与示例代码,希望能帮到大家!
社区模板帮助中心,点此进入>>
介绍归纳 C Sharp 语音的基础重点知识。包括语言基础、字面常量、程序集、不安全代码、基础类、枚举、数组、泛型、字符串、正则表达式、委托与事件、文件、异常、多线程、异步、反射、网络、绘图、WinForm、Windows、跨平台调用等内容。思维导图示例中,有示例代码,方便学习与练习。
这份思维导图归纳了一些HTML基本的元素标签、布局、表单,以及 HTML5 API 如 WebSockets、Fetch API 等内容。CSS 主要是归纳了选择器。JavaScript 主要是包含了函数与箭头函数、this 关键字、Promise 异步对象。此外还有AJAX、jQuery 与 jQuery AJAX、JSONP 等内容。导图中的注释有很多相关的详细说明与示例代码,其中后端的测试代码是用的 PHP。希望能帮到大家!
WPF开发相关的笔记。WPF基本概念、XAML基本语法、控件与布局、Binding、依赖属性与附加属性、路由事件与附加事件、命令、资源、模板与样式、2D绘图与动画、3D绘图等内容。导图中的注释还有很多相关的详细说明与示例代码,希望能帮到大家!
WPF
笔记作者:ERIZEIUM
1. 概念
程序的多层架构
数据层(数据存储层)
技术
数据文件
数据库
业务逻辑层(数据处理层)
子层
数据访问层
产品
基于 WCF 和 Entity Framework 的
WCF Data Service
WCF RIA Service
技术
WCF
全称
Windows Communication Foundation
主要作用
编写分布式应用程序的业务逻辑层
WF
全称
Windows Workflow Foundation
主要作用
设计工作流
表示层(数据展示层)
技术
WPF
用于编写程序表示层的技术和工具
全称:Windows Presentation Foundation
与表示层相关的 4 种功能性代码
数据模型
现实世界中事物和逻辑的抽象
业务逻辑
数据模型之间的关系与交互
界面逻辑
控件与控制之间的关系与交互
用户界面
由控件构成的、与用户进行交互的界面,用于把数据展示给用户并响应用户的输入
改善数据与界面交互的探索历史
模式
MVC(Model-View-Controller)
图示

MVP(Model-View-Presenter)
图示

MVVM(Model-View-ViewModel)
相关框架: Stylet、 Prism、 MvvmLight 等。
图示

组成
模型
是指代表真实状态内容的领域模型(面向对象),或指代表内容的数据访问层(以数据为中心)。
视图
是指用户在屏幕上看到的结构、布局和外观(UI)。
视图模型
是暴露公共属性和命令的视图的抽象。
MVVM 没有 MVC 模式的控制器,也没有 MVP 模式的 Presenter,有的是一个绑定器。在视图模型中,绑定器在视图和数据绑定器之间进行通信。 绑定器:声明性数据和命令绑定隐含在 MVVM 模式中。在 Microsoft 解决方案堆中,绑定器是一种名为 XAML 的标记语言。绑定器使开发人员免于被迫编写样板式逻辑来同步视图模型和视图。在微软的堆之外实现时,声明性数据绑定技术的出现是实现该模式的一个关键因素。
核心原则
当任何外部事件发生时,永远只操作 ViewModel 中的数据。
这里外部事件主要指界面点击、文字输入、网络通信等等事件。因为绑定关系的存在,ViewModel 变成啥样,界面就会自动变成啥样。
理念
UI 驱动
消息驱动
特征
用户操作产生消息,Windows 系统将消息送给应用程序
技术
Windows API
MFC
事件驱动
特征
关系
事件→订阅→事件处理器
用户操作激发事件,事件处理器响应事件、操作数据
数据是被动的
界面控件是主动的
技术
Windows Forms
Web Forms
数据驱动
特征
关系
数据是“内容”,控件是“形式”
内容决定形式 => 数据驱动界面
桥梁
数据关联/数据绑定(Data Binding)
数据变化时,会通知界面。用户对控件的操作也会送达数据
数据占主导地位
控件和控件事件被弱化
控件事件一般只参与界面逻辑
技术
WPF
2. XAML
WPF 项目文件
App.xaml
x:Class
该文件 x 命名空间中的 Class 特性声明程序的应用程序类
指示 XAML 编译器,将由 XAML 生成的类与哪个 C# 的 partial 类合并
StartupUri 特性
声明程序的启动窗体
关于“代码后置”
描述程序 UI 的代码在 XAML 文件,描述逻辑的 C# 代码在 cs 文件。逻辑代码隐藏在 UI 代码后面。 两者在 partial 类的支持下,通过事件性的 Attribute 建立联系。
例: <Button Click="Button_Click"/>
特点
XAML
是一种单纯的声明型语言
声明 UI 元素
绘制 UI 和动画
是一种派生于 XML 的语言
出现一个标签,就意味着声明了一个对象
映射关系
标签 对应 类
特性 对应 属性
层级关系
包含
并列
C# 的 partial 机制为更好地实现视图与逻辑代码分离提供技术基础
Attribute、Property 说明
Attribute 表示文法层面的特征、特性
Property 表示对象层面的属性
XAML 文档树形结构
影响着 UI 布局设计
影响着 WPF 的属性子系统、事件子系统等
编程时可以在树上按名称查找元素、获取父/子节点等
WPF 提供了 LogicalTreeHelper 和 VisualTreeHelper 类以辅助操作
命名空间
xmlns 关键字
用于定义使用的命名空间
格式
xmlns[:可选的映射前缀]="命名空间"
引用程序集中的
引入内部的
xmlns[:可选的映射前缀]="clr-namespace:命名空间"
例: xmlns:local="clr-namespace:WpfApp1"
引入外部的
xmlns[:可选的映射前缀]="clr-namespace:命名空间;assembly=程序集名称"
例: xmlns:sys="clr-namespace:System;assembly=mscorlib"
硬编码引入
XAML 解析器解析到这些硬编码(一般为 URI,URI 可以很好地保证唯一性)后,会将对应的程序集、命名空间引入
例: xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
使用命名空间中内容的格式
[可选的映射前缀:]类名
x 命名空间
对应一些与 XAML 语法和编译相关的内容
内容主要分类
Attribute (附加属性)
x:Class
指示 XAML 编译器,将由 XAML 生成的类与哪个 C# 的 partial 类合并
x:ClassModifier
设置 x:Class 指定生成的类的访问控制级别
例:
x:ClassModifier="public"
x:Subclass
x:Name
指示 XAML 编译器为该对象声明一个引用变量 (否则是匿名的,只能通过其容器访问)(在同一XAML文件内,该变量名需要唯一), 如果该对象有 Name 属性,则其值将一同设置
一个 XAML 标签对应一个对象。在 .NET 平台,通过“引用者”引用实例,常见的引用者是引用变量。
x:FieldModifier
设置 x:Name 指定的引用变量的访问控制级别
x:Key
为资源设置在资源字典中的索引(键)
例:
<Window.Resources> <sys:String x:Key="myStr" x:Shared="false">Hello1</sys:String> <sys:String x:Key="{x:Type sys:String}" x:Shared="false">Hello2</sys:String> </Window.Resources> XAML 中使用: <TextBox Text="{StaticResource ResourceKey=myStr}"/> <TextBox Text="{StaticResource ResourceKey={x:Type sys:String}}"/> C# 代码中使用: string str1 = this.FindResource("myStr") as string; string str2 = this.FindResource(typeof(System.String)) as string; 注意,XAML 中使用 String 类型需要引用程序集: xmlns:sys="clr-namespace:System;assembly=mscorlib"
x:Shared
设置检索出资源时,是获得共享对象(true,默认),还是获得副本(false)
x:Uid
x:TypeArguments
标记扩展
x:Type
其值是一个 Type 数据类型值
例:
class MyButton : Button { public Type UserType {get;set;} } <local:MyButton UserType="{x:Type TypeName=某类型名}" /> <local:MyButton UserType="{x:Type TypeName=TextBlock}" /> 附: C# 代码中可以使用 Type 数据创建实例,如下方法: System.Activator.CreateInstance(Type type)
x:Null
显式地将一个属性赋为空值
例:
清除控件被默认应用的(非默认)样式
<Button Style="{x:Null}"/> 等价的属性元素赋值方法: <Button> <Button.Style> <x:Null/> </Button.Style> </Button>
x:Array
通过其 Items 属性向使用者暴露一个已知类型的 ArrayList 实例。元素类型由 Type 属性指明
在 XAML 中声明包含数据的 x:Array 实例(只能使用属性元素方式)
例:
<ListBox> <ListBox.ItemsSource> <x:Array Type="sys:String"> <sys:String>Tim</sys:String> <sys:String>Tom</sys:String> <sys:String>Victor</sys:String> </x:Array> </ListBox.ItemsSource> </ListBox>
x:Static
在 XAML 中使用一个类的 static 数据成员
例:
public partial class Window1 : Window { public static string txt = "山高月小"; } <TextBox Text="{x:Static Member=local:Window1.txt}"/>
XAML 指令元素
x:Code
在 XAML 中包含 C# 代码
例:
<Button x:Name="button" Content="转换" Click="Button_Click"/> <x:Code> <![CDATA[private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show("你好"); }]]> </x:Code>
x:XData
声明 XmlDataProvider 实例的数据
例:
WPF 中把包含数据的对象称为数据源,将数据源中的数据提供给数据使用者的对象称为数据提供者(Data Provider)。WPF 中有一种数据提供者 XmlDataProvider 专门提供 XML 化的数据。 <Window.Resources> <XmlDataProvider x:Key="SupermarketData" XPath="Supermarket/Areas"> <x:XData> <Supermarket xmlns=""> <Areas> <Clothing Name="服装"> <Suit Style="款1">套装1</Suit> <Suit Style="款2">套装2</Suit> </Clothing> <Fruits> <Orange>橙子1</Orange> <Orange>橙子2</Orange> <Orange>橙子3</Orange> </Fruits> </Areas> </Supermarket> </x:XData> </XmlDataProvider> </Window.Resources>
声明的对象
属性的赋值方法
使用标签的 Attribute
普通赋值
来源
即 XML 的 Attribute=Value 文法
Value 为字符串形式
例:
<TextBox Text="文本"/>
XAML 处理器处理
Value 部分
被识别为字符串
或 使用属性类型的类型转换器转换为对象
值的转换
内部支持的类型转换
例:
<Rectangle Fill="Blue"/> Blue 字符串最终被 Brush 类型的 BrushConverter 类型转换器转换为 Brush (派生类)对象并赋值给 Rectangle 的实例的 Fill 属性。
自定义类型转换
Value 字符串与 CLR 对象之间的转换
继承 TypeConverter
例:
转换器实现
using System.ComponentModel; using System.Globalization; //(2)设置本类型的转换器特性: [TypeConverterAttribute(typeof(HumanTypeConverter))] public class Human { public string Name { get; set; } public Human Child { get; set; } } //(1)定义类型转换器: //继承 TypeConverter 类,重写 ConvertFrom 方法及其它需要的方法。 public class HumanTypeConverter : TypeConverter { //判断是否可以将给定类型转换为本类型 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } return base.CanConvertFrom(context, sourceType); } //将给定类型对象转换为本类型对象 public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string) { Human h = new Human(); h.Name = value as string; return h; } return base.ConvertFrom(context, culture, value); } //判断是否可以将本类型转换为给定类型 public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { if (destinationType == typeof(string)) { return true; } return base.CanConvertTo(context, destinationType); } //将本类型对象转换为给定类型对象 public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(string)) { Human h = value as Human; return h.Name; } return base.ConvertTo(context, culture, value, destinationType); } }
XAML 代码
<Window.Resources> <!--Value 字符串转换为 CLR 对象--> <local:Human x:Key="human" Child="abc"/> </Window.Resources> <StackPanel> <Button x:Name="button" Content="转换" Click="Button_Click"/> </StackPanel>
C# 逻辑代码
private void Button_Click(object sender, RoutedEventArgs e) { #region 测试 //获取资源中的对象 Human h = this.FindResource("human") as Human; //转换器转换 HumanTypeConverter converter = new HumanTypeConverter(); //新建转换器实例 if (converter.CanConvertFrom(typeof(string))) //方法内部将以参数列表(null, sourceType) 调用 CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { h = converter.ConvertFrom("def") as Human; //方法内部将以参数列表(null, CultureInfo.CurrentCulture, value) 调用 ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) } string hStr = string.Empty; if (converter.CanConvertTo(typeof(string))) { hStr = converter.ConvertTo(h, typeof(string)) as string; } TypeConverter converter2 = TypeDescriptor.GetConverter(typeof(Human)) as HumanTypeConverter; //类附加了 TypeConverterAttribute 时,可以通过 TypeDescriptor.GetConverter 静态方法获取转换器实例 if (converter2.CanConvertFrom(typeof(string))) { h = converter2.ConvertFrom("abc") as Human; } #endregion }
标记扩展赋值
是一种特殊的 Attribute=Value 语法
Value字符串的内容为 "{标记扩展标识符 [后续标记]}" 格式
{标记扩展标识符 [后续标记]} 中的内容一般不使用 "字符串" 形式,而是直接使用 符号名 形式
例:
Binding标记扩展例: <Button x:Name="button" Content="按钮" Click="Button_Click"/> <TextBox x:Name="textBox" Text="{Binding ElementName=button,Path=Content,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> 等价的C#代码: textBox.SetBinding(TextBox.TextProperty, new Binding() { ElementName = "button", Path = new PropertyPath("Content"), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); 等价的属性元素: <TextBox x:Name="textBox"> <TextBox.Text> <Binding ElementName="button" Path="Content" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"/> </TextBox.Text> </TextBox>
标记内容被 XAML 处理器识别为对标记指定类型的构造方法的调用与属性赋值,以生成对象
对象的类型名会采用标记扩展标识符的名称
即位置紧邻“{”的第1个符号名
说明
只有 System.Windows.Markup.MarkupExtension 的派生类才能使用标记扩展
标记扩展的类一般以 Extension 为后缀,在 XAML 中该后缀可省略
例:
"{x:Static ...}" 等价于 "{x:StaticExtension ...}"
更多语法
标记扩展标识符的后续标记
逗号“,”代表各个标记的分隔符
若从后续标记的第1个标记起,连续的数个标记均不包含赋值语句, 则每个标记都将被视为构造方法的对应参数
此时,标记相当于固定位置参数
对于后续 名称=值 形式的标记, 会被识别为对属性名称对应属性的赋值
此时,标记相当于具名参数,即指定初始化项
若值的第1个字符为“{”,则需要在前面添加“{}”进行转义,防止被识别为嵌套的标记扩展
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <sys:Double x:Key="dbl1">123456.789</sys:Double> <sys:DateTime x:Key="date1">2021-01-02 03:04:05.678</sys:DateTime> </Window.Resources> <StackPanel> <!--格式字符串值的第1个字符是“{”,在前面添加“{}”进行转义:--> <TextBlock Text="{Binding Source={StaticResource dbl1},Path=.,StringFormat={}{0:F1}}" /> <TextBlock Text="{Binding Source={StaticResource date1},Path=.,StringFormat={}{0:yyyy-MM-dd HH:mm:ss.fff}}" /> <TextBlock Text="{Binding Source={StaticResource date1},Path=.,StringFormat='{}{0:yyyy-MM-dd HH:mm:ss.fff}'}" /> <!--格式字符串值的第1个字符不是“{”,前面不用添加“{}”:--> <TextBlock Text="{Binding Source={StaticResource dbl1},Path=.,StringFormat=小数:{0:F1}}" /> <TextBlock Text="{Binding Source={StaticResource date1},Path=.,StringFormat=时间:{0:yyyy-MM-dd HH:mm:ss.fff}}" /> <TextBlock Text="{Binding Source={StaticResource date1},Path=.,StringFormat=yyyy-MM-dd HH:mm:ss.fff}" /> <TextBlock Text="{Binding Source={StaticResource date1},Path=.,StringFormat='yyyy-MM-dd HH:mm:ss.fff'}" /> </StackPanel> </Window>
例:
<StackPanel> <Grid x:Name="grid1" Background="Red" Margin="10" > <DockPanel x:Name="dockPanel1" Background="Orange" Margin="10"> <Grid x:Name="grid2" Background="Yellow" Margin="10"> <DockPanel x:Name="dockPanel2" Background="LawnGreen" Margin="10"> <!--RelativeSource 处的标记扩展:对应调用 public RelativeSource(RelativeSourceMode mode, Type ancestorType, int ancestorLevel) 构造方法:--> <TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource FindAncestor,{x:Type Grid},1},Path=Name}" Margin="10"/> <!--RelativeSource 处的标记扩展:先调用 public RelativeSource() 构造方法,再对 Mode、AncestorType、AncestorLevel 属性赋值--> <TextBox x:Name="textBox2" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DockPanel},AncestorLevel=2},Path=Name}" Margin="10"/> </DockPanel> </Grid> </DockPanel> </Grid> </StackPanel>
标记扩展可以嵌套
例:
Text="{Binding Source={StaticResource mySrc}, Path=PersonName}"
使用属性元素(Property Element)
来源
XML 文法
非空标签均有内容(Content)
每个子级标签都是父级标签内容的一个元素(Element)
属性元素
是指某个标签的一个元素对应这个标签的一个特性,从而可以对应对象的一个属性
指定类型某属性的语法
在类型后通过“.”操作符访问属性
形式: <类型> <类型.属性> <值/> </类型.属性> </类型>
例:
<Rectangle.Fill> <SolidColorBrush Color="Blue"/> </Rectangle.Fill>
特点
属性元素代码整体比较冗长,更适合用于给类型复杂的属性赋值
简写
简写前 例:
<Grid VerticalAlignment="Center" HorizontalAlignment="Center"> <Rectangle x:Name="rectangle" Width="200" Height="120"> <Rectangle.Fill> <LinearGradientBrush> <LinearGradientBrush.StartPoint> <Point X="0" Y="0"/> </LinearGradientBrush.StartPoint> <LinearGradientBrush.EndPoint> <Point X="1" Y="1"/> </LinearGradientBrush.EndPoint> <LinearGradientBrush.GradientStops> <GradientStopCollection> <GradientStop Offset="0.2" Color="LightBlue"/> <GradientStop Offset="0.7" Color="Blue"/> <GradientStop Offset="1.0" Color="DarkBlue"/> </GradientStopCollection> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Grid>
简写后 例:
<Grid VerticalAlignment="Center" HorizontalAlignment="Center"> <Rectangle x:Name="rectangle" Width="200" Height="120"> <Rectangle.Fill> <LinearGradientBrush> <LinearGradientBrush.GradientStops> <GradientStop Offset="0.2" Color="LightBlue"/> <GradientStop Offset="0.7" Color="Blue"/> <GradientStop Offset="1.0" Color="DarkBlue"/> </LinearGradientBrush.GradientStops> </LinearGradientBrush> </Rectangle.Fill> </Rectangle> </Grid>
技巧
尽量使用字符串形式赋值
充分利用默认值
充分利用 XAML 的简写方式
如 LinearGradientBrush.GradientStops 属性的类型为 GradientStopCollection,XAML 允许省略这个集合类型的标签而把集合元素直接写在属性元素的内容里(因为 XAML 标签的内容区域映射了控件的内容属性)。 控件的内容属性也支持类似的简写。 如: <Button> <Button.Content> <sys:String>OK</sys:String> </Button.Content> </Button> 简写为: <Button> <sys:String>OK</sys:String> </Button>
3. 控件与布局
概念
UI 的功能是让用户
观察数据
用 UI 元素显示数据
操作数据
用 UI 元素响应用户的操作
WPF 把能够显示数据、响应用户操作的 UI 元素称为控件
控件所显示的数据称为控件的“数据内容”
控件响应用户操作后会执行自己的一些方法或以事件的形式通知应用程序,这称为控件的“行为”或“算法内容”
在概念上,控件是数据和行为的载体,无需具有固定的形象
主要控件
分类
布局控件
可以容纳多个控件或嵌套其它布局控件
父类
Panel
内容属性
Children
如 Grid、StackPanel、DockPanel
内容控件
只能容纳一个其它控件或布局控件作为它的内容
父类
ContentControl
内容属性
Content
如 Window、Button
带标题内容控件
相当于一个内容控件,但可以加标题,标题部分可以容纳一个控件或布局
父类
HeaderedContentControl
内容属性
Content
Header
如 GroupBox、TabItem
条目控件
可以显示一列数据,一般情况下这列数据类型相同
父类
ItemsControl
内容属性
Items 或 ItemsSource
如 ListBox、ComboBox
带标题条目控件
相当于一个条目控件,但可以加标题
父类
HeaderedItemsControl
内容属性
Items 或 ItemsSource
Header
如 TreeViewItem、MenuItem
特殊内容控件
如 TextBox 容纳字符串
如 TextBlock 容纳带格式文本
如 Image 容纳图片类型数据
派生关系
图示

主要 UI 元素类型
控件的内容属性
控件和控件的内容都是内存中的对象,控件通过某个属性引用内容对象,这个属性称为内容属性
不同控件的内容属性有不同的名字,如 Content、Child、Items、Children
控件的内容属性由 ContentPropertyAttribute 特性指定,如: [ContentPropertyAttribute("Content")] public class ContentControl : Control, IAddChild { }
控件详解
ContentControl 族
包含
内容
属性
Content
内容
单一元素
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <!--设置 Window 对象的 Content 属性:--> <Window.Content> <StackPanel> <TextBlock Text="任务栏按钮中显示的进度:"/> <Slider x:Name="slider1" Maximum="1.0" TickFrequency="0.1" TickPlacement="BottomRight"/> </StackPanel> </Window.Content> <Window.TaskbarItemInfo> <TaskbarItemInfo ProgressValue="{Binding ElementName=slider1,Path=Value}" ProgressState="Normal"/> </Window.TaskbarItemInfo> </Window>
HeaderedContentControl 族
包含
内容
属性
Content
Header
内容
单一元素
ItemsControl 族
每种控件都有自己的条目容器(ItemContainer)
会自动使用其条目容器包装每个条目
包含
内容
属性
Items 或 ItemsSource
例:
ListBox
条目容器:ListBoxItem
显示控件
<ListBox> <ListBox.Items> <ListBoxItem> <CheckBox Content="1"/> </ListBoxItem> <CheckBox Content="2"/> <CheckBox Content="3"/> <Button Content="4"/> <Button Content="5"/> <Button Content="6" Click="Button_Click"/> </ListBox.Items> </ListBox> 包装器测试: private void Button_Click(object sender, RoutedEventArgs e) { Button btn = sender as Button; DependencyObject level1 = VisualTreeHelper.GetParent(btn); DependencyObject level2 = VisualTreeHelper.GetParent(level1); DependencyObject level3 = VisualTreeHelper.GetParent(level2); string typeStr = level3.GetType().ToString(); //System.Windows.Controls.ListBoxItem } 结果显示各条目最终以 ListBoxItem 包装。 
显示数据
XAML 代码: <ListBox x:Name="listBoxEmp"/> C# 代码: public class Emp { public int Id {get; set;} public string Name {get; set;} public int Age {get; set;} } List<Emp> emps = new List<Emp>() { new Emp(){Id=1, Name="Tim", Age=10}, new Emp(){Id=2, Name="Tom", Age=20}, new Emp(){Id=3, Name="Guo", Age=30} }; this.listBoxEmp.ItemsSource = emps; this.listBoxEmp.DisplayMemberPath = "Name"; this.listBoxEmp.SelectedValuePath = "Id"; ItemsSource 设置用于生成 ItemsControl 的内容的集合; DisplayMemberPath 设置从每条数据获取用于显示的值的路径;若需要更复杂的形式可使用 DataTemplate; SelectedValuePath 设置用于从 SelectedItem 获取 SelectedValue 的路径;
HeaderedItemsControl 族
包含
MenuItem、TreeViewItem、ToolBar
内容
属性
Items 或 ItemsSource
Header
Decorator 族
在 UI 上起装饰效果
包含
内容
属性
Child
内容
单一元素
TextBlock 和 TextBox
TextBlock
内容
属性
Inlines
例: <TextBlock TextWrapping="Wrap"> <TextBlock.Inlines> <Run>TextBlock支持的流显示样式示例:</Run> <LineBreak/> <Bold>粗体(Bold)</Bold> <LineBreak/> <Italic>斜体(Italic)</Italic> <LineBreak/> <Underline>下划线(Underline)</Underline> <LineBreak/> <Hyperlink NavigateUri="https://www.zhihu.com">超级链接</Hyperlink> <LineBreak/> <Span Foreground="Red" FontSize="18">Span设置字体、颜色等</Span> <LineBreak/> <InlineUIContainer> <StackPanel Background="AntiqueWhite"> <TextBlock>Inline UI 容器</TextBlock> <Button Content="按钮" Width="80"/> </StackPanel> </InlineUIContainer> </TextBlock.Inlines> </TextBlock>
Text
内容
带格式文本
TextBox
内容
属性
Text
内容
字符串
Shape 族元素
是视觉元素,不是控件
包含
主要属性
使用 Fill 属性设置填充
使用 Stroke 属性设置边线
Panel族
主要用于控制 UI 布局
包含
主要属性
内容
Children
主要控件
Grid
网格
主要属性
行、列集合属性
ColumnDefinitions
使用 ColumnDefinition 定义其中一列
RowDefinitions
使用 RowDefinition 定义其中一行
设置 ShowGridLines 属性可以显示网格线
Height(高)、Width(宽)的指定
绝对值
double 数值加单位后缀
单位
px
像素(默认)
in
英寸
cm
厘米
pt
点
比例值
double 数值加星号
比值:所有数值相加为分母,每个比例值分别为分子
控件默认 1*,简写为 *
自动值
字符串 Auto
具体数值由内部控件决定
指定内部元素所在的行、列
在内部元素上使用附加属性
起始位置
Grid.Row="行编号"
Grid.Column="列编号"
跨行、列
Grid.RowSpan="行数"
Grid.ColumnSpan="列数"
GridSplitter 控件
可以重新调整行或列的大小
例:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="100"/> <RowDefinition Height="20"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="20"/> <ColumnDefinition Width="100"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Rectangle Grid.Row="0" Grid.Column="0" Fill="Red"/> <Rectangle Grid.Row="0" Grid.Column="2" Fill="HotPink"/> <Rectangle Grid.Row="0" Grid.Column="3" Fill="Yellow"/> <Rectangle Grid.Row="2" Grid.Column="0" Fill="Green"/> <Rectangle Grid.Row="2" Grid.Column="2" Fill="Blue"/> <Rectangle Grid.Row="2" Grid.Column="3" Fill="BlueViolet"/> <!--ResizeDirection属性为Auto时,控件会将设置为 Stretch 的对齐方向的两侧的方向为调整方向--> <!--垂直分割,水平(列之间)调整。独占1列--> <GridSplitter Grid.Row="0" Grid.Column="1" Grid.RowSpan="3" HorizontalAlignment="Center" VerticalAlignment="Stretch" ResizeDirection="Columns" Width="5" ShowsPreview="False" Background="Moccasin"/> <!--垂直分割,水平(列之间)调整。与其它内容共享1列或1行时,可以调整控件定义顺序、Margin 或设置附加属性 ZIndex 等使其可见--> <GridSplitter Grid.Row="0" Grid.Column="2" Grid.RowSpan="3" HorizontalAlignment="Right" VerticalAlignment="Stretch" ResizeDirection="Columns" Width="5" ShowsPreview="False" Background="LightSteelBlue" Panel.ZIndex="1"/> <!--水平分割,垂直(行之间)调整。独占1行--> <GridSplitter Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="4" HorizontalAlignment="Stretch" VerticalAlignment="Center" ResizeDirection="Rows" Height="5" ShowsPreview="False" Background="Cyan"/> </Grid> 
StackPanel
栈式面板
主要属性
Orientation
子元素的堆叠方向
HorizontalAlignment
在父元素中的水平对齐方式
VerticalAlignment
在父元素中的垂直对齐方式
WrapPanel
自动折行面板
主要属性
Orientation
流延伸方向
HorizontalAlignment
在父元素中的水平对齐方式
VerticalAlignment
在父元素中的垂直对齐方式
DockPanel
泊靠式面板
主要属性
LastChildFill
值为 True 时(默认),最后一个元素会充满 DockPanel 内部剩余空间
指定内部元素泊靠的位置
在内部元素上使用附加属性
DockPanel.Dock="位置枚举"
Canvas
画布
指定内部元素所在的坐标
在内部元素上使用附加属性
Canvas.Left="距离"
设置元素的左边缘与其父 Canvas 的左边缘之间的距离
Canvas.Top="距离"
设置元素的上边缘与其父 Canvas 的上边缘之间的距离
Canvas.Right="距离"
设置元素的右边缘与其父 Canvas 的右边缘之间的距离
Canvas.Bottom="距离"
设置元素的下边缘与其父 Canvas 的下边缘之间的距离
内容缩放
可以将 Canvas 放在 Viewbox 控件中以便于缩放
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Viewbox> <Canvas Width="300" Height="200"> <Rectangle Width="200" Height="100" Fill="DarkMagenta" Canvas.Left="10" Canvas.Top="10"/> </Canvas> </Viewbox> </Window>
关于 UI 元素的跨线程访问
说明
出于线程安全的考虑,UI 元素只允许被创建它的线程访问
通常,WPF 应用程序从两个线程开始:一个用于处理呈现,一个用于管理 UI。 呈现线程有效地隐藏在后台运行,而 UI 线程则需要接收输入、处理事件、绘制屏幕以及运行应用程序代码。
关于 DispatcherObject 类
命名空间:System.Windows.Threading
是 DependencyObject 类的基类
说明
只有创建与此 DispatcherObject 关联的 Dispatcher 对象的线程,才可以直接访问此 DispatcherObject
若要从其它线程访问,则只能通过与此 DispatcherObject 关联的 Dispatcher 对象的 Invoke(~) 或 BeginInvoke(~) 系列方法访问
属性
Dispatcher
与此 DispatcherObject 关联的 Dispatcher(调度器)
类型:Dispatcher
命名空间:System.Windows.Threading
说明
Dispatcher 可为特定线程维护其按优先顺序排列的工作项队列
当在线程中创建1个 Dispatcher 时,该 Dispatcher 对象将成为可与该线程关联的唯一 Dispatcher。即使 Dispatcher 已经关闭。
主要方法
bool CheckAccess()
检查调用线程是否可以访问此 DispatcherObject
该方法最终将检查调用线程是否为 与此 DispatcherObject 关联的 Dispatcher 创建时所在的线程
void VerifyAccess()
验证调用线程是否可以访问此 DispatcherObject
该方法最终将调用与此 DispatcherObject 关联的 Dispatcher 的 CheckAccess() 方法来检查调用线程是否可以访问此 DispatcherObject。若不可以,则引发异常
例:
C# 代码
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Threading; using System.Threading; using System.Threading.Tasks; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { bool isSameThread = buttonByUI.Dispatcher.Thread == Thread.CurrentThread; MessageBox.Show($"在UI 线程访问 UI 线程创建的元素:{Environment.NewLine}buttonByUI.Dispatcher.Thread == Thread.CurrentThread:{Environment.NewLine}{isSameThread}"); buttonByUI.Content = "在UI 线程访问 UI 线程创建的元素"; Button buttonByOther = null; Thread otherThread = new Thread(() => { buttonByOther = new Button(); isSameThread = buttonByOther.Dispatcher.Thread == buttonByUI.Dispatcher.Thread; MessageBox.Show($"在其它线程创建按钮:{Environment.NewLine}buttonByOther.Dispatcher.Thread == buttonByUI.Dispatcher.Thread:{Environment.NewLine}{isSameThread}"); isSameThread = buttonByOther.Dispatcher.Thread == Thread.CurrentThread; MessageBox.Show($"在其它线程创建按钮:{Environment.NewLine}buttonByOther.Dispatcher.Thread == Thread.CurrentThread:{Environment.NewLine}{isSameThread}"); buttonByOther.Content = "其它线程创建的按钮"; }); otherThread.SetApartmentState(ApartmentState.STA); //将线程单元状态设置为单线程单元,以在线程中创建 UI 元素 otherThread.Start(); otherThread.Join(); try { isSameThread = buttonByOther.Dispatcher.Thread == Thread.CurrentThread; MessageBox.Show($"在 UI 线程访问其它线程创建的元素:{Environment.NewLine}buttonByOther.Dispatcher.Thread == Thread.CurrentThread:{Environment.NewLine}{isSameThread}"); buttonByOther.Content = "在 UI 线程访问其它线程创建的元素"; } catch (Exception ex) { MessageBox.Show($"在 UI 线程访问其它线程创建的元素:{Environment.NewLine}{ex.Message}", "异常"); } Task.Run(() => { try { isSameThread = buttonByUI.Dispatcher.Thread == Thread.CurrentThread; MessageBox.Show($"在其它线程访问 UI 线程创建的元素:{Environment.NewLine}buttonByUI.Dispatcher.Thread == Thread.CurrentThread:{Environment.NewLine}{isSameThread}"); buttonByUI.Content = "在其它线程访问 UI 线程创建的元素"; } catch (Exception ex) { MessageBox.Show($"在其它线程访问 UI 线程创建的元素:{Environment.NewLine}{ex.Message}", "异常"); } buttonByUI.Dispatcher.Invoke(new Action(() => //注意:根据上下文,此处 Task 不能 Wait(),否则会导致死锁 { isSameThread = buttonByUI.Dispatcher.Thread == Thread.CurrentThread; MessageBox.Show($"在其它线程中,通过 UI 线程创建的元素的 Dispatcher 同步访问 UI 线程创建的元素:{Environment.NewLine}buttonByUI.Dispatcher.Thread == Thread.CurrentThread:{Environment.NewLine}{isSameThread}"); buttonByUI.Content = "在其它线程中,通过 UI 线程创建的元素的 Dispatcher 同步访问 UI 线程创建的元素"; }), DispatcherPriority.Normal); buttonByUI.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { isSameThread = buttonByUI.Dispatcher.Thread == Thread.CurrentThread; MessageBox.Show($"在其它线程中,通过 UI 线程创建的元素的 Dispatcher 异步访问 UI 线程创建的元素:{Environment.NewLine}buttonByUI.Dispatcher.Thread == Thread.CurrentThread:{Environment.NewLine}{isSameThread}"); buttonByUI.Content = "在其它线程中,通过 UI 线程创建的元素的 Dispatcher 异步访问 UI 线程创建的元素"; })); }); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel> <Button x:Name="buttonByUI" Content="UI 线程创建的按钮" Click="Button_Click"/> </StackPanel> </Window>
WPF 与 Windows Forms 互操作
WindowsFormsHost 类
可用于在 WPF 中承载 Windows 窗体控件
程序集:WindowsFormsIntegration.dll
命名空间:System.Windows.Forms.Integration
主要属性
Child
承载的 Windows 窗体控件
类型:System.Windows.Forms.Control
ElementHost 类
可用于在 Windows 窗体中承载 WPF 元素
程序集:WindowsFormsIntegration.dll
命名空间:System.Windows.Forms.Integration
主要属性
Child
承载的 WPF 元素
类型:System.Windows.UIElement
4. Binding
基础
概念
一般情况下,Binding 源(Source)是逻辑层对象,Binding 目标(Target)是 UI 层对象。
Binding 作为两层之间的桥梁,数据通过该“桥梁”在两层间流动。对于该过程,我们可以控制
通行方向
通行时机
设置"关卡"转换数据类型或校验数据
Binding 模型图

Binding 使用 例:
(1)创建属性更改后能通知 Binding 的数据源类
实现 INotifyPropertyChanged 的类
方式一
using System.ComponentModel; public class Student : INotifyPropertyChanged //当为 Binding 设置了数据源后, Binding 就会自动侦听来自这个接口的 PropertyChanged 事件。 { private string name; public event PropertyChangedEventHandler PropertyChanged; public string Name { get { return name; } set { name = value; if (this.PropertyChanged != null) { //当 Name 属性的值更改时,激发 PropertyChanged 事件。Binding 接收到这个事件后发现是名为 Name 的属性值更改,就会通知 Binding 目标端的 UI 元素显示新的值。 this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs("Name")); } } } }
方式二(推荐)
using System.ComponentModel; using System.Runtime.CompilerServices; public class ObservableObject : INotifyPropertyChanged //创建具有通知能力的对象基类 { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } public class Student : ObservableObject //继承 { private string name; public string Name { get { return name; } set { name = value; OnPropertyChanged(); //例:调用继承的 OnPropertyChanged 方法,激发 PropertyChanged 事件 //OnPropertyChanged(new PropertyChangedEventArgs(nameof(Name))); //或:例:调用重写的 OnPropertyChanged 方法,激发 PropertyChanged 事件 } } protected override void OnPropertyChanged(PropertyChangedEventArgs e) { base.OnPropertyChanged(e); } }
(2)关联/绑定 数据源与目标
XAML 代码部分
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel> <Button x:Name="button" Content="按钮" Click="Button_Click"/> </StackPanel> </Window>
C# 代码部分
using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.ComponentModel; namespace WpfApp1 { public partial class MainWindow : Window { private Student stu; //数据源对象 public MainWindow() { InitializeComponent(); #region 详细的绑定操作 //准备数据源 stu = new Student(); //(1)配置数据源绑定信息 Binding binding = new Binding(); binding.Source = stu; //设置数据源 binding.Path = new PropertyPath("Name"); //设置要建立绑定的数据源属性访问路径 binding.Mode = BindingMode.TwoWay; //设置数据的流向 binding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; //设置更新的时机 //(2)为目标设置绑定 BindingOperations.SetBinding(this.button, //设置目标 Button.ContentProperty, //设置要建立绑定的目标属性 binding); //设置用于绑定的绑定信息 #endregion #region 简化的绑定操作 this.button.SetBinding(Button.ContentProperty, new Binding("Name") { Source = stu = new Student(), Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); #endregion } private void Button_Click(object sender, RoutedEventArgs e) { stu.Name += "Name"; } } }
Binding
配置绑定
Binding 类
命名空间: System.Windows.Data
主要属性
数据源的设置
隐式获取
Binding 中没有显式指定数据源时,Binding 会尝试从元素的 DataContext 属性获取绑定的数据源。
元素的 DataContext 属性
含义
元素参与数据绑定时的数据上下文
用法
例:
获取自身的 DataContext
C# 代码: stu = new Student(); this.button.DataContext = stu; //设置元素的数据上下文 XAML 代码: <Button x:Name="button" Content="{Binding Path=Name,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Click="Button_Click"/>
从父级继承 DataContext
C# 代码: public class Car : ObservableObject { private string brand; public string Brand { get { return brand; } set { brand = value; OnPropertyChanged(); } } } XAML 代码: <Window.Resources> <local:Car x:Key="myCar"> <local:Car.Brand>Super</local:Car.Brand> </local:Car> </Window.Resources> <StackPanel> <StackPanel.DataContext> <Binding Source="{StaticResource myCar}"/> </StackPanel.DataContext> <!--TextBlock 从 StackPanel 继承 DataContext--> <TextBlock Text="{Binding Brand}"/> </StackPanel> 说明: DataContext 是一个依赖属性,控件依赖属性未被赋值时,控件会把自己容器的属性值“借过来”。
显式指定
Source
含义
绑定的数据源
用法
例:
绑定资源:
C# 代码: public class Person : ObservableObject { private string name; public string Name { get { return name; } set { name = value; OnPropertyChanged(); } } public Person() {} public Person(string name) { this.name = name; } } XAML 代码: <Window.Resources> <local:Person x:Key="myPerson1" Name="Joe"/> <!--ObjectDataProvider 类:包装和创建可以用作绑定的数据源对象--> <ObjectDataProvider x:Key="myPerson2" ObjectType="{x:Type local:Person}"> <ObjectDataProvider.ConstructorParameters> <sys:String>Jof</sys:String> </ObjectDataProvider.ConstructorParameters> </ObjectDataProvider> </Window.Resources> <TextBox Text="{Binding Source={StaticResource myPerson1},Path=Name}"/> <TextBox Text="{Binding Source={StaticResource myPerson2},Path=Name}"/>
ElementName
含义
要用作数据源的元素的名称
作用域需在同一XAML文件内。
用法
例:
绑定另一个元素:
<Button x:Name="button" Content="按钮"/> <TextBox Text="{Binding ElementName=button,Path=Content,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/> 或 <Button Name="button" Content="按钮"/> <TextBox Text="{Binding ElementName=button,Path=Content,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
RelativeSource
含义
相对于绑定目标位置的数据源
通过 RelativeSource 类的几个非静态或静态属性,我们可以控制它查找数据源的方式。
属性的类型
RelativeSource
属性
Mode
描述数据源相对于绑定目标位置的查找模式
值
FindAncestor
引用绑定目标元素父链中的上级
PreviousData
引用数据项列表中当前绑定数据项之前的数据项
Self
引用绑定目标元素自身
TemplatedParent
引用应用了模板的元素
这类似于设置 TemplateBindingExtension,并仅当 Binding 在模板中时适用。 对于模板方案来说,TemplateBinding 是绑定的优化形式,它与使用 {Binding RelativeSource={RelativeSource TemplatedParent}} 构造的 Binding 相似。不过,TemplateBinding 仅支持单向的数据绑定,即数据从应用了模板的元素流向模板。
AncestorType
Mode 为 FindAncestor 时,表示要查找的上级节点的类型
AncestorLevel
Mode 为 FindAncestor 时,表示要查找的上级节点与绑定目标元素相距的层数
例:
C# 代码
using System.Windows; using System.ComponentModel; using System.Runtime.CompilerServices; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } //================ public class Student : ObservableObject { private string name; public string Name { get { return name; } set { name = value; OnPropertyChanged(); } } } public class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <x:Array x:Key="stuArray" Type="local:Student"> <local:Student Name="名称1"/> <local:Student Name="名称2"/> <local:Student Name="名称3"/> <local:Student Name="名称4"/> <local:Student Name="名称5"/> </x:Array> </Window.Resources> <StackPanel> <Grid x:Name="grid1" Background="Red" Margin="10" > <DockPanel x:Name="dockPanel1" Background="Orange" Margin="10"> <Grid x:Name="grid2" Background="Yellow" Margin="10"> <DockPanel x:Name="dockPanel2" Background="LawnGreen" Margin="10"> <!--引用绑定目标元素父链中的上级--> <TextBox x:Name="textBox1" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type Grid},AncestorLevel=1},Path=Name}" Margin="10"/> <TextBox x:Name="textBox2" Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType={x:Type DockPanel},AncestorLevel=2},Path=Name}" Margin="10"/> <ListBox x:Name="listBox1" ItemsSource="{Binding Source={StaticResource stuArray}}" Margin="10"> <ListBox.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=Name}"/> <!--引用数据项列表中当前绑定数据项之前的数据项--> <TextBlock Text="{Binding RelativeSource={RelativeSource Mode=PreviousData}, Path=Name}" Margin="10 0 0 0"/> </StackPanel> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <!--引用绑定目标元素自身--> <TextBox x:Name="textBox3" Text="{Binding RelativeSource={RelativeSource Mode=Self},Path=Name}" Margin="10"/> <TextBox x:Name="textBox4" Text="测试1" Uid="测试2" Margin="10"> <TextBox.Template> <ControlTemplate TargetType="TextBox"> <StackPanel> <!--引用应用了模板的元素--> <TextBox Text="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Text}"/> <!--TemplateBindingExtension 对比--> <TextBox Text="{TemplateBinding Property=Uid}"/> </StackPanel> </ControlTemplate> </TextBox.Template> </TextBox> </DockPanel> </Grid> </DockPanel> </Grid> </StackPanel> </Window>
Path
含义
要建立绑定的(CLR对象)数据源属性访问路径
用法
绑定源本身
指定为 .
C# 代码: private int a = 987; txt1.DataContext = a; txt2.DataContext = a; txt3.DataContext = a; txt4.DataContext = a; txt5.DataContext = a; txt3.SetBinding(TextBlock.TextProperty, new Binding("."));//绑定源本身时,指定 Path 为 . txt4.SetBinding(TextBlock.TextProperty, new Binding("")); //绑定源本身时,也可以指定 Path 为空字符串 txt5.SetBinding(TextBlock.TextProperty, new Binding()); //绑定源本身时,也可以不指定 Path XAML 代码: <!--绑定源本身时,指定 Path 为 . --> <TextBlock x:Name="txt1" Text="{Binding Path=.}"/> <!--绑定源本身时,也可以不指定 Path--> <TextBlock x:Name="txt2" Text="{Binding}"/> <TextBlock x:Name="txt3"/> <TextBlock x:Name="txt4"/> <TextBlock x:Name="txt5"/>
绑定属性
指定属性名
<StackPanel> <TextBox x:Name="txt" Text="文本"/> <TextBlock Text="{Binding ElementName=txt,Path=Text}"/> </StackPanel>
访问下一级属性时使用 . 操作符
<StackPanel> <TextBox x:Name="txt" Text="文本"/> <TextBlock Text="{Binding ElementName=txt,Path=Text.Length}"/> </StackPanel>
绑定索引器
因为“索引器”是带参属性,也属于属性,可以按属性的方式绑定。
按索引器语法 [] 指定
例:
C# 代码
using System.Windows; using System.Windows.Data; using System.ComponentModel; using System.Runtime.CompilerServices; using System.Collections.ObjectModel; using System.Linq; namespace WpfApp1 { public partial class MainWindow : Window { private OperateData operateData; public MainWindow() { InitializeComponent(); operateData = new OperateData() { Order = new Order() }; this.DataContext = operateData; string mall = "abcdefg"; for (int i = 0; i < mall.Length; i++) { operateData.Order[mall[i].ToString(), i] = $"订单{mall[i]}-{i}"; } } private void Button_Click(object sender, RoutedEventArgs e) { try { string[] datas = operateData.AccessData.Split("+".ToCharArray()); operateData.Order[datas[0], int.Parse(datas[1])] = datas[2]; } catch (System.Exception) { } } } //================ public class OperateData : ObservableObject { private Order order; public Order Order { get { return order; } set { order = value; OnPropertyChanged(); } } private string accessData; public string AccessData { get { return accessData; } set { accessData = value; OnPropertyChanged(); } } } public class Order : ObservableObject { private ObservableCollection<(string key, string value)?> orders = new ObservableCollection<(string key, string value)?>(); public ObservableCollection<(string key, string value)?> Items { get { return orders; } } public string this[string mallAndNumber] { get { (string key, string value)? order = orders.Where(x => x.Value.key == mallAndNumber).FirstOrDefault(); return order?.value; } } public string this[string mall, int number] { get { (string key, string value)? order = orders.Where(x => x.Value.key == $"{mall}+{number}").FirstOrDefault(); return order?.value; } set { (string key, string value)? order = orders.Where(x => x.Value.key == $"{mall}+{number}").FirstOrDefault(); if (order != null) { int index = orders.IndexOf(order); orders.RemoveAt(index); orders.Insert(index, ($"{mall}+{number}", value)); } else { orders.Add(($"{mall}+{number}", value)); } OnPropertyChanged(Binding.IndexerName); //索引器属性名称为 Item[] } } } public class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib" d:DataContext="{d:DesignInstance local:OperateData}"> <!--d:DataContext="{d:DesignInstance ~}" 为设计时数据上下文指定设计时实例--> <StackPanel> <StackPanel Orientation="Horizontal"> <Label Content="访问关键字:"/> <TextBox Text="{Binding AccessData,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" Width="100"/> <Button Content="设置" Click="Button_Click" Width="50"/> </StackPanel> <WrapPanel Orientation="Horizontal" Width="700"> <Label Content="Order.Items.[5]:"/> <!--指定索引器--> <TextBlock Text="{Binding Order.Items.[5]}"/> <Label Content="Order.Items[3]:" Margin="20 0 0 0"/> <!--指定索引器( . 可以省略)--> <TextBlock Text="{Binding Order.Items[3]}"/> <Label Content="Order[e+4]:" Margin="20 0 0 0"/> <TextBlock Text="{Binding Order[e+4]}"/> <Label Content="Order.[g,6]:" Margin="20 0 0 0"/> <TextBlock> <TextBlock.Text> <!--指定索引器(带多个参数的索引器,各参数使用 , 分隔)--> <Binding Path="Order.[g,6]"/> </TextBlock.Text> </TextBlock> <Label Content="Order[f,5]:" Margin="20 0 0 0"/> <TextBlock> <TextBlock.Text> <Binding Path="Order[f,5]"/> </TextBlock.Text> </TextBlock> <!--指定索引器(参数前可以用 () 括起类型,对参数类型进行指定)--> <Label Content="Order[(sys:String)d,(sys:Int32)3]:" Margin="20 0 0 0"/> <TextBlock> <TextBlock.Text> <Binding Path="Order[(sys:String)d,(sys:Int32)3]"/> </TextBlock.Text> </TextBlock> </WrapPanel> <ListBox ItemsSource="{Binding Order.Items}" Height="300"/> </StackPanel> </Window>
绑定集合(视图)的当前项
指定 /
访问当前项下一级属性时无需使用 . 操作符
例:
C# 代码: ObservableCollection<string> stringList = new ObservableCollection<string>() { "Tim", "Jseph", "Charles" }; this.DataContext = stringList; XAML 代码: <TextBox Text="{Binding /}"/> <TextBox Text="{Binding /Length,Mode=OneWay}"/> <!--IsSynchronizedWithCurrentItem 为 true 时,Selector 将使 SelectedItem 与 CurrentItem 保持同步。--> <ListBox ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True"/> 说明: ListBox 继承自 Selector,Selector 有 SelectedItem(object 类型) 属性。 Selector 继承自 ItemsControl,ItemsControl 有 Items(ItemCollection 类型)(get) 与 ItemsSource(IEnumerable 类型)(get;set;) 属性,ItemsSource 操作 Items 的 _itemsSource 字段并设置 DefaultCollectionView。 ItemCollection 有父类 CollectionView,ItemCollection 有 CurrentItem(object 类型)属性。
关于集合视图
WPF 总是绑定到视图,而非集合
所有集合都具有一个默认 CollectionView。 WPF 总是绑定到视图,而非集合。 如果直接绑定到集合,则 WPF 实际会绑定到该集合的默认视图。 此默认视图由到集合的所有绑定共享,这使到集合的所有直接绑定都可共享一个默认视图的排序、筛选、分组和当前项特征。
例:
获取集合的默认视图
可通过 System.Windows.Data 命名空间里的 CollectionViewSource 类的 GetDefaultView 方法获取集合的默认视图。 方法原型: public static ICollectionView GetDefaultView(object source); 例: ICollectionView collectionView = CollectionViewSource.GetDefaultView(stringList);
在 XAML 中创建集合视图
xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase" xmlns:dat="http://schemas.microsoft.com/netfx/2009/xaml/presentation" <Window.Resources> <x:Array x:Key="stringArray" Type="sys:String"> <sys:String>AAA11</sys:String> <sys:String>AAA2</sys:String> <sys:String>BBB333</sys:String> <sys:String>BBB4</sys:String> <sys:String>CCC55</sys:String>> <sys:String>CCC666</sys:String> </x:Array> <!--创建集合视图--> <CollectionViewSource x:Key="cvs" Source="{StaticResource stringArray}"> <CollectionViewSource.SortDescriptions> <scm:SortDescription PropertyName="." Direction="Descending"/> </CollectionViewSource.SortDescriptions> <CollectionViewSource.GroupDescriptions> <dat:PropertyGroupDescription PropertyName="Length"/> </CollectionViewSource.GroupDescriptions> </CollectionViewSource> </Window.Resources> <ListBox ItemsSource="{Binding Source={StaticResource cvs}}"/>
绑定附加属性
指定的属性使用 () 括起
<Grid> <TextBox x:Name="txt" Text="文本" Grid.Row="123"/> <TextBlock Text="{Binding ElementName=txt,Path=(Grid.Row)}"/> </Grid>
XPath
含义
要建立绑定的(XML)数据源属性访问路径
XPath 查询表达式
用法
WPF 中的 XPath 属性使用 SelectNodes(~) 方法处理,所以 XPath 按 XPath 表达式语法赋值
类:XmlNode 方法原型: public XmlNodeList SelectNodes(string xpath);
主要 XPath 表达式 语法
语法示例 XML: <?xml version="1.0" encoding="ISO-8859-1"?> <bookstore> <book> <title lang="eng">Harry Potter</title> <price>29.99</price> </book> <book> <title lang="eng">Learning XML</title> <price>39.95</price> </book> </bookstore>
选取节点
XPath 使用路径表达式在 XML 文档中选取节点。节点是通过沿着路径或者 step 来选取的。 位置路径表达式: 绝对位置路径: /step/step/... 相对位置路径: step/step/... 步(step)包括: 轴(axis) 定义所选节点与当前节点之间的树关系 节点测试(node-test) 识别某个轴内部的节点 零个或者更多谓语(predicate) 更深入地提炼所选的节点集 步的语法: 轴名称::节点测试[谓词]
nodename
选取此节点的所有子节点
例: bookstore 选取 bookstore 元素的所有子节点。
/
从根节点选取
例: /bookstore 选取根元素 bookstore。(假如路径起始于正斜杠( / ),则此路径始终代表到某元素的绝对路径!) bookstore/book 选取属于 bookstore 的子元素的所有 book 元素。
//
从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置
例: //book 选取所有 book 子元素,而不管它们在文档中的位置。 bookstore//book 选择属于 bookstore 元素的后代的所有 book 元素,而不管它们位于 bookstore 之下的什么位置。
.
选取当前节点
..
选取当前节点的父节点
@
选取属性
例: //@lang (结合 // )选取名为 lang 的所有属性。
选取未知节点
XPath 通配符可用来选取未知的 XML 元素。
*
匹配任何元素节点
例: /bookstore/* 选取 bookstore 元素的所有子元素。 //* 选取文档中的所有元素。
@*
匹配任何属性节点
例: //title[@*] 选取所有带有属性的 title 元素。
node()
匹配任何类型的节点
选取若干路径
通过在路径表达式中使用“|”运算符,您可以选取若干个路径。
例:
选取 book 元素的所有 title 和 price 元素。
//book/title | //book/price
选取文档中的所有 title 和 price 元素。
//title | //price
选取属于 bookstore 元素的 book 元素的所有 title 元素,以及文档中所有的 price 元素。
/bookstore/book/title | //price
谓词
谓词用来查找某个特定的节点或者包含某个指定的值的节点。 谓词被嵌在方括号中。
例:
选取属于 bookstore 子元素的第一个 book 元素。
/bookstore/book[1]
选取属于 bookstore 子元素的最后一个 book 元素。
/bookstore/book[last()]
选取属于 bookstore 子元素的倒数第二个 book 元素。
/bookstore/book[last()-1]
选取最前面的两个属于 bookstore 元素的子元素的 book 元素。
/bookstore/book[position()<3]
选取所有拥有名为 lang 的属性的 title 元素。
//title[@lang]
选取所有 title 元素,且这些元素拥有值为 eng 的 lang 属性。
//title[@lang='eng']
选取 bookstore 元素的所有 book 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]
选取 bookstore 元素中的 book 元素的所有 title 元素,且其中的 price 元素的值须大于 35.00。
/bookstore/book[price>35.00]/title
例:
ListView 展示列表数据
可以借助 GridViewColumn 类的 DisplayMemberBinding 属性
<Window.Resources> <XmlDataProvider x:Key="SupermarketData"> <x:XData> <bookstore xmlns=""> <book> <title lang="eng">Harry Potter</title> <price>29.99</price> </book> <book> <title lang="eng">Learning XML</title> <price>39.95</price> </book> </bookstore> </x:XData> </XmlDataProvider> </Window.Resources> <StackPanel DataContext="{Binding Source={StaticResource SupermarketData}}"> <TextBlock Text="{Binding XPath=.}"/> <TextBlock Text="{Binding XPath=bookstore/book/title}"/> <TextBlock Text="{Binding XPath=bookstore/book/title/@lang}"/> <ListView ItemsSource="{Binding XPath=bookstore/book}"> <ListView.View> <GridView> <GridViewColumn DisplayMemberBinding="{Binding XPath=title}"/> <GridViewColumn DisplayMemberBinding="{Binding XPath=title/@lang}"/> <GridViewColumn DisplayMemberBinding="{Binding XPath=price}"/> </GridView> </ListView.View> </ListView> </StackPanel>
TreeView 展示分层数据
可以借助 TreeView 类的 ItemTemplate 属性,为该属性设置一个 HierarchicalDataTemplate
<Window.Resources> <XmlDataProvider x:Key="FileSystemData" XPath="FileSystem/Folder"> <x:XData> <FileSystem xmlns=""> <Folder Name="Books"> <Folder Name="Programming"> <Folder Name="Windows"> <Folder Name="WPF"/> <Folder Name="Winform"/> <Folder Name="ASP.NET"/> </Folder> </Folder> </Folder> <Folder Name="Tools"> <Folder Name="Development"/> <Folder Name="Designment"/> <Folder Name="Players"/> </Folder> </FileSystem> </x:XData> </XmlDataProvider> </Window.Resources> <StackPanel> <TreeView ItemsSource="{Binding Source={StaticResource ResourceKey=FileSystemData}}"> <TreeView.ItemTemplate> <!--HierarchicalDataTemplate 是分层数据模板类,会自动递归应用,ItemsSource 属性指定下一层的数据。该模板类主要作用于 HeaderedItemsControl 类的 HeaderTemplate 属性。--> <HierarchicalDataTemplate ItemsSource="{Binding XPath=Folder}"> <TextBlock Text="{Binding XPath=@Name}"/> </HierarchicalDataTemplate> </TreeView.ItemTemplate> </TreeView> </StackPanel>
Mode
含义
绑定的数据流向
UpdateSourceTrigger
含义
绑定的数据更新时机
Converter
含义
绑定源值与目标值之间的转换器
关于转换器
内部支持的转换
例:
<sys:Double x:Key="dbl">3.1415926</sys:Double> <TextBlock Text="{Binding Source={StaticResource dbl}}"/> Double 类型资源值最终被转换为 String 类型的值,并赋值给 TextBlock 的实例的 Text 属性。
自定义转换器实现
实现 IValueConverter
例:
转换器实现
using System.Globalization; using System.Windows.Data; //可以设置转换器支持转换的源类型与目标类型特性 [ValueConversionAttribute(typeof(string), typeof(string))] public class StateConverter : IValueConverter { //绑定源值转换为目标值 public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (targetType != typeof(string)) { return value; } switch (value.ToString()) { case "0": return "关"; case "1": return "开"; default: return "关"; } } //目标值转换为绑定源值 public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (targetType != typeof(string)) { return value; } switch (value.ToString()) { case "关": return "0"; case "开": return "1"; default: return "0"; } } }
XAML 代码
<Window.Resources> <!--定义一个转换器资源--> <local:StateConverter x:Key="stateConverter"/> </Window.Resources> <TextBox x:Name="textBox"/> <!--绑定源值与目标值之间的转换--> <TextBox Text="{Binding ElementName=textBox,Path=Text,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,Converter={StaticResource stateConverter}}"/>
ConverterParameter
含义
传递给 Converter 的参数
其值将通过 parameter 参数传入 Convert(~) 和 ConvertBack(~) 方法
ValidationRules
含义
从绑定目标值到源值的校验规则的集合
有效性校验规则类实现
继承 ValidationRule
属性
ValidatesOnTargetUpdated
是否在绑定目标被源更新时也进行校验
若校验规则的 ValidatesOnTargetUpdated 属性设置为 True,则绑定目标被源更新时也会进行校验。否则,只有绑定目标更新到源时会进行校验。
例:
校验规则实现
using System.Windows.Controls; using System.Globalization; //继承 ValidationRule 类,重写 Validate 方法 public class RangeValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (double.TryParse(value.ToString(), out double val)) { if (val >= 0 && val <= 100) { return ValidationResult.ValidResult; //有效时返回有效 ValidationResult 实例(IsValid 属性设置为 true,ErrorContent 属性不用设置) } } return new ValidationResult(false, "校验失败"); //无效时返回无效 ValidationResult 实例(IsValid 属性设置为 false,ErrorContent 设置为所需错误内容) } }
XAML 代码
<StackPanel> <TextBox> <TextBox.Text> <Binding ElementName="slider1" Path="Value" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged"> <Binding.ValidationRules> <local:RangeValidationRule/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <Slider x:Name="slider1" Minimum="-10" Maximum="110"/> </StackPanel>
更多用法
例:
C# 代码
using System; using System.Globalization; using System.Windows; using System.Windows.Controls; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void TextBox1_Error(object sender, ValidationErrorEventArgs e) { var errors = Validation.GetErrors(textBox1); //通过 Validation.GetErrors(~) 静态方法获取指定元素的 Validation.Errors 附加属性的值 if (errors.Count == 0) { return; } //Validation.Errors 附加属性值(集合类型)的元素类型为 ValidationError object errorContent = errors[0].ErrorContent; //属性 ErrorContent,表示此 ValidationError 的上下文,例如描述错误的字符串。 Exception exception = errors[0].Exception; //属性 Exception,表示导致此 ValidationError 的 Exception 对象 ValidationRule ruleInError = errors[0].RuleInError; //属性 RuleInError,表示导致此 ValidationError 的 ValidationRule 对象 } } //================ public class RangeValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (double.TryParse(value.ToString(), out double val)) { if (val >= 0 && val <= 100) { return ValidationResult.ValidResult; } } return new ValidationResult(false, "校验失败"); } } public class RangeValidationRule2 : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { if (double.TryParse(value.ToString(), out double val)) { if (val >= 10 && val <= 90) { return ValidationResult.ValidResult; } } return new ValidationResult(false, "校验失败2"); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <Style x:Key="textBoxInError" TargetType="TextBox"> <Style.Triggers> <Trigger Property="Validation.HasError" Value="True"> <!--(控件)元素的 Validation.HasError 附加属性值为 True 时,表示有错误。这时可以展示(控件)元素附加属性 Validation.Errors 第1个元素(或其它后续元素(若有)) 的 ErrorContent--> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Mode=Self},Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> </Style> <ControlTemplate x:Key="validationTemplate"> <DockPanel> <!--AdornedElementPlaceholder 占位,代表被修饰的元素--> <AdornedElementPlaceholder/> <TextBlock Foreground="Red" Text="!"/> </DockPanel> </ControlTemplate> </Window.Resources> <StackPanel> <TextBox x:Name="textBox1" Width="100" Style="{StaticResource textBoxInError}" Validation.ErrorTemplate="{StaticResource validationTemplate}" Validation.Error="TextBox1_Error"> <!--(控件)元素的 Validation.ErrorTemplate 附加属性用于设置在装饰器层生成校验错误反馈的 ControlTemplate--> <TextBox.Text> <Binding ElementName="slider1" Path="Value" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True" ValidatesOnExceptions="True"> <Binding.ValidationRules> <local:RangeValidationRule ValidatesOnTargetUpdated="True"/> <local:RangeValidationRule2 ValidatesOnTargetUpdated="False"/> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> <Slider x:Name="slider1" Minimum="-10" Maximum="110"/> </StackPanel> </Window>
NotifyOnValidationError
含义
是否在出现校验错误时激发元素的 Validation.Error 附加事件
ValidatesOnExceptions
含义
是否默认添加 ExceptionValidationRule 校验规则
BindsDirectlyToSource
含义
是否计算相对于 DataSourceProvider 对象的 Path
例:
C# 代码: public class Caculate { public string Add(string arg1, string arg2) { double x = 0; double y = 0; double z = 0; if (double.TryParse(arg1, out x) && double.TryParse(arg2, out y)) { z = x + y; return z.ToString(); } return "输入错误"; } } XAML 代码: <Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <ObjectDataProvider x:Key="AddObject" ObjectType="{x:Type local:Caculate}"> <ObjectDataProvider.MethodName>Add</ObjectDataProvider.MethodName> <ObjectDataProvider.MethodParameters> <sys:String>0</sys:String> <sys:String>0</sys:String> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </Window.Resources> <StackPanel> <!--Binding 的 BindsDirectlyToSource 属性设置为 True 时,绑定目标的新值将写入其直接 Source(ObjectDataProvider 对象),而不是被 ObjectDataProvider 包装着的对象(即例中 Caculate 对象)。该例中,结果将随参数的改变而改变。--> <!--DataSourceProvider 是 ObjectDataProvider 与 XmlDataProvider 的基类。--> <TextBox Text="{Binding Source={StaticResource AddObject},Path=MethodParameters[0],BindsDirectlyToSource=True,UpdateSourceTrigger=PropertyChanged}"/> <TextBox Text="{Binding Source={StaticResource AddObject},Path=MethodParameters[1],BindsDirectlyToSource=True,UpdateSourceTrigger=PropertyChanged}"/> <!--ObjectDataProvider 用作 Binding 的 Source 时,对象本身就代表数据,所以下面的绑定没有设置 Path=Data--> <TextBlock Text="{Binding Source={StaticResource AddObject}}"/> </StackPanel> </Window>
StringFormat
含义
用于当绑定目标类型为字符串时,设置其格式化字符串
具体格式化字符串内容可参考 String Format(String format, Object arg0) 方法。
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <sys:Double x:Key="dbl1">123456.789</sys:Double> </Window.Resources> <StackPanel> <!--“形象描述字符”格式化字符串:--> <TextBlock Text="{Binding Source={StaticResource dbl1},Path=.,StringFormat=#.0}" /> <!--复合“格式字符串”格式化字符串:--> <TextBlock Text="{Binding Source={StaticResource dbl1},Path=.,StringFormat={}{0:F1}}" /> <TextBlock Text="{Binding Source={StaticResource dbl1},Path=.,StringFormat=小数{0:F1}}" /> </StackPanel> </Window>
MultiBinding 类
命名空间: System.Windows.Data
多路 Binding
主要属性
Bindings
含义
Binding 对象的集合
它们汇集起来的数据将共同决定传往 MultiBinding 目标的数据
类型
Collection<BindingBase>
MultiBinding 当前仅支持 Binding 类型的对象,而不支持 MultiBinding 或 PriorityBinding 类型的对象。
Mode
含义
绑定的数据流向
UpdateSourceTrigger
含义
绑定的数据更新时机
Converter
含义
绑定源值与目标值之间的转换器
转换器实现
实现 IMultiValueConverter
ConverterParameter
含义
传递给 Converter 的参数
值将通过 parameter 参数传入 Convert(~) 和 ConvertBack(~) 方法
StringFormat
含义
用于当绑定目标类型为字符串时,设置其格式化字符串
具体格式化字符串内容可参考 String Format(String format, params Object[] args) 方法。
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <sys:Double x:Key="dbl1">123456.789</sys:Double> <sys:Double x:Key="dbl2">223456.789</sys:Double> <sys:Double x:Key="dbl3">323456.789</sys:Double> </Window.Resources> <StackPanel> <TextBlock> <TextBlock.Text> <!--复合“格式字符串”格式化字符串--> <MultiBinding StringFormat="第1个数:{0:F1},第2个数:|{1,15}|,第3个数:{2:C}"> <Binding Source="{StaticResource dbl1}" Path="."/> <Binding Source="{StaticResource dbl2}" Path="."/> <Binding Source="{StaticResource dbl3}" Path="."/> </MultiBinding> </TextBlock.Text> </TextBlock> </StackPanel> </Window>
例:
C# 代码
using System; using System.Linq; using System.Windows; using System.Windows.Data; using System.Globalization; using System.Windows.Controls; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); #region 详细的多路绑定操作 //(1)配置数据源绑定信息 MultiBinding multiBinding = new MultiBinding(); multiBinding.Mode = BindingMode.OneWay; multiBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged; //注意,这里添加的顺序将会影响 IMultiValueConverter 接口 Convert(~) 方法中 values 参数的各元素顺序 multiBinding.Bindings.Add(new Binding("Text") { Source = textBox1 }); multiBinding.Bindings.Add(new Binding("Text") { Source = textBox2 }); multiBinding.Bindings.Add(new Binding("Text") { Source = textBox3 }); multiBinding.Bindings.Add(new Binding("Text") { Source = textBox4 }); multiBinding.Converter = new IsEnabledConverter(); //设置转换器属性 //(2)为目标设置绑定 BindingOperations.SetBinding(this.button1, Button.IsEnabledProperty, multiBinding); #endregion } } //================ //多路绑定转换器实现 public class IsEnabledConverter : IMultiValueConverter { //绑定源值转换为目标值 public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values.Cast<string>().Any(text => !string.IsNullOrEmpty(text)) && values[0].ToString() == values[1].ToString() && values[2].ToString() == values[3].ToString()) { return true; } return false; } //目标值转换为绑定源值 public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <local:IsEnabledConverter x:Key="isEnabledConverter"/> </Window.Resources> <StackPanel> <GroupBox Header="C# 代码中的多路绑定"> <StackPanel> <TextBox x:Name="textBox1" Width="90"/> <TextBox x:Name="textBox2" Width="90"/> <TextBox x:Name="textBox3" Width="100"/> <TextBox x:Name="textBox4" Width="100"/> <Button x:Name="button1" Content="注册"/> </StackPanel> </GroupBox> <GroupBox Header="XAML 代码中的多路绑定"> <StackPanel> <TextBox x:Name="textBox5" Width="90"/> <TextBox x:Name="textBox6" Width="90"/> <TextBox x:Name="textBox7" Width="100"/> <TextBox x:Name="textBox8" Width="100"/> <Button Content="注册"> <Button.IsEnabled> <MultiBinding Mode="OneWay" UpdateSourceTrigger="PropertyChanged" Converter="{StaticResource isEnabledConverter}"> <Binding ElementName="textBox5" Path="Text"/> <Binding ElementName="textBox6" Path="Text"/> <Binding ElementName="textBox7" Path="Text"/> <Binding ElementName="textBox8" Path="Text"/> </MultiBinding> </Button.IsEnabled> </Button> </StackPanel> </GroupBox> </StackPanel> </Window>
设置绑定:SetBinding 方法
命名空间: System.Windows.Data
使用 BindingOperations 类的 SetBinding 静态方法
方法原型
public static BindingExpressionBase SetBinding(DependencyObject target, DependencyProperty dp, BindingBase binding)
元素基类 FrameworkElement 还封装了该方法,使用更方便
public BindingExpressionBase SetBinding(DependencyProperty dp, BindingBase binding) { return BindingOperations.SetBinding(this, dp, binding); }
参数
DependencyObject target
绑定的目标
DependencyProperty dp
绑定的目标属性
BindingBase binding
描述绑定的绑定对象
5. 属性
CLR 属性
例:
public class Student { private int name; //CLR 属性“Name”的支持字段 public int Name //CLR 属性“Name”,get、set 访问器最终会被编译成两个方法 { get { return name; } set { name = value; } } } 注: VS 中输入 propfull,再按 2 次 Tab 键,可快速输入带支持字段的属性的代码模板。
上例这种 .NET Framework 中的属性又称为 CLR 属性(CLR, Common Language Runtime)。
我们既可以说 CLR 属性是 private 字段的安全访问包装(器),也可以说一个 private 字段在后台支持(back)一个 CLR 属性。
依赖对象(Dependency Object)
简介
“依赖对象”允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间)、只保留在需要用到数据时能够获得默认值、借用其它对象数据或实时分配空间的能力
它这种实时获取数据的能力是依靠依赖属性来实现的
拥有依赖属性的对象被称为“依赖对象”
主要方法
public object GetValue(DependencyProperty dp)
获取指定依赖属性的值
public void SetValue(DependencyProperty dp, object value)
设置指定依赖属性的本地值
依赖属性(Dependency Property)
简介
概念
依赖属性就是一种可以自己没有值,并能通过使用 Binding 从数据源获得值(依赖在别人身上)的属性
依赖属性的宿主是依赖对象
与 CLR 属性对比
节省实例对内存的开销
属性值可以通过 Binding 依赖在其它对象上
使用
创建
例:
public class Student : DependencyObject { public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student), new PropertyMetadata(null, null)); //依赖属性 NameProperty,是 CLR 属性“Name”的后台支持 public string Name //CLR 属性“Name”,是依赖属性 NameProperty 的访问包装(器),让外界可以通过“实例属性”的形式访问 { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty, value); } } } 注: VS 中输入 propdp,再按 2 次 Tab 键,可快速输入依赖属性的代码模板。
步骤说明
创建自定义的依赖属性,宿主类需要派生自 DependencyObject
自定义依赖属性的引用变量
使用 public static readonly 修饰
类型为 DependencyProperty
变量名后加上 Property 后缀(命名约定)
实例通过 DependencyProperty.Register(~) 方法获取
主要重载方法的参数
string name
要注册的依赖属性名称
按照约定,该名称要与依赖属性的 CLR 属性包装的名称一致,以方便指明该依赖属性是哪个 CLR 属性的后台支持
Type propertyType
依赖属性的(注册)类型(即依赖属性内部所存储的值的类型)
Type ownerType
依赖属性的宿主类型
PropertyMetadata defaultMetadata
依赖属性元数据
自定义依赖属性的包装器
CLR 属性
访问
C# 代码
using System.Windows; using System.Windows.Data; using System.Windows.Controls; namespace WpfApp1 { public partial class MainWindow : Window { private Student stu = new Student(); public MainWindow() { InitializeComponent(); #region 依赖属性的依赖效果展示 //textBox1 的 Text 属性作为 stu 的 Name 属性的数据源 BindingOperations.SetBinding(stu, Student.NameProperty, new Binding("Text") { Source = textBox1, Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); //stu 的 Name 属性作为 textBox2 的 Text 属性的数据源 textBox2.SetBinding(TextBox.TextProperty, new Binding("Name") { Source = stu, Mode = BindingMode.TwoWay, UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged }); #endregion } private void Button_Click(object sender, RoutedEventArgs e) { //使用 DependencyObject 的相关方法访问依赖属性 string value = (string)stu.GetValue(Student.NameProperty); stu.SetValue(Student.NameProperty, "名称"); //使用依赖属性的 CLR 属性包装访问依赖属性 value = stu.Name; stu.Name = "名称2"; value = stu.Name; } } //================ public class Student : DependencyObject { public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(Student), new PropertyMetadata(null, null)); public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty, value); } } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <StackPanel> <Button Content="依赖属性访问" Click="Button_Click"/> <TextBox x:Name="textBox1"/> <TextBox x:Name="textBox2"/> </StackPanel> </Window>
依赖属性值访问的优先级依次为
(1)WPF属性系统强制值
(2)由动画过程控制的值
(3)本地变量值(存储在 EffectiveValueEntry 数组中)
(4)由上级元素的 Template 设置的值
(5)由隐式样式(Implicit Style)设置的值
(6)由样式之触发器(Style Trigger)设置的值
(7)由模板之触发器(Template Trigger)设置的值
(8)由样式之设置器(Style Setter)设置的值
(9)由默认样式(Default Style)设置的值,默认模式其实就是由主题(Theme)指定的模式
(10)由上级元素继承而来的值
(11)默认值,来源于依赖属性的元数据(metadata)
实现原理
DependencyProperty
(1)PropertyFromName 字段
private static Hashtable PropertyFromName = new Hashtable();
用于存储依赖属性实例
(2)RegisterCommon(~) 方法
DependencyProperty.Register(~) 方法内部会调用 DependencyProperty.RegisterCommon(~) 方法
该方法内部使用依赖属性的 name 与 ownerType (CLR 属性包装名 与 宿主类型)数据,通过 FromNameKey 类生成一个 key(可以唯一索引到依赖属性实例),用作该依赖属性添加到 PropertyFromName 哈希表时的键
(3)GlobalIndex 属性
依赖属性实例的全局唯一索引
该属性还会用作依赖属性实例的哈希码
DependencyObject
(1)_effectiveValues 字段
private EffectiveValueEntry[] _effectiveValues;
这是一个按依赖属性实例的 GlobalIndex 排序的数组,依赖属性实例的对应的值通过一些处理后就保存在该数组中
这种排序是通过专门的算法来维持的。
所以,依赖属性实例就相当于一个标识,用于检索其在依赖对象实例中的属性值
附加属性(Attached Properties)
简介
概念
一个属性本来不属于某个对象,但由于某种需求而被后来附加上。也就是把对象放入一个特定环境后,对象才具有的属性
表现出来就是被环境赋予的属性
作用
将属性与宿主类型解耦
使用
创建
例:
public class School { //附加属性 GradeProperty,是其包装方法 GetGrade(~) 与 SetGrade(~) 的后台支持 public static readonly DependencyProperty GradeProperty = DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new PropertyMetadata(-1, null)); public static int GetGrade(DependencyObject obj) // obj 参数为被附加属性的依赖对象 { return (int)obj.GetValue(GradeProperty); } public static void SetGrade(DependencyObject obj, int value) { obj.SetValue(GradeProperty, value); } } 注: VS 中输入 propa,再按 2 次 Tab 键,可快速输入附加属性的代码模板。
步骤说明
附加属性的本质也是依赖属性
创建自定义的附加属性,宿主类无派生要求
自定义附加属性的引用变量
使用 public static readonly 修饰
类型为 DependencyProperty
变量名后加上 Property 后缀(命名约定)
实例通过 DependencyProperty.RegisterAttached(~) 方法获取
主要重载方法的参数
string name
要注册的附加属性名称
Type propertyType
附加属性的(注册)类型(即附加属性内部所存储的值的类型)
Type ownerType
附加属性的宿主类型
PropertyMetadata defaultMetadata
附加属性元数据
自定义附加属性的包装器
Get附加属性名称(~) 与 Set附加属性名称(~) 方法
访问
访问自定义附加属性
C# 代码
using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { Student student = new Student(); //准备一个依赖对象实例,用作被附加上额外属性的对象 int grade = School.GetGrade(student); //通过附加属性 GradeProperty 的 GetGrade(~) 包装方法访问附加属性 School.SetGrade(student, 6); //通过附加属性 GradeProperty 的 SetGrade(~) 包装方法访问附加属性 grade = School.GetGrade(student); } } //================ public class School { public static readonly DependencyProperty GradeProperty = DependencyProperty.RegisterAttached("Grade", typeof(int), typeof(School), new PropertyMetadata(-1, null)); public static int GetGrade(DependencyObject obj) { return (int)obj.GetValue(GradeProperty); } public static void SetGrade(DependencyObject obj, int value) { obj.SetValue(GradeProperty, value); } } public class Student : DependencyObject { } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <StackPanel> <Button Content="附加属性访问" Click="Button_Click"/> </StackPanel> </Window>
与控件的附加属性访问对比
C# 代码
private void Button_Click(object sender, RoutedEventArgs e) { int column = Grid.GetColumn(button1); Grid.SetColumn(button1, 1); column = Grid.GetColumn(button1); }
XAML 代码
<Grid ShowGridLines="True"> <Grid.ColumnDefinitions> <ColumnDefinition Width="200"/> <ColumnDefinition Width="200"/> </Grid.ColumnDefinitions> <Button x:Name="button1" Content="改变位置" Click="Button_Click"/> </Grid>
与依赖属性访问对比
访问 DependencyObject 类的 GetValue(~) 与 SetValue(~) 方法时
依赖属性形式中的调用者,就是拥有该依赖属性的宿主自身的实例
例:  
附加属性形式中的调用者,是通过拥有该附加属性的宿主的附加属性包装方法,从外部传入的被附加属性的对象
例:  
根据依赖属性的实现原理可知,该过程最终访问了被附加属性的对象的 _effectiveValues 字段
6. 事件
路由事件(Routed Event)
简介
来源
就像属性系统在 WPF 中得到升级,进化为依赖属性一样。事件系统在 WPF 中也被升级,进化成为路由事件,并在其基础上衍生出命令传递机制。
编程的本质是用编译器(有时要借助类库)来扩展操作系统的功能,所以,程序的基本运行不可能脱离操作系统——Windows 本身就是一种消息驱动的操作系统,所以我们的程序注定都是消息驱动的,程序运行的时候也要把自己的消息系统与整个操作系统的消息系统“连通”才能够被执行和响应。 纵观几代 windows 平台程序开发,最早的 Windows API 开发(C语言)和 MFC 开发我们可以直接看到各种消息并可以定义自己的消息;到了 COM 和 VB 时代,消息被封装为事件(Event)并一直沿用至 .NET 平台开发——无论怎么说,程序间模块使用消息互相通信的本质是没有改变的。 从 Windows APl 开发到传统的 .NET 开发,消息的传递(或者说事件的激发与响应)都是直接模式的,即消息直接由发送者交给接收者(或者说事件拥有者激发的事件直接由事件响应者的事件处理器(Event Handler)来处理)(这里,我们把这种事件模型称为“直接事件模型”或“CLR 事件模型”)。 WPF 把这种直接消息模型升级为可传递的消息模型。前面我们已经知道 WPF 的 UI 是由布局组件和控件构成的树形结构,当这棵树上的某个结点激发出某个事件时,程序员可以选择以传统的直接事件模式让响应者来响应之,也可以让这个事件在 UI 组件树沿着一定的方向传递且路过多个中转结点,并在这个路由过程中被恰当地处理。
为了降低“直接事件”由事件订阅带来的耦合度和代码量
WPF 中有两种“树”
逻辑树(Logical Tree)
由布局组件、控件等组成
可视元素树(Visual Tree)
由可视化组件等组成
路由事件沿该树传递
路由(Route)含义
起点与终点间有若干个中转站,从起点出发后经过每个中转站时要做出选择,最终以正确(比如最短或者最快)的路径到达终点。
主要区别
直接事件
直接事件激发时,发送者直接将消息通过事件订阅交送给事件响应者,事件响应者使用其事件处理器方法对事件的发生做出响应、驱动程序逻辑按客户需求运行
路由事件
路由事件的事件拥有者和事件响应者之间则没有直接显式的订阅关系,事件的拥有者只负责激发事件,事件将由谁响应它并不知道。事件的响应者则安装有事件侦听器,针对某类事件进行侦听,当有此类事件传递至此时事件响应者就使用事件处理器来响应事件并决定事件是否可以继续传递
使用内置路由事件
例:Button.Click 事件路由效果
C# 代码
using System; using System.Windows; using System.Windows.Controls; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //AddHandler(~) 为指定的路由事件指定路由事件处理器,并将该事件处理器添加到当前元素的事件处理器集合中 gridRoot.AddHandler(Button.ClickEvent, new RoutedEventHandler(Button_Click1)); //AddHandler(~) 方法是 UIElement 类中的方法 } private void Button_Click1(object sender, RoutedEventArgs e) { string msg = $"事件参数属性 Source Type:{e.Source.GetType()},Name:{(e.Source as FrameworkElement).Name}"; //在 UI 中激发路由事件时,参数的属性 Source 表示事件在 Logical Tree 的源头 msg += $"\r\n事件参数属性 OriginalSource Type:{e.OriginalSource.GetType()},Name:{(e.OriginalSource as FrameworkElement).Name}"; //参数的属性 OriginalSource 表示事件的最初激发源。在 UI 中激发路由事件时,参数的属性 OriginalSource 表示事件在 Visual Tree 的源头 MessageBox.Show(msg, "Button_Click1"); } private void Button_Click2(object sender, RoutedEventArgs e) { string msg = $"事件参数属性 Source Type:{e.Source.GetType()},Name:{(e.Source as FrameworkElement).Name}"; msg += $"\r\n事件参数属性 OriginalSource Type:{e.OriginalSource.GetType()},Name:{(e.OriginalSource as FrameworkElement).Name}"; MessageBox.Show(msg, "Button_Click2"); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Grid x:Name="gridRoot" Background="Gray"> <Grid x:Name="gridBack" Background="Honeydew" Margin="20"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="50"/> </Grid.RowDefinitions> <Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="30" Button.Click="Button_Click2"> <!--按钮被点击后,Click 事件将沿 Visual Tree 传递--> <Button x:Name="buttonLeft" Content="左按钮" Width="170" Height="130" Margin="30"/> </Canvas> <Canvas x:Name="canvasRight" Grid.Column="1" Background="Blue" Margin="30"> <Button x:Name="buttonRight" Content="右按钮" Width="170" Height="130" Margin="30"/> </Canvas> <ContentControl x:Name="contentControl" Grid.Row="1" Grid.ColumnSpan="2"> <ContentControl.Template> <ControlTemplate> <Button x:Name="buttonInTemplate" Content="模板内按钮" Width="200" Margin="5"/> </ControlTemplate> </ContentControl.Template> </ContentControl> </Grid> </Grid> </Window>
自定义路由事件
使用
创建
例:
//1、若需要,可创建自定义的路由事件参数 public class ReportTimeEventArgs : RoutedEventArgs { public ReportTimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { } public DateTime ClickTime { get; set; } } //2、创建自定义路由事件的宿主类 public class TimeButton : Button { //(1)声明并注册路由事件 public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton)); //路由事件 ReportTimeEvent,是 CLR 事件“ReportTime”的后台支持 //(2)为路由事件添加 CLR 事件包装(器) public event EventHandler<ReportTimeEventArgs> ReportTime //CLR 事件(属性)“ReportTime”,是路由事件 ReportTimeEvent 的访问包装(器),让外界可以通过“实例事件(属性)”的形式访问 { add { AddHandler(ReportTimeEvent, value); } remove { RemoveHandler(ReportTimeEvent, value); } } //(3)创建可以激发路由事件的方法 protected override void OnClick() { base.OnClick(); //保证 Button 原有功能正常 ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this); args.ClickTime = DateTime.Now; RaiseEvent(args); //激发指定路由事件 } }
步骤说明
创建自定义的路由事件,宿主类需要派生自 UI (可视)元素类 UIElement / ContentElement / UIElement3D
自定义路由事件的引用变量
使用 public static readonly 修饰
类型为 RoutedEvent
变量名后加上 Event 后缀(命名约定)
实例通过 EventManager.RegisterRoutedEvent(~) 方法获取
主要参数
string name
要注册的路由事件名称
按照约定,该名称要与路由事件的 CLR 事件包装的名称一致,以方便指明该路由事件是哪个 CLR 事件的后台支持
RoutingStrategy routingStrategy
事件的路由策略
Type handlerType
路由事件的事件处理器类型
Type ownerType
路由事件的宿主类型
自定义路由事件的包装器
CLR 事件(属性)
访问
C# 代码
using System; using System.Windows; using System.Windows.Controls; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); gridRoot.AddHandler(TimeButton.ReportTimeEvent, new EventHandler<ReportTimeEventArgs>(TimeButton_ReportTime1)); //使用路由事件的 CLR 事件包装访问路由事件 buttonLeft.ReportTime += new EventHandler<ReportTimeEventArgs>(buttonLeft_ReportTime); } private void TimeButton_ReportTime1(object sender, ReportTimeEventArgs e) { FrameworkElement element = sender as FrameworkElement; string msg = $"点击时间Ticks:{e.ClickTime.Ticks}\r\n处理时间Ticks:{DateTime.Now.Ticks}\r\n当前元素:{element.Name}"; MessageBox.Show(msg, "TimeButton_ReportTime1"); } private void canvasLeft_ReportTime(object sender, ReportTimeEventArgs e) { FrameworkElement element = sender as FrameworkElement; string msg = $"点击时间Ticks:{e.ClickTime.Ticks}\r\n处理时间Ticks:{DateTime.Now.Ticks}\r\n当前元素:{element.Name}"; MessageBox.Show(msg, "canvasLeft_ReportTime"); if (element == canvasLeft) { e.Handled = true; //将事件标记为已处理。将事件标记为已处理将会限制路由事件在事件路由过程中对于侦听器的可见性。 事件将仍然进行路由过程的其余部分,但只会调用在 AddHandler(RoutedEvent, Delegate, Boolean) 方法中将 handledEventsToo 参数设置为 true 的事件处理器。 同时,将不会调用实例侦听器上的默认事件处理器(比如用可扩展应用程序标记语言 (XAML) 表示的那些事件处理器)。 标记为已处理的事件这种情形并不常见。 } } private void canvasRight_ReportTime(object sender, ReportTimeEventArgs e) { FrameworkElement element = sender as FrameworkElement; string msg = $"点击时间Ticks:{e.ClickTime.Ticks}\r\n处理时间Ticks:{DateTime.Now.Ticks}\r\n当前元素:{element.Name}"; MessageBox.Show(msg, "canvasRight_ReportTime"); } private void buttonLeft_ReportTime(object sender, ReportTimeEventArgs e) { FrameworkElement element = sender as FrameworkElement; string msg = $"点击时间Ticks:{e.ClickTime.Ticks}\r\n处理时间Ticks:{DateTime.Now.Ticks}\r\n当前元素:{element.Name}"; MessageBox.Show(msg, "buttonLeft_ReportTime"); } } //================ public class ReportTimeEventArgs : RoutedEventArgs { public ReportTimeEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { } public DateTime ClickTime { get; set; } } public class TimeButton : Button { public static readonly RoutedEvent ReportTimeEvent = EventManager.RegisterRoutedEvent("ReportTime", RoutingStrategy.Bubble, typeof(EventHandler<ReportTimeEventArgs>), typeof(TimeButton)); public event EventHandler<ReportTimeEventArgs> ReportTime { add { AddHandler(ReportTimeEvent, value); } remove { RemoveHandler(ReportTimeEvent, value); } } protected override void OnClick() { base.OnClick(); ReportTimeEventArgs args = new ReportTimeEventArgs(ReportTimeEvent, this); args.ClickTime = DateTime.Now; RaiseEvent(args); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Grid x:Name="gridRoot" Background="Gray"> <Grid x:Name="gridBack" Background="Honeydew" Margin="20"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Canvas x:Name="canvasLeft" Grid.Column="0" Background="Red" Margin="30" local:TimeButton.ReportTime="canvasLeft_ReportTime"> <local:TimeButton x:Name="buttonLeft" Content="左按钮" Width="170" Height="130" Margin="30"/> </Canvas> <Canvas x:Name="canvasRight" Grid.Column="1" Background="Blue" Margin="30" local:TimeButton.ReportTime="canvasRight_ReportTime"> <local:TimeButton x:Name="buttonRight" Content="右按钮" Width="170" Height="130" Margin="30"/> </Canvas> </Grid> </Grid> </Window>
附加事件(Attached Event)
简介
作用
主要是让附加属性宿主(一般为非 UI 元素类型),可以与 UI 元素通信
使用
创建
例:
public class Person { public string Name { get; set; } //示例场景:Name 属性改变时,产生附加事件(路由事件) //附加事件 NameChangedEvent,是其包装方法 AddNameChangedHandler(~) 与 RemoveNameChangedHandler(~) 的后台支持 public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Person)); public static void AddNameChangedHandler(DependencyObject obj, RoutedEventHandler h) //obj 参数为被附加事件的 UI 元素对象 { (obj as UIElement)?.AddHandler(NameChangedEvent, h); } public static void RemoveNameChangedHandler(DependencyObject obj, RoutedEventHandler h) { (obj as UIElement)?.RemoveHandler(NameChangedEvent, h); } }
步骤说明
附加事件的本质也是路由事件
创建自定义的附加事件,宿主类无派生要求
自定义附加事件的引用变量
使用 public static readonly 修饰
类型为 RoutedEvent
变量名后加上 Event 后缀(命名约定)
实例通过 EventManager.RegisterRoutedEvent(~) 方法获取
主要参数
string name
要注册的附加事件名称
RoutingStrategy routingStrategy
事件的路由策略
Type handlerType
附加事件的事件处理器类型
Type ownerType
附加事件的宿主类型
自定义附加事件的包装器
Add附加事件名称Handler(~) 与 Remove附加事件名称Handler(~) 方法
访问
访问自定义附加事件
C# 代码
using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Person.AddNameChangedHandler(gridRoot, PersonNameChangedHandler1); //通过附加事件 NameChangedEvent 的 AddNameChangedHandler(~) 包装方法访问附加事件,给指定的 UI 元素附加事件 } private void button_Click(object sender, RoutedEventArgs e) { Person student = new Person(); student.Name = "Tom"; RoutedEventArgs args = new RoutedEventArgs(Person.NameChangedEvent, student); button.RaiseEvent(args); //借用外部 UI 元素实例的 RaiseEvent(~) 方法激发附加事件 } private void PersonNameChangedHandler1(object sender, RoutedEventArgs e) { MessageBox.Show((e.OriginalSource as Person).Name, "PersonNameChangedHandler1"); } private void PersonNameChangedHandler2(object sender, RoutedEventArgs e) { MessageBox.Show((e.OriginalSource as Person).Name, "PersonNameChangedHandler2"); } } //================ public class Person { public string Name { get; set; } public static readonly RoutedEvent NameChangedEvent = EventManager.RegisterRoutedEvent("NameChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(Person)); public static void AddNameChangedHandler(DependencyObject obj, RoutedEventHandler h) { (obj as UIElement)?.AddHandler(NameChangedEvent, h); } public static void RemoveNameChangedHandler(DependencyObject obj, RoutedEventHandler h) { (obj as UIElement)?.RemoveHandler(NameChangedEvent, h); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Grid x:Name="gridRoot" Background="Gray"> <Grid x:Name="gridBack" Background="Honeydew" Margin="20" local:Person.NameChanged="PersonNameChangedHandler2"> <Canvas x:Name="canvas" Grid.Column="0" Background="BlueViolet" Margin="30"> <Button x:Name="button" Content="改变 Person Name,激发附加事件" Height="130" Margin="30" Click="button_Click"/> </Canvas> </Grid> </Grid> </Window>
与路由事件访问对比
访问 UI 元素类的 AddHandler(~) 与 RemoveHandler(~) 方法时
路由事件形式中的调用者,就是拥有该路由事件的宿主自身的实例
附加事件形式中的调用者,是通过拥有该附加事件的宿主的附加事件包装方法,从外部传入的被附加事件的对象
7. 命令
来源
事件
特点
事件不具有约束力,接收者如何响应事件送来的消息未做规定,每个接收者都可以使用自己的行为来响应事件
命令
特点
命令具有约束力,每个接收者都要执行相同语义的行为
命令系统
基本元素
命令(Command)
即实现了 ICommand 接口的类
命名空间: System.Windows.Input
如:
RoutedCommand
属性
InputGestures
命令对应的输入笔势集合
元素类型为 InputGesture
输入设备笔势的抽象类
主要派生类
KeyGesture
MouseGesture
若命令源中设置了 CommandParameter 属性,则在命令的 InputGestures 属性中添加的输入笔势会无效(因为该处无法控制传递的 CommandParameter 数据)。 这时需要在命令目标的外围 UI 元素(如命令绑定所在元素,或在命令绑定所在元素与命令目标元素的路由路径之间的任一元素)的 InputBindings 属性上添加输入笔势(相当于为命令指派了另一个命令源)。
关于 InputBindings 属性
元素类型为 InputBinding
主要属性
Gesture
输入笔势
类型为 InputGesture
Command
CommandParameter
CommandTarget
主要派生类
KeyBinding
MouseBinding
Name
命令的名称
OwnerType
命令的宿主类型
RoutedUICommand
属性
InputGestures
Name
OwnerType
Text
描述该命令的文本
命令源(Command Source)
命令的发送者
即实现了 ICommandSource 接口的类
接口属性
Command
为命令源指派的命令
CommandParameter
给命令传递的参数
CommandTarget
为命令(源)指定的命令目标
如:
Button
MenuItem
InputBinding
命令目标(Command Target)
命令的接收者
即实现了 IInputElement 接口的类
如:
UIElement
ContentElement
UIElement3D
主要事件
命令目标的一些事件,如:PreviewCanExecute、CanExecute、PreviewExecuted、Executed 是被 CommandManager 附加上的。 其中 PreviewCanExecute、CanExecute 的发送由系统控制,且发送频率较高。
命令关联/绑定(Command Binding)
负责关联命令的一些外围逻辑
逻辑如:
命令执行前判断是否可以执行
命令执行后的后续工作
命令绑定要安置在命令目标的外围 UI 元素上,以“登高望远”捕获相关的路由事件
为外围 UI 元素的 CommandBindings 属性添加命令绑定元素时,要在命令相关 UI 元素初始化代码之后的位置编码,防止在命令相关 UI 元素初始化完成前就产生相关事件,导致异常
相关的类
CommandBinding
属性
Command
命令绑定所辅助的命令
事件
PreviewCanExecute
判断命令是否可执行之前的事件
(一般不用设置该属性)
CanExecute
判断命令是否可执行时的事件
PreviewExecuted
命令执行之前的事件
(一般不设置该属性,设置后,Executed 事件默认不被激发(可看源码))
Executed
命令执行时的事件
关系图

主要使用步骤
(1)命令
创建命令类
若命令与具体业务逻辑无关
可以使用 RoutedCommand
若命令为与业务逻辑相关的专有命令
需要
创建 RoutedCommand 的派生类
或 创建实现了 ICommand 接口的类
创建命令类实例
说明
程序中的某种操作一般只需要一个命令实例与之对应即可
所以程序中的命令一般使用单例模式
(2)命令源
指派命令的源(为命令源指派命令)
操作
设置命令源的 Command 属性
说明
一个命令可以有多个命令源
(3)命令目标
指定命令源的命令目标
操作
设置命令源的 CommandTarget 属性
说明
若没有为命令源指定命令目标,WPF 系统默认当前拥有焦点的对象就是命令目标
(4)命令绑定
组合命令的外围逻辑,辅助命令进行命令执行前、执行后的相关操作
说明
一旦某个命令目标被命令源“瞄上”,命令源就会频繁地向命令目标“投石问路”。
命令目标也就会相应地发送 PreviewCanExecute 和 CanExecute 附加事件,事件会沿着 UI 元素树传递并被命令绑定所捕捉,命令绑定捕捉到这些事件后会把命令能不能发送实时报告给命令。
如果命令被发送出来并到达命令目标,命令目标就会发送 PreviewExecuted 和 Executed 附加事件,这两个事件也会沿着 Ul 元素树传递并被命令绑定所捕捉,命令绑定会完成一些后续的工作。(对于那些与业务逻辑无关的通用命令,这些后续工作才是最重要的。)
例:按钮发送命令,清空文本框
后台绑定命令方式:
C# 代码
using System.Windows; using System.Windows.Input; namespace WpfApp1 { public partial class MainWindow : Window { //1、命令 private RoutedCommand clearCmd = new RoutedCommand("Clear", typeof(MainWindow)); private const string CMDPARAM1 = "清空按钮1"; private const string CMDPARAM2 = "清空按钮2"; private int previewCanExecuteCount = 0; private int canExecuteCount = 0; private int previewExecutedCount = 0; private int executedCount = 0; public MainWindow() { InitializeComponent(); //未设置命令源的 CommandParameter 属性时,可通过命令的 InputGestures 属性添加命令的输入笔势 //clearCmd.InputGestures.Add(new KeyGesture(Key.C, ModifierKeys.Alt)); //2、命令源 btnClear1.Command = clearCmd; btnClear1.CommandParameter = CMDPARAM1; btnClear2.Command = clearCmd; btnClear2.CommandParameter = CMDPARAM2; //3、命令目标 btnClear1.CommandTarget = txtText1; btnClear2.CommandTarget = txtText2; //4、命令绑定 CommandBinding commandBinding = new CommandBinding(); commandBinding.Command = clearCmd; //commandBinding.PreviewCanExecute += CmdPreviewCanExecuteHandler; commandBinding.CanExecute += CmdCanExecuteHandler; //commandBinding.PreviewExecuted += CmdPreviewExecutedHandler; commandBinding.Executed += CmdExecutedHandler; //设置了命令源的 CommandParameter 属性时(没设置也可以),要通过命令目标的外围 UI 元素的 InputBindings 属性设置命令的输入笔势 grid.InputBindings.Add(new KeyBinding(clearCmd, Key.C, ModifierKeys.Alt) { CommandParameter = CMDPARAM1, CommandTarget = txtText1 }); grid.InputBindings.Add(new KeyBinding(clearCmd, Key.D, ModifierKeys.Alt) { CommandParameter = CMDPARAM2, CommandTarget = txtText2 }); grid.CommandBindings.Add(commandBinding); //将命令绑定安置在命令目标的外围 UI 元素上 } void CmdPreviewCanExecuteHandler(object sender, CanExecuteRoutedEventArgs e) { txtPreviewCanExecuteCount.Text = $"{++previewCanExecuteCount}"; } void CmdCanExecuteHandler(object sender, CanExecuteRoutedEventArgs e) { txtCanExecuteCount.Text = $"{++canExecuteCount}"; if (e.Parameter is CMDPARAM1) { if (string.IsNullOrEmpty(txtText1.Text)) { e.CanExecute = false; } else { e.CanExecute = true; } } else if (e.Parameter is CMDPARAM2) { if (string.IsNullOrEmpty(txtText2.Text)) { e.CanExecute = false; } else { e.CanExecute = true; } } //e.Handled = true; //可根据需要设置为已处理 } void CmdPreviewExecutedHandler(object sender, ExecutedRoutedEventArgs e) { txtPreviewExecutedCount.Text = $"{++previewExecutedCount}"; } void CmdExecutedHandler(object sender, ExecutedRoutedEventArgs e) { txtExecutedCount.Text = $"{++executedCount}"; if (e.Parameter is CMDPARAM1) { txtText1.Clear(); } else if (e.Parameter is CMDPARAM2) { txtText2.Clear(); } //e.Handled = true; //可根据需要设置为已处理 } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Grid x:Name="gridRoot"> <Grid x:Name="grid"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBox x:Name="txtText1"/> <Button x:Name="btnClear1" Content="清空源1" Grid.Column="1"/> <TextBox x:Name="txtText2" Grid.Row="1"/> <Button x:Name="btnClear2" Content="清空源2" Grid.Column="1" Grid.Row="1"/> <Label Content="PreviewCanExecute 发送次数:" Grid.Row="2"/> <TextBox x:Name="txtPreviewCanExecuteCount" Grid.Column="1" Grid.Row="2"/> <Label Content="CanExecute 发送次数:" Grid.Row="3"/> <TextBox x:Name="txtCanExecuteCount" Grid.Column="1" Grid.Row="3"/> <Label Content="PreviewExecuted 发送次数:" Grid.Row="4"/> <TextBox x:Name="txtPreviewExecutedCount" Grid.Column="1" Grid.Row="4"/> <Label Content="Executed 发送次数:" Grid.Row="5"/> <TextBox x:Name="txtExecutedCount" Grid.Column="1" Grid.Row="5"/> </Grid> </Grid> </Window>
前台绑定命令方式:
C# 代码
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Input; namespace WpfApp1 { public partial class MainWindow : Window { public static readonly string CMDPARAM1 = "清空按钮1"; public static readonly string CMDPARAM2 = "清空按钮2"; public static Count count = new Count(); public MainWindow() { InitializeComponent(); } void CmdPreviewCanExecuteHandler(object sender, CanExecuteRoutedEventArgs e) { ++count.PreviewCanExecute; } void CmdCanExecuteHandler(object sender, CanExecuteRoutedEventArgs e) { ++count.CanExecute; if (e.Parameter?.ToString() == CMDPARAM1) { if (string.IsNullOrEmpty(txtText1.Text)) { e.CanExecute = false; } else { e.CanExecute = true; } } else if (e.Parameter?.ToString() == CMDPARAM2) { if (string.IsNullOrEmpty(txtText2.Text)) { e.CanExecute = false; } else { e.CanExecute = true; } } //e.Handled = true; //可根据需要设置为已处理 } void CmdPreviewExecutedHandler(object sender, ExecutedRoutedEventArgs e) { ++count.PreviewExecuted; } void CmdExecutedHandler(object sender, ExecutedRoutedEventArgs e) { ++count.Executed; if (e.Parameter.ToString() == CMDPARAM1) { txtText1.Clear(); } else if (e.Parameter.ToString() == CMDPARAM2) { txtText2.Clear(); } //e.Handled = true; //可根据需要设置为已处理 } } //================ public class Count : ObservableObject { private int previewCanExecute; public int PreviewCanExecute { get { return previewCanExecute; } set { previewCanExecute = value; OnPropertyChanged(); } } private int canExecute; public int CanExecute { get { return canExecute; } set { canExecute = value; OnPropertyChanged(); } } private int previewExecuted; public int PreviewExecuted { get { return previewExecuted; } set { previewExecuted = value; OnPropertyChanged(); } } private int executed; public int Executed { get { return executed; } set { executed = value; OnPropertyChanged(); } } } public class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <RoutedCommand x:Key="clearCmd"/> </Window.Resources> <Grid x:Name="gridRoot"> <Grid x:Name="grid"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBox x:Name="txtText1"/> <Button x:Name="btnClear1" Content="清空源1" Grid.Column="1" Command="{StaticResource clearCmd}" CommandParameter="{x:Static local:MainWindow.CMDPARAM1}" CommandTarget="{Binding ElementName=txtText1}"/> <TextBox x:Name="txtText2" Grid.Row="1"/> <Button x:Name="btnClear2" Content="清空源2" Grid.Column="1" Grid.Row="1" Command="{StaticResource clearCmd}" CommandParameter="{x:Static local:MainWindow.CMDPARAM2}" CommandTarget="{Binding ElementName=txtText2}"/> <Label Content="PreviewCanExecute 发送次数:" Grid.Row="2"/> <TextBox x:Name="txtPreviewCanExecuteCount" Grid.Column="1" Grid.Row="2" Text="{Binding Source={x:Static local:MainWindow.count},Path=PreviewCanExecute,Mode=OneWay}"/> <Label Content="CanExecute 发送次数:" Grid.Row="3"/> <TextBox x:Name="txtCanExecuteCount" Grid.Column="1" Grid.Row="3" Text="{Binding Source={x:Static local:MainWindow.count},Path=CanExecute,Mode=OneWay}"/> <Label Content="PreviewExecuted 发送次数:" Grid.Row="4"/> <TextBox x:Name="txtPreviewExecutedCount" Grid.Column="1" Grid.Row="4" Text="{Binding Source={x:Static local:MainWindow.count},Path=PreviewExecuted,Mode=OneWay}"/> <Label Content="Executed 发送次数:" Grid.Row="5"/> <TextBox x:Name="txtExecutedCount" Grid.Column="1" Grid.Row="5" Text="{Binding Source={x:Static local:MainWindow.count},Path=Executed,Mode=OneWay}"/> <Grid.InputBindings> <KeyBinding Command="{StaticResource clearCmd}" CommandParameter="{x:Static local:MainWindow.CMDPARAM1}" CommandTarget="{Binding ElementName=txtText1}" Gesture="Alt+C"/> <KeyBinding Command="{StaticResource clearCmd}" CommandParameter="{x:Static local:MainWindow.CMDPARAM2}" CommandTarget="{Binding ElementName=txtText2}" Key="D" Modifiers="Alt"/> </Grid.InputBindings> <Grid.CommandBindings> <CommandBinding Command="{StaticResource clearCmd}" CanExecute="CmdCanExecuteHandler" Executed="CmdExecutedHandler"/> </Grid.CommandBindings> </Grid> </Grid> </Window>
WPF 内置命令
命令库
说明: 命令目标一般不提供命令的逻辑,但有些控件为命令库中的一些命令提供了实现逻辑。例如,TextBox 类为 ApplicationCommands.Copy、ApplicationCommands.Cut、ApplicationCommands.Paste 等命令(内置有命令绑定)提供了逻辑。
命令实例从命令库静态类的相关静态属性获取
属性/命令类型为 RoutedUICommand
命名空间:System.Windows.Input
NavigationCommands
一组与导航相关的命令
ApplicationCommands
一组与应用程序相关的命令
ComponentCommands
一组与组件相关的命令
MediaCommands
一组与媒体相关的命令
命名空间:System.Windows.Documents
EditingCommands
一组与编辑相关的命令
例:
C# 代码
using System.Windows; using System.Windows.Input; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } void CmdCanExecuteHandler1(object sender, CanExecuteRoutedEventArgs e) { if (string.IsNullOrEmpty(txtText1.Text)) { e.CanExecute = true; } else { e.CanExecute = false; } } void CmdExecutedHandler1(object sender, ExecutedRoutedEventArgs e) { txtText1.Text = "新建文本"; } void CmdCanExecuteHandler2(object sender, CanExecuteRoutedEventArgs e) { RoutedUICommand command = e.Command as RoutedUICommand; if (command is null) return; if (txtText2.Text == command.Text) { e.CanExecute = false; } else { e.CanExecute = true; } } void CmdExecutedHandler2(object sender, ExecutedRoutedEventArgs e) { RoutedUICommand command = e.Command as RoutedUICommand; if (command is null) return; txtText2.Text = command.Text; } void CmdCanExecuteHandler3(object sender, CanExecuteRoutedEventArgs e) { //对于 EditingCommands.Delete 对应命令,TextBox 本身提供了逻辑,这里不会响应 if (string.IsNullOrEmpty(txtText3.Text)) { e.CanExecute = false; } else { e.CanExecute = true; } } void CmdExecutedHandler3(object sender, ExecutedRoutedEventArgs e) { //对于 EditingCommands.Delete 对应命令,TextBox 本身提供了逻辑,这里不会响应 txtText3.Clear(); } void CmdCanExecuteHandler4(object sender, CanExecuteRoutedEventArgs e) { if (string.IsNullOrEmpty(txtText4.Text)) { txtText4.FontSize = SystemFonts.MessageFontSize; e.CanExecute = false; } else { e.CanExecute = true; } } void CmdExecutedHandler4(object sender, ExecutedRoutedEventArgs e) { txtText4.FontSize += 1; } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Grid x:Name="gridRoot"> <Grid x:Name="grid"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBox x:Name="txtText1"/> <Button x:Name="btn1" Content="新建" Grid.Column="1" Command="{x:Static ApplicationCommands.New}" CommandTarget="{Binding ElementName=txtText1}"/> <TextBox x:Name="txtText2" Grid.Row="1"/> <Button x:Name="btn2" Content="播放" Grid.Column="1" Grid.Row="1" Command="MediaCommands.Play" CommandTarget="{Binding ElementName=txtText2}"/> <TextBox x:Name="txtText3" Grid.Row="2"/> <Button x:Name="btn3" Content="删除" Grid.Column="1" Grid.Row="2" Command="EditingCommands.Delete" CommandTarget="{Binding ElementName=txtText3}"/> <TextBox x:Name="txtText4" Grid.Row="3"/> <Button x:Name="btn4" Content="增加字体大小" Grid.Column="1" Grid.Row="3" Command="EditingCommands.IncreaseFontSize" CommandTarget="{Binding ElementName=txtText4}"/> <Grid.CommandBindings> <!--访问内置命令库中的命令对应的属性,在 XAML 中可以使用简写。即直接指定属性名,而不用添加类作用域限定--> <CommandBinding Command="New" CanExecute="CmdCanExecuteHandler1" Executed="CmdExecutedHandler1"/> <CommandBinding Command="Play" CanExecute="CmdCanExecuteHandler2" Executed="CmdExecutedHandler2"/> <!--转换时,字符串 Delete 会优先转换为 ApplicationCommands.Delete,所以这里未用简写--> <CommandBinding Command="EditingCommands.Delete" CanExecute="CmdCanExecuteHandler3" Executed="CmdExecutedHandler3"/> <CommandBinding Command="IncreaseFontSize" CanExecute="CmdCanExecuteHandler4" Executed="CmdExecutedHandler4"/> </Grid.CommandBindings> </Grid> </Grid> </Window>
8. 资源
概念
资源
我们把有用的东西称为资源,程序中的数据就是算法的资源
资源存储位置
程序文件
传统的程序级别的二进制资源
外部文件
变量
WPF 中,还支持对象级别的对象资源。即每个 UI 元素都可以拥有自己的资源
对象级别资源
存储位置
UI 元素的 Resources 属性
属性继承自 FrameworkElement
类型为 ResourceDictionary
关于资源字典 ResourceDictionary
该类型实现了基于哈希表的字典
键、值的类型均为 object
检索资源优先级依次为
(1)沿逻辑树向上逐层查找 UI 元素中的资源
(2)在应用程序资源中查找
(3)在主题资源中查找
(4)在系统资源中查找
使用值时,XAML 中可以自动转换为资源本身的类型,但 C# 中需要手动转换
资源项的键
值
一般为 String、 Type 或 System.Windows.ResourceKey 派生类等类型的值
分类
显式键
即有通过 x:Key 为添加到 ResourceDictionary 的资源项设置的索引键
隐式键
即没有通过 x:Key 为添加到 ResourceDictionary 的资源项设置索引键时,系统自动设置的键
如:
使用隐式键的 Style 资源,将默认作为其 TargetType 属性指定目标类型元素的样式
说明: Style 类通过给自身设置 [DictionaryKeyPropertyAttribute("TargetType")] 特性,指定其 TargetType 属性值为隐式键的值。 UI 元素的 Style 属性未设置时,会使用自身的 DefaultStyleKey 依赖属性值检索资源中的样式。该值在 UI 元素构造时一般会被覆盖为“typeof(目标元素类型)”的值: 如: DefaultStyleKeyProperty.OverrideMetadata(typeof(Label), new FrameworkPropertyMetadata(typeof(Label)));
使用隐式键的 DataTemplate 资源,将默认作为其 DataType 属性指定数据类型的数据模板
主要成员
方法
public void Add(object key, object value);
属性
Source
含义
待加载资源的 URI
一般使用 Pack URI 指定
URI 的目标必须是另一个 XAML 文件,根元素需为 ResourceDictionary
类型
Uri
主要用途
该属性一般在为 ResourceDictionary 的 MergedDictionaries 属性添加的 ResourceDictionary 实例中使用
用法
例:
C# 代码
using System.Collections; using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { listBox.Items.Clear(); foreach (var resourceItem in stackPanel.Resources) { DictionaryEntry dictionaryItem = (DictionaryEntry)resourceItem; listBox.Items.Add($"{dictionaryItem.Key} : {dictionaryItem.Value}"); } } } }
窗体 XAML
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <StackPanel x:Name="stackPanel"> <StackPanel.Resources> <ResourceDictionary Source="NumberDictionaryResource.xaml"/> </StackPanel.Resources> <Button Content="展示资源" Click="Button_Click"/> <ListBox x:Name="listBox"/> </StackPanel> </Window>
待加载的资源字典 XAML
文件名: NumberDictionaryResource.xaml 内容: <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <sys:String x:Key="Num1">数字1</sys:String> <sys:String x:Key="Num2">数字2</sys:String> <sys:String x:Key="Num3">数字3</sys:String> <sys:String x:Key="Num4">数字4</sys:String> <sys:String x:Key="Num5">数字5</sys:String> <sys:String x:Key="Num6">数字6</sys:String> </ResourceDictionary>
MergedDictionaries
含义
表示合并到当前资源字典的其它资源字典集合
类型
Collection<ResourceDictionary>
用法
例:
C# 代码
using System.Collections; using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { listBox.Items.Clear(); foreach (var resourceItem in stackPanel.Resources) { DictionaryEntry dictionaryItem = (DictionaryEntry)resourceItem; listBox.Items.Add($"{dictionaryItem.Key} : {dictionaryItem.Value}"); } foreach (var resourceDictionary in stackPanel.Resources.MergedDictionaries) { foreach (var resourceItem in resourceDictionary) { DictionaryEntry dictionaryItem = (DictionaryEntry)resourceItem; listBox.Items.Add($"{dictionaryItem.Key} : {dictionaryItem.Value}"); } } } } }
窗体 XAML
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <StackPanel x:Name="stackPanel"> <StackPanel.Resources> <ResourceDictionary> <sys:String x:Key="str1">Hello 1</sys:String> <sys:String x:Key="str2">Hello 2</sys:String> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="NumberDictionaryResource.xaml"/> <ResourceDictionary> <sys:String x:Key="str3">Hello 3</sys:String> <sys:String x:Key="str4">Hello 4</sys:String> </ResourceDictionary> </ResourceDictionary.MergedDictionaries> </ResourceDictionary> </StackPanel.Resources> <Button Content="展示资源" Click="Button_Click"/> <ListBox x:Name="listBox"/> </StackPanel> </Window>
例:
资源的定义与检索
C# 代码
using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { //在 C# 代码中使用资源字典中的资源: //使用 FrameworkElement 的相关方法、属性检索资源 string str1 = this.FindResource("str1") as string; string str2 = this.TryFindResource("str2") as string; string str3 = this.TryFindResource("str3") as string; double dbl = (double)this.FindResource("dbl"); object dbl2 = this.TryFindResource("dbl2"); dbl = (double)this.Resources["dbl"]; this.Resources["dbl"] = 123.456; dbl = (double)this.Resources["dbl"]; } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <sys:String x:Key="str1">Hello, Resource.</sys:String> <sys:String x:Key="str2">你好,对象级资源。</sys:String> <sys:Double x:Key="dbl">3.1415926</sys:Double> </Window.Resources> <StackPanel> <!--在 XAML 中使用资源字典中的资源:--> <TextBlock Text="{StaticResource ResourceKey=str1}"/> <TextBlock Text="{StaticResource str2}"/> <!--Double 类型的值直接赋给 String 类型的属性会报错。--> <!--<TextBlock Text="{StaticResource dbl}"/>--> <TextBlock Text="{Binding Source={StaticResource dbl}}"/> <Button Content="测试" Click="Button_Click"/> </StackPanel> </Window>
对资源的引用
方式
静态引用
特点
只在加载时求值,有利于减少性能消耗
使用
引用时使用 StaticResourceExtension 标记扩展
动态引用
特点
运行时求值
使用
引用时使用 DynamicResourceExtension 标记扩展
例:
C# 代码
using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { this.Resources["str1"] = "运行时修改的字符串1"; this.Resources["str2"] = "运行时修改的字符串2"; } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <sys:String x:Key="str1">加载时字符串1</sys:String> <sys:String x:Key="str2">加载时字符串2</sys:String> </Window.Resources> <StackPanel> <Button Content="修改资源" Click="Button_Click"/> <TextBlock Text="{StaticResource ResourceKey=str1}"/> <TextBlock Text="{DynamicResource ResourceKey=str2}"/> </StackPanel> </Window>
关于 Windows 主题资源
说明
WPF 允许为不同的 Windows 主题创建资源。这些资源文件必须位于一个名为“Themes”的文件夹中
Themes 文件夹在工程中的位置: 
资源文件内容
文件内容为一个 ResourceDictionary
资源项一般为使用隐式键的 Style
关于主题资源中资源项的键
若要为资源项指定显式键,键值的类型需为 ComponentResourceKey
关于 ComponentResourceKey 类
命名空间:System.Windows
基类:System.Windows.ResourceKey
主要属性
TypeInTargetAssembly
资源项所在程序集中的任意类类型,用于辅助资源项定位
ResourceId
资源项唯一标识符
用途
一般用于自定义控件
分类
特定主题资源
我们可以为特定主题定义资源,以根据所使用的主题更改控件的外观。 例如,Windows Classic 主题(Windows 2000的默认主题)中的 Button 的外观与 Windows Luna 主题(Windows XP的默认主题)中的是不同的,因为 Button 为每个主题使用了不同的 ControlTemplate。
特定主题资源以特定文件名存储在工程中
命名规约
一般为:Themes/主题名称.颜色.xaml
例:
查找方式
(1)如果没有为特定主题定义资源,控件将在 Classic.xaml 中查找资源
(2)如果未从与当前主题对应的文件或 Classic.xaml 中找到资源,控件将在通用主题资源中查找
通用主题资源
与特定主题无关的主题资源
文件名
Generic.xaml
注意: VS 中新建项向导添加自定义控件时自动创建的“Generic.xaml”文件,其文件字符编码是 UTF-8无BOM 编码。若编译时出现字符编码错误,将其文件字符编码改为 UTF-8带BOM 即可。 
使用
注意
程序集需要用 ThemeInfoAttribute 特性来指定主题资源文件位置
在程序集工程的 AssemblyInfo.cs 文件中添加,例:
[assembly: ThemeInfo( ResourceDictionaryLocation.None, //指定特定主题资源所处位置(未在页面或应用程序资源字典中找到资源时采用) ResourceDictionaryLocation.SourceAssembly //指定通用主题资源所处位置(未在页面、应用程序或任何特定主题资源字典中找到时采用) )]
例:
C# 代码
using System.Windows; using System.Windows.Controls; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } //================ public class CustomControl1 : Control { static CustomControl1() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl1), new FrameworkPropertyMetadata(typeof(CustomControl1))); } } public class CustomControl2 : Control { static CustomControl2() { DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomControl2), new FrameworkPropertyMetadata(typeof(CustomControl2))); } } public class ThemesResourcesLocationMark { } }
主窗体 XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel> <local:CustomControl1 Width="100" Height="50"/> <local:CustomControl2 Width="100" Height="50"/> <!--通过键检索主题资源项(建议使用动态引用来引用主题资源中的资源项):--> <local:CustomControl2 Width="100" Height="50" Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type local:ThemesResourcesLocationMark},ResourceId=custom2Style}}"/> </StackPanel> </Window>
通用主题资源文件代码
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp1"> <Style TargetType="{x:Type local:CustomControl1}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl1}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Rectangle Fill="LightSkyBlue"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <Style TargetType="{x:Type local:CustomControl2}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl2}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Rectangle Fill="MediumSlateBlue"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> <!--定义一个带显式键的主题资源项:--> <Style x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type local:ThemesResourcesLocationMark},ResourceId=custom2Style}" TargetType="{x:Type local:CustomControl2}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:CustomControl2}"> <Border Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"> <Rectangle Fill="NavajoWhite"/> </Border> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
程序级别资源
项目 Resources.resx 文件中的资源
添加步骤
(1)打开项目 Properties 文件夹中的 Resources.resx 文件
系统会根据该文件生成一个在 程序命名空间.Properties 命名空间下的 Resources 类
通过该类的方法、属性(一般被修饰为 static 的)可以获取资源
(2)在界面中选择资源类型,然后添加、编辑。如图:
(1)字符串资源:  (2)其它类型资源:  添加一个图片文件后,VS 自动新建了一个 Resources 文件夹,并将该图片文件放在其中。
(3)若资源要在 XAML 中引用,则还需要将访问修饰符改为 Public
使用
C# 代码
using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { textBlock2.Text = WpfApp1.Properties.Resources.str2; } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:props="clr-namespace:WpfApp1.Properties"> <StackPanel> <TextBlock Text="{x:Static props:Resources.str1}"/> <Button Content="加载第2个" Click="Button_Click"/> <TextBlock x:Name="textBlock2"/> </StackPanel> </Window>
项目中直接添加的资源
添加步骤
在 VS 项目中右键添加
例:
  添加后,根据需要,设置文件属性的复制、生成行为,如: 
访问
关于 Pack URI
来源
URI
比较完整的组成示例
协议:[//][用户名:密码@][主机.域名][:端口号][[/]目录路径][/文件名][?查询字符串][#片段标识符]
Pack URI 可以理解成协议为 pack 的 URI
组成
[pack://授权][路径]
授权
使用时,必须用字符“,”替换字符“/”,并且必须对保留字符(如“%”和“?”)进行转义。 授权中的 3个 “/”可以理解为 URI 省略“主机.域名:端口号”后的形式 协议://[主机.域名][:端口号][/路径] 省略后: 协议://[/路径]
application:///
标识应用程序的数据文件
siteoforigin:///
标识源站点的文件
路径
[/程序集名称][;版本][;公钥][;组件][/目录路径]文件名
注: 绝对目录路径以当前程序集工程目录 或 可执行程序集启动目录 为起点; 相对目录路径以当前代码文件所在目录为起点。
例:
pack://application:,,,/ReferencedAssembly;v1.0.0.1;component/Subfolder/ResourceFile.xaml
对应的相对路径
/Subfolder/ResourceFile.xaml
或 Subfolder/ResourceFile.xaml
pack://siteoforigin:,,,/Subfolder/SiteOfOriginFile.xaml
例:
C# 代码
using System; using System.Windows; using System.Windows.Media.Imaging; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Uri image21Uri = new Uri(@"pack://application:,,,/WpfApp1;v1.0.0.0;component/img1.jpg"); //该 Uri(~) 构造方法默认 URI 参数为 UriKind.Absolute 类型 image21.Source = new BitmapImage(image21Uri); Uri image22Uri = new Uri(@"/img1.jpg", UriKind.Relative); image22.Source = new BitmapImage(image22Uri); Uri image23Uri = new Uri(@"img1.jpg", UriKind.Relative); image23.Source = new BitmapImage(image23Uri); } } }
窗体 XAML
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Grid> <Grid.Resources> <ResourceDictionary Source="Files/ImageDictionary.xaml"/> </Grid.Resources> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBlock Grid.Column="0" Grid.Row="0" Text="XAML中绝对路径:pack://application:,,,/WpfApp1;v1.0.0.0;component/img1.jpg" TextWrapping="Wrap"/> <Image Grid.Column="1" Grid.Row="0" Source="pack://application:,,,/WpfApp1;v1.0.0.0;component/img1.jpg"/> <TextBlock Grid.Column="0" Grid.Row="1" Text="XAML中绝对路径:pack://application:,,,/WpfApp1;v1.0.0.0;component/Files/Images/img2.jpg" TextWrapping="Wrap"/> <Image Grid.Column="1" Grid.Row="1" Source="pack://application:,,,/WpfApp1;v1.0.0.0;component/Files/Images/img2.jpg"/> <TextBlock Grid.Column="0" Grid.Row="2" Text="XAML中相对路径:/img1.jpg" TextWrapping="Wrap"/> <Image Grid.Column="1" Grid.Row="2" Source="/img1.jpg"/> <TextBlock Grid.Column="0" Grid.Row="3" Text="XAML中相对路径:/Files/Images/img2.jpg" TextWrapping="Wrap"/> <Image Grid.Column="1" Grid.Row="3" Source="/Files/Images/img2.jpg"/> <TextBlock Grid.Column="0" Grid.Row="4" Text="XAML中相对路径:img1.jpg" TextWrapping="Wrap"/> <Image Grid.Column="1" Grid.Row="4" Source="img1.jpg"/> <TextBlock Grid.Column="0" Grid.Row="5" Text="XAML中相对路径:Files/Images/img2.jpg" TextWrapping="Wrap"/> <Image Grid.Column="1" Grid.Row="5" Source="Files/Images/img2.jpg"/> <TextBlock Grid.Column="2" Grid.Row="0" Text="XAML中相对路径:Files/ImageDictionary.xaml + (Images/img2.jpg)" TextWrapping="Wrap"/> <ContentControl Grid.Column="3" Grid.Row="0" Content="{StaticResource img2Res}"/> <TextBlock Grid.Column="2" Grid.Row="1" Text="C# 中绝对路径:pack://application:,,,/WpfApp1;v1.0.0.0;component/img1.jpg" TextWrapping="Wrap"/> <Image x:Name="image21" Grid.Column="3" Grid.Row="1"/> <TextBlock Grid.Column="2" Grid.Row="2" Text="C# 中相对路径:/img1.jpg" TextWrapping="Wrap"/> <Image x:Name="image22" Grid.Column="3" Grid.Row="2"/> <TextBlock Grid.Column="2" Grid.Row="3" Text="C# 中相对路径:img1.jpg" TextWrapping="Wrap"/> <Image x:Name="image23" Grid.Column="3" Grid.Row="3"/> </Grid> </Window>
图片资源字典 XAML
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Image x:Key="img2Res" Source="Images/img2.jpg"/> </ResourceDictionary>
程序集资源文件位置

9. 模板
模板
设计工具
Visual Studio
Blend for Visual Studio
相关概念
字面意思
具有一定规格的样板
WPF 技术的作用
WPF 是一种 Windows 程序的表现方式,是程序的数据、算法这个“内容”的表现“形式”
控件
“内容”分类
“算法内容”
表现形式控制者
ControlTemplate(控件模板)
控制控件外观
“数据内容”
表现形式控制者
DataTemplate(数据模板)
控制数据外观
载体作用
作为算法内容的表现形式,让用户操作逻辑
作为数据内容的表现形式,让用户观察数据
详解
控件模板
提示
查看控件系统模板的方法
使用 Blend,在界面设计视图对应控件上或在对象和时间线视图对应元素上右击,选择“编辑模板”,“编辑副本”,再按提示操作,即可导出包含系统模板的 Style。
例:  
与 Logical Tree、Visual Tree 的关系
一般将控件模板作为逻辑树与可视元素树的交界点
只包含控件模板之外元素的树一般视为逻辑树
包含控件模板之外及之内元素的树一般视为可视元素树
ControlTemplate 类
主要属性
TargetType
设置控件模板所针对的控件类型
Triggers
触发器的集合
常用于
ContentControl 控件的 Template 属性
关于
ContentControl 控件的“数据内容”一般由控件模板内安置的 ContentPresenter 元素承载
ItemsControl 控件的 Template 属性
关于
ItemsControl 控件的“数据内容”一般由控件模板内安置的
ItemsPresenter 元素承载
ItemsPresenter 类将根据(ItemsControl 的) ItemsPanel 属性指定的所有条目的布局面板(容器)模板来布局所有条目。若未指定,则使用默认值
例:
<ListBox> <!--设置 ItemsPanel 属性,指定所有条目的面板模板--> <ListBox.ItemsPanel> <ItemsPanelTemplate> <WrapPanel/> </ItemsPanelTemplate> </ListBox.ItemsPanel> <CheckBox Content="CheckBox1"/> <CheckBox Content="CheckBox2"/> <CheckBox Content="CheckBox3"/> <Button Content="Button4"/> <Button Content="Button5"/> <Button Content="Button6"/> </ListBox>
或 条目宿主控件承载
例:
<ListBox> <ListBox.Template> <ControlTemplate TargetType="ListBox"> <ScrollViewer HorizontalScrollBarVisibility="Disabled"> <!-- IsItemsHost 设置为 True 指明该控件为所由条目的宿主(容器)--> <WrapPanel IsItemsHost="True"/> </ScrollViewer> </ControlTemplate> </ListBox.Template> <CheckBox Content="CheckBox1"/> <CheckBox Content="CheckBox2"/> <CheckBox Content="CheckBox3"/> <Button Content="Button4"/> <Button Content="Button5"/> <Button Content="Button6"/> </ListBox>
例:
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <StackPanel> <Button Width="100" Height="50" Content="按钮"> <Button.Template> <!--提示:控件模板更推荐在 Style 里设置--> <!--可以通过 TargetType 属性设置控件模板所针对的控件类型--> <ControlTemplate TargetType="{x:Type Button}"> <Grid> <Ellipse Fill="{TemplateBinding Background}"/> <!--使用 ContentPresenter 元素承载 ContentControl 控件的数据内容--> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/> </Grid> <!--可以通过 Triggers 属性设置控件模板的触发器--> <ControlTemplate.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Aqua"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Button.Template> </Button> <ListBox Width="100" Height="70" Background="Honeydew"> <TextBlock Text="项1"/> <TextBlock Text="项2"/> <TextBlock Text="项3"/> <ListBox.Template> <ControlTemplate TargetType="{x:Type ListBox}"> <Grid> <Rectangle Fill="{TemplateBinding Background}"/> <!--使用 ItemsPresenter 元素承载 ItemsControl 控件的数据内容--> <ItemsPresenter/> </Grid> </ControlTemplate> </ListBox.Template> </ListBox> </StackPanel> </Window>
数据模板
DataTemplate 类
主要属性
DataType
设置数据模板所针对的数据类型
Triggers
触发器的集合
常用于
ContentControl 控件的 ContentTemplate 属性
ItemsControl 控件的 ItemTemplate 属性
GridViewColumn 控件的 CellTemplate 属性
例:
C# 代码
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Media; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } //================ public class ColorInfo : ObservableObject { private string name; public string Name { get { return name; } set { name = value; OnPropertyChanged(); } } private Color color; public Color Color { get { return color; } set { color = value; OnPropertyChanged(); } } private string _value; public string Value { get { return _value; } set { _value = value; OnPropertyChanged(); } } } public class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib"> <Window.Resources> <x:Array x:Key="colorInfos" Type="{x:Type local:ColorInfo}"> <local:ColorInfo Name="红色" Color="#ff0000" Value="#ff0000"/> <local:ColorInfo Name="绿色" Color="#00ff00" Value="#00ff00"/> <local:ColorInfo Name="蓝色" Color="#0000ff" Value="#0000ff"/> </x:Array> </Window.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition Height="100"/> </Grid.RowDefinitions> <ListBox x:Name="listBox1" ItemsSource="{StaticResource colorInfos}" Grid.Column="0" Grid.Row="0" IsSynchronizedWithCurrentItem="True"> <ListBox.ItemTemplate> <!--可以通过 DataType 属性设置数据模板所针对的数据类型--> <DataTemplate DataType="{x:Type local:ColorInfo}"> <TextBlock Text="{Binding Name}"/> <!--可以通过 Triggers 属性设置数据模板的触发器--> <DataTemplate.Triggers> <DataTrigger Binding="{Binding Name}" Value="蓝色"> <Setter Property="ToolTip" Value="蓝色的"/> </DataTrigger> </DataTemplate.Triggers> </DataTemplate> </ListBox.ItemTemplate> </ListBox> <ContentControl Grid.Column="1" Grid.Row="0" Content="{Binding Source={StaticResource colorInfos},Path=/}"> <ContentControl.ContentTemplate> <DataTemplate DataType="{x:Type local:ColorInfo}"> <Grid> <Rectangle> <Rectangle.Fill> <SolidColorBrush Color="{Binding Color}"/> </Rectangle.Fill> </Rectangle> <StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="名称:"/> <TextBlock Text="{Binding Name}"/> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="值:"/> <TextBlock Text="{Binding Value}"/> </StackPanel> </StackPanel> </Grid> </DataTemplate> </ContentControl.ContentTemplate> </ContentControl> <ListView ItemsSource="{StaticResource colorInfos}" Grid.Column="1" Grid.Row="1" IsSynchronizedWithCurrentItem="True"> <ListView.View> <GridView> <GridViewColumn Header="颜色"> <GridViewColumn.CellTemplate> <DataTemplate DataType="{x:Type local:ColorInfo}"> <Rectangle Width="10" Height="10"> <Rectangle.Fill> <SolidColorBrush Color="{Binding Color}"/> </Rectangle.Fill> </Rectangle> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> <GridViewColumn Header="名称"> <GridViewColumn.CellTemplate> <DataTemplate DataType="{x:Type local:ColorInfo}"> <TextBlock Text="{Binding Name}"/> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> <GridViewColumn Header="值"> <GridViewColumn.CellTemplate> <DataTemplate DataType="{x:Type local:ColorInfo}"> <TextBlock Text="{Binding Value}"/> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView> </Grid> </Window>
关于控件的数据模板选择器
自定义选择器
继承 DataTemplateSelector
例:
选择器实现
using System.Windows.Controls; using System.Windows; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Linq; using System; //定义数据模板选择器: //继承 DataTemplateSelector 类,主要重写 SelectTemplate 方法。 public class ShapeDataTemplateSelector : DataTemplateSelector { public string DataConditionPropertyName { get; set; } public List<DictionaryEntry> ConditionTemplates { get; set; } = new List<DictionaryEntry>(); public DataTemplate DefaultTemplate { get; set; } /// <summary> /// 根据数据选择元素的数据模板。 /// </summary> /// <param name="item">数据</param> /// <param name="container">将被应用模板的元素</param> /// <returns>选出的模板</returns> public override DataTemplate SelectTemplate(object item, DependencyObject container) { try { ShapeName data = item as ShapeName; PropertyInfo dataConditionProperty = data.GetType().GetProperty(DataConditionPropertyName); object dataConditionPropertyValue = dataConditionProperty.GetValue(data); if (dataConditionPropertyValue != null) { return ConditionTemplates.Where(kp => kp.Key.Equals(dataConditionPropertyValue)).First().Value as DataTemplate; } return DefaultTemplate; } catch (Exception) { return DefaultTemplate; } } }
C# 代码
using System.Windows; using System.Windows.Controls; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Linq; using System; using System.ComponentModel; using System.Runtime.CompilerServices; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } //================ public class ShapeDataTemplateSelector : DataTemplateSelector { public string DataConditionPropertyName { get; set; } public List<DictionaryEntry> ConditionTemplates { get; set; } = new List<DictionaryEntry>(); public DataTemplate DefaultTemplate { get; set; } public override DataTemplate SelectTemplate(object item, DependencyObject container) { try { ShapeName data = item as ShapeName; PropertyInfo dataConditionProperty = data.GetType().GetProperty(DataConditionPropertyName); object dataConditionPropertyValue = dataConditionProperty.GetValue(data); if (dataConditionPropertyValue != null) { return ConditionTemplates.Where(kp => kp.Key.Equals(dataConditionPropertyValue)).First().Value as DataTemplate; } return DefaultTemplate; } catch (Exception) { return DefaultTemplate; } } } public class ShapeName : ObservableObject { private string name; public string Name { get { return name; } set { name = value; OnPropertyChanged(); } } } public class ObservableObject : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged([CallerMemberName] string name = null) { this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name)); } protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) { this.PropertyChanged?.Invoke(this, e); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:clc="clr-namespace:System.Collections;assembly=mscorlib"> <Window.Resources> <DataTemplate x:Key="rectangleDataTemplate" DataType="{x:Type local:ShapeName}"> <StackPanel> <Rectangle Fill="Blue" Width="100" Height="50"/> <TextBlock Text="{Binding Name}"/> </StackPanel> </DataTemplate> <DataTemplate x:Key="ellipseDataTemplate" DataType="{x:Type local:ShapeName}"> <StackPanel> <Ellipse Fill="Blue" Width="100" Height="50"/> <TextBlock Text="{Binding Name}"/> </StackPanel> </DataTemplate> <local:ShapeDataTemplateSelector x:Key="shapeDataTemplateSelector" DataConditionPropertyName="Name"> <local:ShapeDataTemplateSelector.ConditionTemplates> <clc:DictionaryEntry Key="长方形" Value="{StaticResource rectangleDataTemplate}"/> <clc:DictionaryEntry Key="椭圆形" Value="{StaticResource ellipseDataTemplate}"/> </local:ShapeDataTemplateSelector.ConditionTemplates> </local:ShapeDataTemplateSelector> </Window.Resources> <StackPanel> <!--通过 Button 控件的 ContentTemplateSelector 属性设置控件的数据模板选择器--> <Button ContentTemplateSelector="{StaticResource shapeDataTemplateSelector}"> <Button.Content> <local:ShapeName Name="长方形"/> </Button.Content> </Button> <Button ContentTemplateSelector="{StaticResource shapeDataTemplateSelector}"> <Button.Content> <local:ShapeName Name="椭圆形"/> </Button.Content> </Button> <Button ContentTemplateSelector="{StaticResource shapeDataTemplateSelector}"> <Button.Content> <local:ShapeName Name="未知形状"/> </Button.Content> </Button> </StackPanel> </Window>
样式
相关概念
字面意思
“风格”、“样式”
样式
Style 类
主要属性
TargetType
设置样式所针对的控件类型
设置该属性后,在 XAML 中设置 Setters 或 Triggers 中元素的 Property 属性时,可以不用添加类作用域限定
例:
<Label Content="样式示例"> <Label.Style> <!--Style 未设置 TargetType 属性时:--> <Style> <Style.Triggers> <Trigger Property="Label.IsMouseOver" Value="True"> <Setter Property="Label.Background" Value="CadetBlue"/> </Trigger> </Style.Triggers> </Style> </Label.Style> </Label> <Label Content="样式示例"> <Label.Style> <!--Style 设置 TargetType 属性后:--> <Style TargetType="Label"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Orange"/> </Trigger> </Style.Triggers> </Style> </Label.Style> </Label>
Setters
属性设置器 SetterBase 对象的集合
关于 SetterBase 类
命名空间:System.Windows
主要用于控制控件的静态外观风格
Triggers
条件触发器 TriggerBase 对象的集合
关于 TriggerBase 类
命名空间:System.Windows
主要用于控制控件的动态行为风格
例:
XAML 代码
<Button Content="样式示例"> <Button.Style> <Style TargetType="{x:Type Button}"> <!--属性设置器集合--> <Style.Setters> <Setter Property="Background" Value="Blue"/> </Style.Setters> <!--条件触发器集合--> <Style.Triggers> <Trigger Property="IsPressed" Value="True"> <Setter Property="Foreground" Value="LightYellow"/> </Trigger> </Style.Triggers> </Style> </Button.Style> </Button>
关于
SetterBase
作用
设置指定的属性值
派生类
Setter 类
主要属性
TargetName
此设置器所用于的对象的名称
Property
设置器要设置的(依赖)属性
Value
要对指定的属性设置的值
EventSetter 类
表示要添加样式的事件资源库中的一个事件资源
该事件资源会被添加到 Style 类的一个 EventHandlersStore 类型的字段中
主要属性
Event
设置器要添加到样式事件资源库中的——要响应的(路由)事件
Handler
响应指定事件时的事件处理器
例:
C# 代码
using System.Windows; using System.Windows.Controls; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { MessageBox.Show("Button_Click"); } private void ButtonEventSetter_Click(object sender, RoutedEventArgs e) { MessageBox.Show("ButtonEventSetter_Click"); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel> <Button Content="样式示例" Click="Button_Click"> <Button.Style> <Style TargetType="{x:Type Button}"> <Style.Setters> <Setter Property="Background" Value="Blue"/> <EventSetter Event="Click" Handler="ButtonEventSetter_Click"/> </Style.Setters> </Style> </Button.Style> </Button> </StackPanel> </Window>
TriggerBase
作用
满足条件时触发一个操作(行为)
主要属性
EnterActions
在触发器进入触发状态时执行的触发器操作 TriggerAction 对象的集合
此属性不适用于 EventTrigger 类
关于 TriggerAction 类
命名空间:System.Windows
用于描述触发器触发时执行的操作
派生类
System.Windows.Media.Animation.BeginStoryboard
启动故事板的动画
主要属性
Name
BeginStoryboard 对象名称
Storyboard
要启动的故事板
HandoffBehavior
动画切换行为
类型:HandoffBehavior 枚举
SnapshotAndReplace
新动画将替换它们所应用到的属性上的任何现有动画(有利于性能)
Compose
通过把新动画追加到组合链的末尾来组合新动画和现有动画(过渡更平滑)
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="500" Width="800"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="300"/> <ColumnDefinition Width="300"/> </Grid.ColumnDefinitions> <Button Width="100" Height="100" Content="SnapshotAndReplace" RenderTransformOrigin="0.5,0.5"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="RenderTransform"> <Setter.Value> <ScaleTransform ScaleX="1" ScaleY="1"/> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <BeginStoryboard.Storyboard> <Storyboard> <!--为使效果明显,动画 From 属性使用动画的当前值:--> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" To="3" Duration="0:0:3"/> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" To="3" Duration="0:0:3"/> </Storyboard> </BeginStoryboard.Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <EventTrigger.Actions> <!--BeginStoryboard 对象的 HandoffBehavior 属性设置为 SnapshotAndReplace:--> <BeginStoryboard x:Name="beginStoryboardSnapshotAndReplace" HandoffBehavior="SnapshotAndReplace"> <BeginStoryboard.Storyboard> <Storyboard> <!--为使效果明显,动画 From 属性使用动画的当前值,To 属性使用应用动画前的原始值:--> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" Duration="0:0:3"/> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" Duration="0:0:3"/> </Storyboard> </BeginStoryboard.Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> </Button.Style> </Button> <Button Width="100" Height="100" Grid.Column="1" Content="Compose" RenderTransformOrigin="0.5,0.5"> <Button.Style> <Style TargetType="{x:Type Button}"> <Setter Property="RenderTransform"> <Setter.Value> <ScaleTransform ScaleX="1" ScaleY="1"/> </Setter.Value> </Setter> <Style.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <EventTrigger.Actions> <BeginStoryboard> <BeginStoryboard.Storyboard> <Storyboard> <!--为使效果明显,动画 From 属性使用动画的当前值:--> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" To="3" Duration="0:0:3"/> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" To="3" Duration="0:0:3"/> </Storyboard> </BeginStoryboard.Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <EventTrigger.Actions> <!--BeginStoryboard 对象的 HandoffBehavior 属性设置为 Compose:--> <BeginStoryboard x:Name="beginStoryboardCompose" HandoffBehavior="Compose"> <BeginStoryboard.Storyboard> <Storyboard> <!--为使效果明显,动画 From 属性使用动画的当前值,To 属性使用应用动画前的原始值:--> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleX" Duration="0:0:3"/> <DoubleAnimation Storyboard.TargetProperty="RenderTransform.ScaleY" Duration="0:0:3"/> </Storyboard> </BeginStoryboard.Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> </Button.Style> </Button> </Grid> </Window>
System.Windows.Media.Animation.ControllableStoryboardAction
控制指定的故事板
主要属性
BeginStoryboardName
要控制的 BeginStoryboard 对象名称
派生类
PauseStoryboard
ResumeStoryboard
SetStoryboardSpeedRatio
SeekStoryboard
SkipStoryboardToFill
StopStoryboard
RemoveStoryboard
System.Windows.Controls.SoundPlayerAction
播放音频
主要属性
Source
音频源
ExitActions
在触发器退出触发状态时执行的触发器操作 TriggerAction 对象的集合
此属性不适用于 EventTrigger 类
例:
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel> <Label Content="样式示例" Opacity="0.5" Background="DarkMagenta" Height="50"> <Label.Style> <Style TargetType="{x:Type Label}"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <!--进入触发状态时:--> <Trigger.EnterActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1"/> </Storyboard> </BeginStoryboard> </Trigger.EnterActions> <!--退出触发状态时:--> <Trigger.ExitActions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:1"/> </Storyboard> </BeginStoryboard> </Trigger.ExitActions> </Trigger> </Style.Triggers> </Style> </Label.Style> </Label> </StackPanel> </Window>
派生类
Trigger 类
关注单个属性值的触发器
主要属性
SourceName
此触发器所关注的对象的名称
常用于模板内的触发器
Property
触发器关注的(依赖)属性
Value
满足条件的属性值
Setters
满足条件时触发的设置器集合
不满足条件时,被设置的属性值会被还原
例:
XAML 代码
<Label Content="样式示例"> <Label.Style> <Style TargetType="{x:Type Label}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type Label}"> <StackPanel Orientation="Horizontal"> <Label x:Name="lbl1" Content="{TemplateBinding Content}" Width="100"/> <Label x:Name="lbl2" Width="100"/> </StackPanel> <ControlTemplate.Triggers> <Trigger SourceName="lbl1" Property="IsMouseOver" Value="True"> <Trigger.Setters> <Setter TargetName="lbl2" Property="Background" Value="Orange"/> </Trigger.Setters> </Trigger> </ControlTemplate.Triggers> </ControlTemplate> </Setter.Value> </Setter> </Style> </Label.Style> </Label> <!--使用样式并结合 VisualBrush,实现 TextBox 文本为空时,显示提示:--> <TextBox> <TextBox.Resources> <VisualBrush x:Key="emptyText" AlignmentX="Left" Stretch="None" Opacity="0.3"> <VisualBrush.Visual> <TextBlock Text="请输入"/> </VisualBrush.Visual> </VisualBrush> </TextBox.Resources> <TextBox.Style> <Style TargetType="TextBox"> <Style.Triggers> <Trigger Property="Text" Value="{x:Null}"> <Setter Property="Background" Value="{StaticResource emptyText}"/> </Trigger> <Trigger Property="Text" Value=""> <Setter Property="Background" Value="{StaticResource emptyText}"/> </Trigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox>
DataTrigger 类
关注单个数据值的触发器
主要属性
Binding
用于绑定触发器关注的数据
Value
满足条件的数据值
Setters
满足条件时触发的设置器集合
不满足条件时,被设置的属性值会被还原
例:
XAML 代码
<Label Content="样式示例"> <Label.Style> <Style TargetType="{x:Type Label}"> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=IsMouseOver}" Value="True"> <DataTrigger.Setters> <Setter Property="Background" Value="Orange"/> </DataTrigger.Setters> </DataTrigger> </Style.Triggers> </Style> </Label.Style> </Label>
MultiTrigger 类
关注多个属性值的触发器
主要属性
Conditions
需要满足的条件集合
元素类型 System.Windows.Condition
主要属性
SourceName
Property
Binding
Value
Setters
满足条件时触发的设置器集合
不满足条件时,被设置的属性值会被还原
例:
XAML 代码
<TextBox Text="样式示例"> <TextBox.Style> <Style TargetType="{x:Type TextBox}"> <Style.Triggers> <MultiTrigger> <MultiTrigger.Conditions> <Condition Property="IsMouseOver" Value="True"/> <Condition Property="Text" Value="触发"/> </MultiTrigger.Conditions> <MultiTrigger.Setters> <Setter Property="Background" Value="Orange"/> </MultiTrigger.Setters> </MultiTrigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox>
MultiDataTrigger 类
关注多个数据值的触发器
主要属性
Conditions
需要满足的条件集合
Setters
满足条件时触发的设置器集合
不满足条件时,被设置的属性值会被还原
例:
XAML 代码
<TextBox Text="样式示例"> <TextBox.Style> <Style TargetType="{x:Type TextBox}"> <Style.Triggers> <MultiDataTrigger> <MultiDataTrigger.Conditions> <Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=IsMouseOver}" Value="True"/> <Condition Binding="{Binding RelativeSource={RelativeSource Mode=Self},Path=Text}" Value="触发"/> </MultiDataTrigger.Conditions> <MultiDataTrigger.Setters> <Setter Property="Background" Value="Orange"/> </MultiDataTrigger.Setters> </MultiDataTrigger> </Style.Triggers> </Style> </TextBox.Style> </TextBox>
EventTrigger 类
关注事件的触发器
主要属性
SourceName
此触发器所关注的对象的名称
一般用于模板或元素内的触发器
RoutedEvent
触发器关注的(路由)事件
Actions
在触发器被触发时执行的触发器操作 TriggerAction 对象的集合
例:
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <StackPanel> <Label Content="样式示例" Opacity="0.5" Background="DarkMagenta" Height="50"> <Label.Style> <Style TargetType="{x:Type Label}"> <Style.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <!--被 MouseEnter 事件触发时:--> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:1"/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <!--被 MouseLeave 事件触发时:--> <EventTrigger.Actions> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetProperty="Opacity" To="0.5" Duration="0:0:1"/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> </Style.Triggers> </Style> </Label.Style> </Label> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="300"/> <RowDefinition /> </Grid.RowDefinitions> <TextBlock Text="动画控制演示:" Grid.Column="0" Grid.Row="0"/> <Rectangle x:Name="rectangle" Fill="CornflowerBlue" Width="0" HorizontalAlignment="Left" Grid.Column="1" Grid.Row="0"/> <StackPanel Orientation="Horizontal" Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="2"> <Button x:Name="beginButton" Content="开始" Width="90"/> <Button x:Name="pauseButton" Content="暂停" Width="90"/> <Button x:Name="resumeButton" Content="继续" Width="90"/> <StackPanel.Triggers> <!--关注 beginButton 对象事件的触发器:--> <EventTrigger SourceName="beginButton" RoutedEvent="Button.Click"> <EventTrigger.Actions> <BeginStoryboard x:Name="beginStoryboard"> <Storyboard> <DoubleAnimation Storyboard.TargetName="rectangle" Storyboard.TargetProperty="Width" From="0" To="500" Duration="0:0:5"/> </Storyboard> </BeginStoryboard> </EventTrigger.Actions> </EventTrigger> <!--关注 pauseButton 对象事件的触发器:--> <EventTrigger SourceName="pauseButton" RoutedEvent="Button.Click"> <EventTrigger.Actions> <PauseStoryboard BeginStoryboardName="beginStoryboard"/> </EventTrigger.Actions> </EventTrigger> <!--关注 resumeButton 对象事件的触发器:--> <EventTrigger SourceName="resumeButton" RoutedEvent="Button.Click"> <EventTrigger.Actions> <ResumeStoryboard BeginStoryboardName="beginStoryboard"/> </EventTrigger.Actions> </EventTrigger> </StackPanel.Triggers> </StackPanel> </Grid> </StackPanel> </Window>
扩展内容
Interactivity
交互行为扩展
使用步骤
(1)为工程安装 NuGet 包:System.Windows.Interactivity.WPF
(2)根据需要引用命名空间
XAML 中的引用
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
Interaction 类
一个拥有行为、触发器集合附加属性的静态类。可以处理 AssociatedObject 更改通知的传播
AssociatedObject 一般代表被附加属性的对象。
主要属性
Behaviors
行为 Behavior 对象的集合
关于 Behavior 类
命名空间:System.Windows.Interactivity
例:
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"> <StackPanel> <!--Interaction.Behaviors 示例:--> <Label Content="拖动测试" Background="AliceBlue" Width="100" Height="50"> <i:Interaction.Behaviors> <ei:MouseDragElementBehavior/> </i:Interaction.Behaviors> </Label> </StackPanel> </Window>
Triggers
条件触发器 TriggerBase 对象的集合
关于 TriggerBase 类
命名空间:System.Windows.Interactivity
例:
C# 代码
using System.ComponentModel; using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Input; namespace WpfApp1 { public partial class MainWindow : Window { public static readonly string CMDPARAM1 = "清空按钮1"; public static readonly string CMDPARAM2 = "清空按钮2"; public MainWindow() { InitializeComponent(); } void CmdCanExecuteHandler(object sender, CanExecuteRoutedEventArgs e) { if (e.Parameter?.ToString() == CMDPARAM1) { if (string.IsNullOrEmpty(txtText1.Text)) { e.CanExecute = false; } else { e.CanExecute = true; } } else if (e.Parameter?.ToString() == CMDPARAM2) { if (string.IsNullOrEmpty(txtText2.Text)) { e.CanExecute = false; } else { e.CanExecute = true; } } } void CmdExecutedHandler(object sender, ExecutedRoutedEventArgs e) { if (e.Parameter.ToString() == CMDPARAM1) { txtText1.Clear(); } else if (e.Parameter.ToString() == CMDPARAM2) { txtText2.Clear(); } } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800" xmlns:sys="clr-namespace:System;assembly=mscorlib" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"> <Window.Resources> <RoutedCommand x:Key="clearCmd"/> </Window.Resources> <Grid x:Name="gridRoot"> <Grid x:Name="grid"> <Grid.ColumnDefinitions> <ColumnDefinition/> <ColumnDefinition/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition/> <RowDefinition/> </Grid.RowDefinitions> <TextBox x:Name="txtText1"/> <Button x:Name="btnClear1" Content="清空源1" Grid.Column="1"> <!--Interaction.Triggers 示例:--> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <i:InvokeCommandAction Command="{StaticResource clearCmd}" CommandParameter="{x:Static local:MainWindow.CMDPARAM1}"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> <TextBox x:Name="txtText2" Grid.Row="1"/> <Button x:Name="btnClear2" Content="清空源2" Grid.Column="1" Grid.Row="1"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <i:InvokeCommandAction Command="{StaticResource clearCmd}" CommandParameter="{x:Static local:MainWindow.CMDPARAM2}"/> </i:EventTrigger> </i:Interaction.Triggers> </Button> <Grid.CommandBindings> <CommandBinding Command="{StaticResource clearCmd}" CanExecute="CmdCanExecuteHandler" Executed="CmdExecutedHandler"/> </Grid.CommandBindings> </Grid> </Grid> </Window>
10. 绘图和动画
绘图
形状
Shape 抽象类
命名空间:System.Windows.Shapes
基类:System.Windows.FrameworkElement
可以作为独立的 UI 元素
主要属性
Fill
填充:设置绘制形状内部的 Brush
Stroke
笔触:设置绘制形状轮廓的 Brush
StrokeThickness
轮廓的宽度(double 值)
StrokeStartLineCap
设置(非闭合)轮廓的起始端形状
类型:PenLineCap 枚举
Flat
一个未超出直线上最后一点的线帽。 等同于无线帽
Triangle
一个底边长度等于直线粗细的等腰直角三角形
Round
一个直径等于直线粗细的半圆形
Square
一个高度等于直线粗细、长度等于直线粗细一半的矩形
StrokeEndLineCap
设置(非闭合)轮廓的结束端形状
StrokeDashArray
设置轮廓线间距样式(虚线效果)
类型:Double 值集合
集合中的每个数值指定数倍于笔的 Thickness 的长度
关于 Thickness 结构体
Left
设置矩形左边框宽度
Top
设置矩形顶边框宽度
Right
设置矩形右边框宽度
Bottom
设置矩形底边框宽度
索引值为偶数的数值指定短划线长度,索引值为奇数的数值指定间隙长度
StrokeDashOffset
设置虚线绘制的起始偏移
正值效果
向左或逆时针等负方向偏移
偏移效果可以按照“原有绘制位置 - 偏移值”得到的最终绘制位置理解。
负值效果
向右或顺时针等正方向偏移
StrokeDashCap
设置虚线两端的形状
StrokeLineJoin
设置轮廓顶点的形状
类型:PenLineJoin 枚举
Miter
常规角顶点
Round
圆角顶点
Bevel
斜角顶点
Stretch
设置形状如何填充其容器
类型:Stretch 枚举
None
内容保持其原始大小
Uniform
按内容原始纵横比调整内容的大小,以适合目标尺寸
UniformToFill
按内容原始纵横比调整内容的大小,以填充目标尺寸。 超出部分会被剪裁
Fill
调整内容的大小以填充目标尺寸。 不保留纵横比
DefiningGeometry
获取 Shape 的 Geometry
GeometryTransform
获取在绘制 Shape 之前应用于其几何图形的 Transform
RenderedGeometry
获取 Shape 的最终呈现的 Geometry
Shape 派生类
Line
直线
主要属性
X1、Y1
起点坐标
X2、Y2
终点坐标
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Line X1="10" Y1="20" X2="600" Y2="20" Stroke="Blue" StrokeThickness="10" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeDashArray="1,2,3,4,5,6,7,8" StrokeDashOffset="-1" StrokeDashCap="Triangle"/> <Line X1="10" Y1="200" X2="600" Y2="200" StrokeThickness="300"> <Line.Stroke> <LinearGradientBrush StartPoint="0,0" EndPoint="1,0.5"> <GradientStop Color="Blue" Offset="0"/> <GradientStop Color="Green" Offset="0.5"/> <GradientStop Color="Red" Offset="1"/> </LinearGradientBrush> </Line.Stroke> </Line> </Grid> </Window>
Polyline
折线
主要属性
Points
顶点集合
例:
<!--顶点集合可用字符串形式赋值,以逗号“,”分隔顶点坐标的X、Y值,以空格“ ”分隔每个坐标:--> <Polyline Points="10,10 20,50 50,5 100,100" Stroke="DarkMagenta" StrokeThickness="2"/>
Rectangle
矩形
例:
<!--其中,RadiusX 设置轮廓顶点圆角椭圆 X 轴半径,RadiusY 设置轮廓顶点圆角椭圆 Y 轴半径:--> <Rectangle Width="200" Height="100" Fill="AliceBlue" Stroke="DarkViolet" RadiusX="20" RadiusY="10"/>
Polygon
多边型
主要属性
Points
顶点集合
FillRule
形状内部填充规则
例:
<Polygon Points="10,10 20,50 50,5 100,100" Stroke="DarkMagenta" StrokeThickness="2" Fill="Cyan" FillRule="EvenOdd"/>
Ellipse
椭圆
例:
<Ellipse Width="200" Height="100" Fill="AliceBlue" Stroke="DarkViolet"/>
Path
路径
主要属性
Data
设置要绘制的几何图形
类型:System.Windows.Media.Geometry
赋值方式
使用属性元素
例:
<Path Stroke="Black" StrokeThickness="1" > <Path.Data> <LineGeometry StartPoint="10,20" EndPoint="100,130" /> </Path.Data> </Path>
使用 StreamGeometry mini-language 路径标记语法
例:
<Path Stroke="Black" Fill="BlueViolet" Data="F0 M 10,100 C 10,300 300,-200 300,100" />
关于路径标记语法
语法格式
"[填充规则] 移动命令 绘制命令[ 绘制命令...][ 关闭命令][ 移动命令 绘制命令[ 绘制命令...][ 关闭命令]...]"
说明
注:关键字一般不区分大小写。
填充规则
StreamGeometry mini-language 可选该部分
值
F0 指定 EvenOdd 填充规则(默认)
F1 指定 Nonzero 填充规则
绘制描述
几何图形
基类
Geometry
命名空间:System.Windows.Media
不可以作为独立的 UI 元素
派生类
LineGeometry
直线几何图形
主要属性
StartPoint
直线起点坐标
EndPoint
直线终点坐标
例:
<Path Stroke="Black" StrokeThickness="1" > <Path.Data> <LineGeometry StartPoint="10,20" EndPoint="100,130" /> </Path.Data> </Path>
RectangleGeometry
矩形几何图形
主要属性
Rect
矩形的尺寸
例:
<Path Stroke="Black" StrokeThickness="1" > <Path.Data> <RectangleGeometry Rect="0,0 100,50"/> </Path.Data> </Path>
EllipseGeometry
椭圆几何图形
主要属性
Center
椭圆中心坐标
RadiusX
椭圆 X 轴半径
RadiusY
椭圆 Y 轴半径
例:图形剪裁
<Grid Background="Aqua"> <Rectangle Width="200" Height="150" Stroke="DarkOrange" StrokeThickness="3"> <Rectangle.Fill> <VisualBrush Stretch="Fill"> <VisualBrush.Visual> <Button Content="图形"/> </VisualBrush.Visual> </VisualBrush> </Rectangle.Fill> <!--通过设置 UIElement.Clip 属性(类型:System.Windows.Media.Geometry),使用 Clip 属性的剪裁几何图形控制元素显示出的区域。位于剪裁几何图形外的图形会被剪除:--> <Rectangle.Clip> <EllipseGeometry x:Name="clip1" RadiusX="100" RadiusY="75" Center="100,75"/> </Rectangle.Clip> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.Loaded"> <BeginStoryboard> <Storyboard> <PointAnimation Storyboard.TargetName="clip1" Storyboard.TargetProperty="(EllipseGeometry.Center)" From="0,0" To="200,150" Duration="0:0:3" RepeatBehavior="Forever" AutoReverse="True"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Rectangle.Triggers> </Rectangle> </Grid>
CombinedGeometry
由两个 Geometry 组合成的几何图形
主要属性
Geometry1
第1个 Geometry 对象
Geometry2
第2个 Geometry 对象
GeometryCombineMode
Geometry 组合模式
类型:GeometryCombineMode 枚举
Union
并集组合,按 A∪B 运算
Intersect
交集组合,按 A∩B 运算
Exclude
排除组合,按 A-B 运算
Xor
异或组合,按 (A-B)+(B-A) 运算
例:
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF"> <Path.Data> <CombinedGeometry GeometryCombineMode="Xor"> <CombinedGeometry.Geometry1> <EllipseGeometry RadiusX="50" RadiusY="50" Center="75,75" /> </CombinedGeometry.Geometry1> <CombinedGeometry.Geometry2> <EllipseGeometry RadiusX="50" RadiusY="50" Center="125,75" /> </CombinedGeometry.Geometry2> </CombinedGeometry> </Path.Data> </Path>
PathGeometry
路径几何图形
主要属性
Figures
路径图形集合
类型:PathFigureCollection
元素类型:PathFigure
命名空间:System.Windows.Media
路径图形
主要属性
Segments
线段集合
类型:PathSegmentCollection
元素类型:PathSegment
命名空间:System.Windows.Media
路径线段
派生类
LineSegment
直线段
主要属性
Point
线段终点
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry FillRule="EvenOdd"> <PathGeometry.Figures> <PathFigureCollection> <PathFigure StartPoint="10,10" IsClosed="False" IsFilled="False"> <PathFigure.Segments> <PathSegmentCollection> <LineSegment Point="20,40"/> <LineSegment Point="40,110"/> <LineSegment Point="50,20"/> </PathSegmentCollection> </PathFigure.Segments> </PathFigure> <PathFigure StartPoint="110,110" IsClosed="False" IsFilled="True"> <PathFigure.Segments> <PathSegmentCollection> <LineSegment Point="120,40"/> <LineSegment Point="140,150"/> <LineSegment Point="150,20"/> </PathSegmentCollection> </PathFigure.Segments> </PathFigure> </PathFigureCollection> </PathGeometry.Figures> </PathGeometry> </Path.Data> </Path>
PolyLineSegment
多直线段
主要属性
Points
线段终点集合
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry> <PathFigure StartPoint="10,10"> <PathFigure.Segments> <PolyLineSegment Points="30,60 30,90 90,130"/> </PathFigure.Segments> </PathFigure> </PathGeometry> </Path.Data> </Path>
ArcSegment
圆弧线段
主要属性
Size
圆弧的 X、Y 轴半径尺寸
RotationAngle
旋转度数
IsLargeArc
是否大弧
SweepDirection
弧的绘制方向
Point
圆弧终点
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry> <PathFigure StartPoint="200,200"> <ArcSegment Size="170,100" RotationAngle="10" IsLargeArc="True" SweepDirection="Clockwise" Point="500,200"/> </PathFigure> </PathGeometry> </Path.Data> </Path>
QuadraticBezierSegment
二次方贝塞尔曲线段
主要属性
Point1
控制点1
Point2
曲线终点
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry> <PathFigure StartPoint="200,200"> <QuadraticBezierSegment Point1="110,50" Point2="500,300"/> </PathFigure> </PathGeometry> </Path.Data> </Path>
BezierSegment
三次方贝塞尔曲线段
主要属性
Point1
控制点1
Point2
控制点2
Point2
曲线终点
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry> <PathFigure StartPoint="200,200"> <BezierSegment Point1="210,5" Point2="700,500" Point3="600,60"/> </PathFigure> </PathGeometry> </Path.Data> </Path>
PolyQuadraticBezierSegment
多个二次方贝塞尔曲线段
主要属性
Points
点集合
按“控制点1”、“(终)端点”类推依次排列
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry> <PathFigure StartPoint="10,100"> <PolyQuadraticBezierSegment Points="30,200 60,90 80,10 200,110" /> </PathFigure> </PathGeometry> </Path.Data> </Path>
PolyBezierSegment
多个三次方贝塞尔曲线段
主要属性
Points
点集合
按“控制点1”、“控制点2”、“(终)端点”类推依次排列
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry> <PathFigure StartPoint="10,100"> <PolyBezierSegment Points="0,0 200,200 270,130 350,-200 400,500 600,200"/> </PathFigure> </PathGeometry> </Path.Data> </Path>
StartPoint
图形起始点
后续线段的从前一个线段的终点起始
IsClosed
是否要闭合
IsFilled
设置该路径图形包含的区域是否用于呈现、剪裁和命中测试
赋值方式
使用属性元素
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry> <PathFigure StartPoint="200,200"> <ArcSegment Size="170,100" RotationAngle="10" IsLargeArc="True" SweepDirection="Clockwise" Point="500,200"/> </PathFigure> </PathGeometry> </Path.Data> </Path>
使用 PathFigureCollection mini-language 路径标记语法
例:
<Path Stroke="Black" StrokeThickness="1" Fill="Aqua"> <Path.Data> <PathGeometry Figures="M 200,200 A 170,100 10 1 1 500,200"/> </Path.Data> </Path>
FillRule
图形内部填充规则
StreamGeometry
PathGeometry 的轻量级替代
通过路径标记语法直接指定 Path 对象的 Data 属性时,就是用的该类型
GeometryGroup
由 Geometry 对象组成的复合几何图形
主要属性
Children
Geometry 集合
FillRule
图形内部填充规则
颜色
结构体
System.Windows.Media.Color
预定义颜色
类
System.Windows.Media.Colors
System.Windows.SystemColors
画刷
基类
Brush
命名空间:System.Windows.Media
Brush 派生类
SolidColorBrush
纯色画刷
主要属性
Color
颜色
类型:System.Windows.Media.Color
GradientBrush 抽象类
渐变画刷
主要属性
GradientStops
渐变停止点集合
元素类型:GradientStop
主要属性
Color
颜色
类型:System.Windows.Media.Color
Offset
渐变停止点在渐变向量中的位置
0.0 表示位于开始处;1.0 表示位于末尾处
MappingMode
设置画刷定位坐标相对于输出区域是绝对的还是相对的
类型:BrushMappingMode 枚举
RelativeToBoundingBox
(默认)坐标与边界框相关:0 指示边界框的 0%,1 指示边界框的 100%
Absolute
坐标与边界框无关。 值直接在本地坐标系中解释
派生类
LinearGradientBrush
线性渐变画刷
主要属性
StartPoint
渐变的起始坐标
类型:Point
EndPoint
渐变的终点坐标
类型:Point
例:
<Rectangle Width="200" Height="100"> <Rectangle.Fill> <LinearGradientBrush StartPoint="0,0" EndPoint="1,1"> <GradientStop Color="Yellow" Offset="0.0" /> <GradientStop Color="Red" Offset="0.25" /> <GradientStop Color="Blue" Offset="0.75" /> <GradientStop Color="LimeGreen" Offset="1.0" /> </LinearGradientBrush> </Rectangle.Fill> </Rectangle>
RadialGradientBrush
径向渐变画刷
主要属性
GradientOrigin
渐变焦点坐标
Center
渐变圆中心坐标
RadiusX
渐变圆 X 轴半径
RadiusY
渐变圆 Y 轴半径
例:
<Rectangle Width="200" Height="100"> <Rectangle.Fill> <RadialGradientBrush GradientOrigin="0.3,0.5" Center="0.7,0.5" RadiusX="0.5" RadiusY="0.4"> <RadialGradientBrush.GradientStops> <GradientStop Color="Yellow" Offset="0" /> <GradientStop Color="Red" Offset="0.25" /> <GradientStop Color="Blue" Offset="0.75" /> <GradientStop Color="LimeGreen" Offset="1" /> </RadialGradientBrush.GradientStops> </RadialGradientBrush> </Rectangle.Fill> </Rectangle>
TileBrush 抽象类
图块画刷
主要属性
Stretch
设置内容如何填充画刷的基本图块
AlignmentX
设置基本图块中内容的水平对齐方式
AlignmentY
设置基本图块中内容的垂直对齐方式
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid Width="200" Height="200" > <Rectangle Stroke="BurlyWood" StrokeThickness="2"> <Rectangle.Fill> <VisualBrush AlignmentX="Left" AlignmentY="Center" Stretch="None"> <VisualBrush.Visual> <StackPanel Background="WhiteSmoke"> <Line X1="1" Y1="1" X2="60" Y2="1" Stroke="DeepSkyBlue" StrokeThickness="2"/> <Rectangle Width="35" Height="15" Fill="BurlyWood"/> <TextBlock Text="Hello, World!"/> <Button Content="按钮"/> </StackPanel> </VisualBrush.Visual> </VisualBrush> </Rectangle.Fill> </Rectangle> <Line X1="0" Y1="100" X2="200" Y2="100" Stroke="Black" StrokeThickness="1"/> <Line X1="100" Y1="0" X2="100" Y2="200" Stroke="Black" StrokeThickness="1"/> </Grid> </Window>
Viewbox
设置用作呈现的内容区域在内容区域中的位置与大小
类型:System.Windows.Rect
ViewboxUnits
设置内容呈现区域的位置与大小相对于内容区域的是绝对的还是相对的
Viewport
设置画刷的基本图块在画刷输出区域中的位置与大小
类型:System.Windows.Rect
ViewportUnits
设置基本图块的位置与大小相对于输出区域的是绝对的还是相对的
TileMode
设置基本图块在输出区域中平铺方式
类型:TileMode 枚举
None
绘制基本图块,但不重复基本图块。 其他区域是透明的
Tile
先绘制基本图块,然后通过重复基本图块来填充其他区域
FlipX
主要效果与 Tile 相同,只不过图块的交替列被水平翻转
FlipY
主要效果与 Tile 相同,只不过图块的交替行被垂直翻转
FlipXY
FlipX 和 FlipY 效果的组合
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid Width="200" Height="200" > <Rectangle Stroke="BurlyWood" StrokeThickness="2"> <Rectangle.Fill> <VisualBrush Stretch="Fill" Viewbox="0.2,0.1,1.3,0.7" ViewboxUnits="RelativeToBoundingBox" Viewport="0.1,0.1,0.2,0.3" ViewportUnits="RelativeToBoundingBox" TileMode="Tile"> <VisualBrush.Visual> <StackPanel Background="WhiteSmoke"> <Line X1="1" Y1="1" X2="60" Y2="1" Stroke="DeepSkyBlue" StrokeThickness="2"/> <Rectangle Width="35" Height="15" Fill="BurlyWood"/> <TextBlock Text="Hello, World!"/> <Button Content="按钮"/> </StackPanel> </VisualBrush.Visual> </VisualBrush> </Rectangle.Fill> </Rectangle> <Line X1="0" Y1="100" X2="200" Y2="100" Stroke="Black" StrokeThickness="1"/> <Line X1="100" Y1="0" X2="100" Y2="200" Stroke="Black" StrokeThickness="1"/> </Grid> </Window>
派生类
ImageBrush
图形画刷
主要属性
ImageSource
图像内容
类型:System.Windows.Media.ImageSource
DrawingBrush
绘图画刷
主要属性
Drawing
绘图内容
类型:System.Windows.Media.Drawing
例:
<Rectangle Width="150" Height="150" Stroke="CadetBlue" StrokeThickness="1"> <Rectangle.Fill> <DrawingBrush Stretch="None"> <DrawingBrush.Drawing> <GeometryDrawing Brush="MediumBlue"> <GeometryDrawing.Geometry> <GeometryGroup> <EllipseGeometry RadiusX="45" RadiusY="20"/> <EllipseGeometry RadiusX="20" RadiusY="45"/> </GeometryGroup> </GeometryDrawing.Geometry> <GeometryDrawing.Pen> <Pen Thickness="10"> <Pen.Brush> <LinearGradientBrush> <GradientStop Color="BlueViolet" Offset="0.0"/> <GradientStop Color="Honeydew" Offset="1.0"/> </LinearGradientBrush> </Pen.Brush> </Pen> </GeometryDrawing.Pen> </GeometryDrawing> </DrawingBrush.Drawing> </DrawingBrush> </Rectangle.Fill> </Rectangle>
VisualBrush
可视元素画刷
主要属性
Visual
可视元素内容
类型:System.Windows.Media.Visual
例:
<Rectangle Width="150" Height="150" Stroke="Black" StrokeThickness="3"> <Rectangle.Fill> <VisualBrush Stretch="None"> <VisualBrush.Visual> <StackPanel Background="WhiteSmoke"> <Rectangle Width="25" Height="25" Fill="YellowGreen"/> <TextBlock Text="Hello, World!"/> <Button Content="按钮"/> </StackPanel> </VisualBrush.Visual> </VisualBrush> </Rectangle.Fill> </Rectangle> 扩展: <Grid Background="SkyBlue"> <Grid.RowDefinitions> <RowDefinition Height="30"/> <RowDefinition Height="30"/> </Grid.RowDefinitions> <TextBlock x:Name="txt" Text="倒影效果" FontSize="20" Width="100" Background="LightSteelBlue"/> <Rectangle Grid.Row="1" Width="100"> <Rectangle.Fill> <VisualBrush Visual="{Binding ElementName=txt}"/> </Rectangle.Fill> <Rectangle.RenderTransform> <ScaleTransform ScaleX="1" ScaleY="-1"/> </Rectangle.RenderTransform> <Rectangle.RenderTransformOrigin> <Point X="1" Y="0.5"/> </Rectangle.RenderTransformOrigin> <!--借助 UIElement.OpacityMask 蒙板属性(类型:System.Windows.Media.Brush)(由画刷的 Alpha 通道值决定对应位置的透明程度),实现倒影效果:--> <Rectangle.OpacityMask> <LinearGradientBrush StartPoint="0,0" EndPoint="0,1"> <GradientStop Offset="0.3" Color="Transparent"/> <GradientStop Offset="1" Color="#99000000"/> </LinearGradientBrush> </Rectangle.OpacityMask> </Rectangle> </Grid>
BitmapCacheBrush
位图缓存画刷
变换
Transform 类
2D 变换
命名空间:System.Windows.Media
派生类
TranslateTransform
平移变换
主要属性
X
沿 X 轴平移距离
Y
沿 Y 轴平移距离
ScaleTransform
缩放变换
主要属性
ScaleX
X 轴缩放比例
ScaleY
Y 轴缩放比例
CenterX
变换中心点 X 坐标
CenterY
变换中心点 Y 坐标
RotateTransform
旋转变换
主要属性
Angle
顺时针旋转角度
CenterX
变换中心点 X 坐标
CenterY
变换中心点 Y 坐标
例:
呈现变换
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Rectangle Height="50" Width="150" Stroke="Blue" StrokeThickness="2" Fill="#CCCCCCFF"> <!--通过 UIElement.RenderTransform 属性设置呈现变换:--> <Rectangle.RenderTransform> <!--旋转变换:--> <RotateTransform Angle="20" CenterX="0" CenterY="0"/> </Rectangle.RenderTransform> <!--通过 UIElement.RenderTransformOrigin 属性设置相对于元素区域的变换中心点(使用时比在变换对象上设置中心点方便):--> <Rectangle.RenderTransformOrigin> <Point X="0.5" Y="0.5"/> </Rectangle.RenderTransformOrigin> </Rectangle> </Grid> </Window>
布局变换
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"/> <ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid Background="CornflowerBlue"> <TextBlock Text="旋转变换"> <!--通过 FrameworkElement.LayoutTransform 属性设置布局变换:--> <TextBlock.LayoutTransform> <!--旋转变换:--> <RotateTransform Angle="70"/> </TextBlock.LayoutTransform> </TextBlock> </Grid> </Grid> </Window>
SkewTransform
扭曲变换
主要属性
AngleX
作用于 X 轴(水平)方向上的(逆时针)扭曲角度
AngleY
作用于 Y 轴(垂直)方向上的(顺时针)扭曲角度
CenterX
变换中心点的 X 坐标
CenterY
变换中心点的 Y 坐标
MatrixTransform
仿射矩阵变换
主要属性
Matrix
变换矩阵结构体
TransformGroup
由 Transform 对象组成的复合 Transform
主要属性
Children
Transform 对象集合
效果
类
Effect
效果抽象类
命名空间:System.Windows.Media.Effects
派生类
BlurEffect
模糊效果
主要属性
Radius
模糊效果曲线的半径
KernelType
创建效果的内核类型
类型:KernelType 枚举
Gaussian
为模糊创建平滑分布的分布曲线
Box
用平直分布曲线创建的简单模糊
RenderingBias
效果呈现质量
类型:RenderingBias 枚举
Performance
注重性能
Quality
注重质量
例:
<Button Content="模糊效果" Width="150" Height="50"> <Button.Effect> <BlurEffect Radius="5" KernelType="Gaussian" RenderingBias="Performance"/> </Button.Effect> </Button>
DropShadowEffect
阴影效果
主要属性
Color
阴影颜色
Direction
阴影投影方向度数
Opacity
阴影不透明度
ShadowDepth
阴影投影深度
BlurRadius
阴影模糊效果曲线的半径
RenderingBias
效果呈现质量
类型:RenderingBias 枚举
Performance
注重性能
Quality
注重质量
例:
<Button Content="阴影效果" Width="150" Height="50"> <Button.Effect> <DropShadowEffect Color="Black" Direction="315" Opacity="0.5" ShadowDepth="5" BlurRadius="5" RenderingBias="Performance"/> </Button.Effect> </Button>
ShaderEffect
着色器效果(抽象类)
主要属性
PixelShader
像素着色器
使用
一般用于 UIElement.Effect 属性
类型:System.Windows.Media.Effects.Effect
例:
<Button Content="效果" Width="150" Height="50"> <Button.Effect> <BlurEffect Radius="5" KernelType="Gaussian" RenderingBias="Performance"/> </Button.Effect> </Button>
动画
相关概念
本质是在一个时间段内,让(UI 元素对象的)(依赖)属性值连续变化
类
Timeline
时间线
命名空间:System.Windows.Media.Animation
主要属性
BeginTime
时间线开始前的延迟
类型:System.Nullable<TimeSpan>
XAML 赋值格式
BeginTime="[-][天.]小时:分钟:秒[.秒的小数部分]" 或 BeginTime="{x:Null}"
Duration
时间线从开始到结束的活动持续时间
类型:System.Windows.Duration
XAML 赋值格式
Duration="[天.]小时:分钟:秒[.秒的小数部分]" 或 Duration="Automatic" 或 Duration="Forever"
SpeedRatio
时间线相对于原来前进速率的新(平均)前进速率
AccelerationRatio
时间线前进速率从 0 加速到最大所用时间占 Duration 的比率
DecelerationRatio
时间线前进速率从最大减速到 0 所用时间占 Duration 的比率
AutoReverse
指示时间线在完成向前迭代后是否按相反的顺序播放
RepeatBehavior
时间线重复行为
类型:System.Windows.Media.Animation.RepeatBehavior
XAML 赋值格式
RepeatBehavior="[天.]小时:分钟:秒[.秒的小数部分]" 或 RepeatBehavior="次数(Double类型)x" 或 RepeatBehavior="Forever"
FillBehavior
指定时间线在活动周期结束后的行为方式
类型:System.Windows.Media.Animation.FillBehavior 枚举
HoldEnd
时间线活动期结束后保持其进度
Stop
时间线活动期结束后不保持其进度
DesiredFrameRate 附加属性
设置在此时间线及其子时间线上期望的运行帧速率
注:只能在根时间线上设置此属性。
类型:System.Nullable<Int32>
例:
C# 代码实现版本
C# 代码
using System.Windows; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Button_Click(object sender, RoutedEventArgs e) { System.Windows.Media.Animation.DoubleAnimation doubleAnimation = new System.Windows.Media.Animation.DoubleAnimation(); doubleAnimation.From = 0; doubleAnimation.To = 300; doubleAnimation.BeginTime = new System.TimeSpan(0, 0, 0); doubleAnimation.Duration = new Duration(new System.TimeSpan(0, 0, 2)); doubleAnimation.SpeedRatio = 1; doubleAnimation.AccelerationRatio = 0.3; doubleAnimation.DecelerationRatio = 0.3; doubleAnimation.AutoReverse = true; doubleAnimation.RepeatBehavior = new System.Windows.Media.Animation.RepeatBehavior(2.3); doubleAnimation.FillBehavior = System.Windows.Media.Animation.FillBehavior.HoldEnd; System.Windows.Media.Animation.Timeline.SetDesiredFrameRate(doubleAnimation, 60); //System.Windows.Media.Animation.Storyboard.SetTargetName(doubleAnimation, nameof(translateTransform1)); //System.Windows.Media.Animation.Storyboard.SetTargetProperty(doubleAnimation, new PropertyPath(System.Windows.Media.TranslateTransform.XProperty)); //调用动画主体对象“translateTransform1”从 Animatable 类继承的 BeginAnimation(~) 方法,对指定属性应用动画 translateTransform1.BeginAnimation(System.Windows.Media.TranslateTransform.XProperty, doubleAnimation); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Button Width="150" Height="50" Content="动画" HorizontalAlignment="Left" VerticalAlignment="Top" Click="Button_Click"> <Button.RenderTransform> <TranslateTransform x:Name="translateTransform1"/> </Button.RenderTransform> </Button> </Grid> </Window>
XAML 代码实现版本
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Button Width="150" Height="50" Content="动画" HorizontalAlignment="Left" VerticalAlignment="Top"> <Button.RenderTransform> <TranslateTransform x:Name="translateTransform1"/> </Button.RenderTransform> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <!--借助 EventTrigger 对象的触发操作,让 BeginStoryboard 对象启动动画:--> <BeginStoryboard> <Storyboard Timeline.DesiredFrameRate="60"> <DoubleAnimation Storyboard.TargetName="translateTransform1" Storyboard.TargetProperty="X" From="0" To="300" BeginTime="0:0:0" Duration="0:0:2" SpeedRatio="1" AccelerationRatio="0.3" DecelerationRatio="0.3" AutoReverse="True" RepeatBehavior="2.3x" FillBehavior="HoldEnd"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> <!--若需要提高呈现比较耗时的内容的(动画)呈现性能,可以通过 UIElement.CacheMode 属性设置一个位图缓存:--> <Button.CacheMode> <BitmapCache RenderAtScale="1"/> </Button.CacheMode> </Button> </Grid> </Window>
派生类
AnimationTimeline
时间线动画
命名空间:System.Windows.Media.Animation
派生类
各种类型值动画的基类
一般的类名规则:类型名AnimationBase
派生类
一般有
插值动画类
一般的类名规则:类型名Animation
关键帧动画类
一般的类名规则:类型名AnimationUsingKeyFrames
路径动画类
一般的类名规则:类型名AnimationUsingPath
例如:DoubleAnimationBase
双精度浮点数动画基类
派生类
DoubleAnimation
双精度浮点数插值动画
主要属性
From
动画的起始值
未指定时使用动画的当前值
To
注:使用时优先于 By 属性。
动画的结束值
未指定时使用应用动画前的原始值
By
动画基于当前值的增量值
EasingFunction
缓动方法
类型:IEasingFunction
命名空间:System.Windows.Media.Animation
实现类
EasingFunctionBase
命名空间:System.Windows.Media.Animation
主要属性
EasingMode
缓动模式
类型:EasingMode 枚举
EaseIn
缓入
插值使用与缓动方法关联的数学公式
EaseOut
缓出
插值使用 100% 插值减去与缓动方法关联的数学公式输出结果之后的值
EaseInOut
动画前半段插值使用 EaseIn,动画后半段插值使用 EaseOut
各模式效果图示

派生类
BackEase
收回缓动
主要属性
Amplitude
收回幅度
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid> <Button Width="150" Height="50" Content="动画" HorizontalAlignment="Left" VerticalAlignment="Top"> <Button.RenderTransform> <TranslateTransform x:Name="translateTransform1" X="100"/> </Button.RenderTransform> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="translateTransform1" Storyboard.TargetProperty="X" From="100" To="500" Duration="0:0:2"> <DoubleAnimation.EasingFunction> <BackEase EasingMode="EaseInOut" Amplitude="1"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </Grid> </Window>
BounceEase
弹跳缓动
主要属性
Bounces
弹跳次数
Bounciness
反弹程度
下次弹跳幅度相比上次弹跳幅度的倍率
值越小,分配给初期的弹跳幅度越大
ElasticEase
振动缓动
主要属性
Oscillations
振动次数
Springiness
弹性程度
下次振动幅度相比上次振动幅度的倍率
值越小,分配给初期的振动幅度越大
CircleEase
圆缓动
SineEase
正弦缓动
ExponentialEase
指数缓动
主要属性
Exponent
指数
PowerEase
幂缓动
主要属性
Power
指数幂
QuadraticEase
二次方缓动
CubicEase
三次方缓动
QuarticEase
四次方缓动
QuinticEase
五次方缓动
IsAdditive
是否应给起始值、结束值加上当前值
IsCumulative
动画重复时是否累加该动画的值
DoubleAnimationUsingKeyFrames
双精度浮点数关键帧动画
主要属性
KeyFrames
动画的 DoubleKeyFrame 对象的集合
类型:DoubleKeyFrameCollection
元素类型:DoubleKeyFrame
Double 值关键帧
主要属性
KeyTime
关键帧的到达时间
类型:System.Windows.Media.Animation.KeyTime
XAML 赋值格式
KeyTime="[天.]小时:分钟:秒[.秒的小数部分]" 或 KeyTime="百分数%" 或 KeyTime="Uniform" (表示根据关键帧动画的时长,平均分配每个关键帧的到达时间) 或 KeyTime="Paced"(表示根据每个关键帧的时间长度来分配可用时间,从而确定每个帧的持续时间。这将使动画的“速度”或步幅保持不变)
Value
关键帧的值
派生类
LinearDoubleKeyFrame
线性 Double 值关键帧
DiscreteDoubleKeyFrame
离散 Double 值关键帧
关键帧的值不会作插值处理
SplineDoubleKeyFrame
样条曲线 Double 值关键帧
主要属性
KeySpline
样条曲线
类型:System.Windows.Media.Animation.KeySpline
XAML 赋值格式
KeySpline="控制点1 控制点2" (X 轴表示时间,Y 轴表示速率)(注:坐标值的区间:[0.0, 1.0]) 例: KeySpline="0.12,0.9 0.8,0.2" 
EasingDoubleKeyFrame
缓动 Double 值关键帧
主要属性
EasingFunction
缓动方法
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="1300"> <Grid> <Button Width="150" Height="50" Content="动画" HorizontalAlignment="Left" VerticalAlignment="Top"> <Button.RenderTransform> <TranslateTransform x:Name="translateTransform1" X="100"/> </Button.RenderTransform> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingKeyFrames Storyboard.TargetName="translateTransform1" Storyboard.TargetProperty="X" Duration="0:0:8"> <LinearDoubleKeyFrame KeyTime="0:0:0" Value="100"/> <LinearDoubleKeyFrame KeyTime="0:0:2" Value="300"/> <DiscreteDoubleKeyFrame KeyTime="0:0:4" Value="500"/> <SplineDoubleKeyFrame KeyTime="0:0:6" Value="700" KeySpline="0.6,0.12 0.26,0.9"/> <EasingDoubleKeyFrame KeyTime="0:0:8" Value="900"> <EasingDoubleKeyFrame.EasingFunction> <ElasticEase/> </EasingDoubleKeyFrame.EasingFunction> </EasingDoubleKeyFrame> </DoubleAnimationUsingKeyFrames> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </Grid> </Window>
IsAdditive
是否应给起始值、结束值加上当前值
IsCumulative
动画重复时是否累加该动画的值
DoubleAnimationUsingPath
双精度浮点数路径动画
主要属性
PathGeometry
动画路径
Source
指定路径用于动画值的输出值来源
类型:PathAnimationSource 枚举
X
X 坐标偏移量
Y
Y 坐标偏移量
Angle
旋转的正切角度
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="1300"> <Grid> <Grid.Resources> <PathGeometry x:Key="pathGeometry1" Figures="M 10,100 C 35,0 135,0 160,100 180,190 285,200 310,100"/> </Grid.Resources> <Button Width="150" Height="50" Content="动画" HorizontalAlignment="Left" VerticalAlignment="Top" RenderTransformOrigin="0.5,0.5"> <Button.RenderTransform> <RotateTransform x:Name="rotateTransform1" Angle="0"/> </Button.RenderTransform> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimationUsingPath Storyboard.TargetName="rotateTransform1" Storyboard.TargetProperty="Angle" Duration="0:0:3" PathGeometry="{StaticResource pathGeometry1}" Source="Angle"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> <Path Stroke="Black" StrokeThickness="1" Data="{StaticResource pathGeometry1}"/> </Grid> </Window>
TimelineGroup
由 Timeline 对象组成的复合 Timeline
命名空间:System.Windows.Media.Animation
主要属性
Children
Timeline 对象集合
派生类
ParallelTimeline
并行时间线
派生类
Storyboard
故事板、动画场景
就是并行执行的一组动画
主要属性
TargetName 附加属性
指定要进行动画处理的对象的名称
未指定时
若动画所属的故事板是使用 BeginStoryboard 对象启动的,则目标对象默认为 BeginStoryboard 对象所属的元素
若动画所属的故事板是使用 Begin(~) 方法启动的,则目标对象默认为调用 Begin(~) 方法时传入的 FrameworkElement 对象或 FrameworkContentElement 对象
TargetProperty 附加属性
指定要进行动画处理的(依赖)属性
例:
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="500" Width="800"> <Grid> <Button Width="150" Height="50" Content="动画" HorizontalAlignment="Left" VerticalAlignment="Top"> <Button.RenderTransform> <TranslateTransform x:Name="translateTransform1" X="100"/> </Button.RenderTransform> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <!--使用 BeginStoryboard 对象启动一个 Storyboard:--> <BeginStoryboard> <Storyboard> <!--给动画设置 Storyboard 类的 TargetName、TargetProperty 等附加属性:--> <DoubleAnimation Storyboard.TargetName="translateTransform1" Storyboard.TargetProperty="X" From="100" To="500" Duration="0:0:2"/> <DoubleAnimation Storyboard.TargetProperty="Height" From="50" To="150" Duration="0:0:3"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </Grid> </Window>
MediaTimeline
媒体内容时间线
命名空间:System.Windows.Media
11. 3D绘图
基本概念
坐标系
传统 2D 绘图坐标系
笛卡尔二维左手坐标系
若考虑 Panel.ZIndex 附加属性的效果,可以看作“笛卡尔三维左手坐标系”
特征
原点位于呈现区域左上角
X 轴正方向朝右
Y 轴正方向朝下
图示

3D 绘图坐标系
笛卡尔三维右手坐标系
特征
原点位于呈现区域中心
X 轴正方向朝右
Y 轴正方向朝上
Z 轴正方向朝外
图示

3D 内容容器
用于容纳 3D 对象,构成 3D 场景
3D 对象
摄像机
表示一个观察位置,在该位置上将 3D 内容投影到 2D 图面
图示: 
光源
用于照亮 3D 场景
3D 模型
充当 3D 场景中的物体
基本内容
3D 内容容器
Viewport3D 类
用于容纳、呈现 3D 对象
命名空间:System.Windows.Controls
继承自:FrameworkElement
主要属性
Camera
摄像机
类型:System.Windows.Media.Media3D.Camera
Children
可视 3D 对象集合
类型:System.Windows.Media.Media3D.Visual3DCollection
摄像机
Camera 抽象类
表示一个观察位置,在该位置上将 3D 内容投影到 2D 图面
命名空间:System.Windows.Media.Media3D
主要属性
Transform
应用于摄像机的变换
类型:System.Windows.Media.Media3D.Transform3D
派生类
ProjectionCamera
投影摄像机基类
主要属性
Position
摄像机在 3D 场景中的位置
类型:System.Windows.Media.Media3D.Point3D 结构体
LookDirection
摄像机拍摄方向
类型:System.Windows.Media.Media3D.Vector3D 结构体
说明:
计算向量方向时,一般以原点 (0,0,0) 为起点,以向量经过的目标点(如 (0,0,-1))为终点计算
UpDirection
指定摄像机朝上的方向是哪个方向
一般指定为 (0,1,0)。
类型:System.Windows.Media.Media3D.Vector3D 结构体
NearPlaneDistance
最近呈现图面
图示: 
FarPlaneDistance
最远呈现图面
派生类
OrthographicCamera
正交投影摄像机
主要属性
Width
摄像机取景框的宽度
PerspectiveCamera
透视投影摄像机
主要属性
FieldOfView
摄像机的水平视角
MatrixCamera
矩阵摄像机
主要属性
ProjectionMatrix
投影转换矩阵
ViewMatrix
观察位置信息转换矩阵
可视 3D 对象
Visual3D 抽象类
提供可视 3D 对象通用的服务和属性,其中包括命中测试、坐标转换和边界框计算
命名空间:System.Windows.Media.Media3D
继承自:DependencyObject
主要属性
Transform
应用于 3D 对象的变换
类型:System.Windows.Media.Media3D.Transform3D
派生类
ModelVisual3D
用于呈现 3D 模型对象的 Visual3D
主要属性
Content
3D 模型内容
类型:System.Windows.Media.Media3D.Model3D
Children
该属性为该类的内容属性。
子级可视 3D 对象集合
类型:System.Windows.Media.Media3D.Visual3DCollection
Viewport2DVisual3D
用于呈现 2D 对象的 Visual3D
主要属性
Geometry
3D 几何图形
主要用于设定一个放置 2D 对象的面
类型:System.Windows.Media.Media3D.Geometry3D
Material
材质
类型:System.Windows.Media.Media3D.Material
Visual
该属性为该类的内容属性。
可视 2D 对象
类型:System.Windows.Media.Visual
IsVisualHostMaterial 附加属性
指定材质是否为可视 2D 对象宿主
UIElement3D
3D UI 元素类
派生类
ModelUIElement3D
用于呈现支持输入、焦点和事件的 3D 模型
主要属性
Model
该属性为该类的内容属性。
3D 模型内容 Model3D
类型:System.Windows.Media.Media3D.Model3D
ContainerUIElement3D
用于容纳一系列可视 3D 对象
主要属性
Children
该属性为该类的内容属性。
子级可视 3D 对象集合
类型:System.Windows.Media.Media3D.Visual3DCollection
3D 模型
Model3D 抽象类
表示一个 3D 模型
命名空间:System.Windows.Media.Media3D
主要属性
Transform
应用于 3D 模型的变换
类型:System.Windows.Media.Media3D.Transform3D
派生类
GeometryModel3D
表示使用指定的材质呈现 3D 几何图形 的 3D 模型
主要属性
Geometry
3D 几何图形
类型:System.Windows.Media.Media3D.Geometry3D
Material
正面材质
类型:System.Windows.Media.Media3D.Material
BackMaterial
反面材质
该属性一般无需设置。
类型:System.Windows.Media.Media3D.Material
Light
表示应用到 3D 场景的光源
Model3DGroup
表示一组 3D 模型
主要属性
Children
子级 3D 模型集合
类型:System.Windows.Media.Media3D.Model3DCollection
光源
Light 抽象类
应用到 3D 场景的光源
主要属性
Color
光线的颜色
类型:System.Windows.Media.Color
派生类
DirectionalLight
光线沿指定方向传播的光源
即“平行光”
主要属性
Direction
光线方向
类型:Vector3D
AmbientLight
全局散射光线光源
即“环境光”
PointLightBase
光线从一点辐射出的光源
即“点光源”
主要属性
Position
光源起点位置
类型:Point3D
Range
光源产生效果的最大距离
ConstantAttenuation
照度按常量衰减的数值
可参考照度衰减系数计算公式: attenuation = 1.0 / (constant + linear * distance + quadratic * distance * distance)
LinearAttenuation
照度随距离按线性衰减的数值
QuadraticAttenuation
照度随距离按二次方衰减的数值
派生类
PointLight
全方向辐射点光源
SpotLight
锥形区域方向辐射点光源
主要属性
Direction
光线辐射方向
图示: 
InnerConeAngle
完全照亮的锥形区域最大角度
OuterConeAngle
能照亮的锥形区域最大角度
3D 几何图形
相关概念
3D 几何图形的基本形状为一个三角形
定义三角形的三个点为其“顶点”
三角形的“正面”
WPF 采用逆时针环绕顺序定义正面
当从三角形的正面看时,以逆时针顺序指定其顶点
三角形的 正/反 面判定
使用右手定则,将除大拇指外的四指指向顶点按排列顺序环绕的方向,之后大拇指指向方向的面就是三角形的正面。
图示

Geometry3D 抽象类
表示一个 3D 几何图形
派生类
MeshGeometry3D
3D 几何图形网格
主要属性
Positions
三角形顶点位置集合
若需要,相邻三角形之间可以共享部分顶点
类型:System.Windows.Media.Media3D.Point3DCollection
TriangleIndices
三角形顶点索引的集合
说明
按顺序指定由 Positions 属性中的哪三个点组成各个三角形
若未设置该属性,则按顺序由 Positions 属性中的每三个点组成各个三角形
类型:System.Windows.Media.Int32Collection
Normals
各顶点对应的法线方向集合
说明
顶点法线向量会用于光照计算过程
若未指定该属性,则会根据相邻三角形面生成法线向量,或直接使用三角形面的法线向量
类型:System.Windows.Media.Media3D.Vector3DCollection
TextureCoordinates
各顶点被映射的纹理坐标集合
材质 Brush 为 SolidColorBrush 时,会使用默认的纹理映射,可以不指定该属性。
说明
WPF 中纹理坐标系原点位于纹理左上角
图示

材质
Material 抽象类
表示一个材质
命名空间:System.Windows.Media.Media3D
派生类
DiffuseMaterial
漫反射材质
主要属性
Brush
材质的画刷
类型:System.Windows.Media.Brush
Color
材质颜色筛选器(控制反射出的光线颜色)
默认值为 #FFFFFF
光源为除 AmbientLight 光源之外的其他光源时使用
AmbientColor
材质反射 AmbientLight 光源的光线颜色
默认值为 #FFFFFF
SpecularMaterial
高光材质
说明
SpecularMaterial 会将其材质颜色添加到 3D 模型的任何现有材质上
一般与 DiffuseMaterial 等材质组合使用
主要属性
Brush
材质的画刷
类型:System.Windows.Media.Brush
Color
材质颜色筛选器(控制反射出的光线颜色)
默认值为 #FFFFFF
SpecularPower
高光强度
数值越小,高光面积越大
EmissiveMaterial
发光材质
说明
EmissiveMaterial 会将其材质颜色添加到 3D 模型的任何现有材质上
一般与 DiffuseMaterial 等材质组合使用
主要属性
Brush
材质的画刷
类型:System.Windows.Media.Brush
Color
材质颜色筛选器(控制反射出的光线颜色)
默认值为 #FFFFFF
还可以控制自身发出的光线颜色
MaterialGroup
由 Material 对象组成的复合 Material
主要属性
Children
Material 对象集合
类型:System.Windows.Media.Media3D.MaterialCollection
3D 变换
Transform3D 类
3D 变换
命名空间:System.Windows.Media.Media3D
派生类
AffineTransform3D
仿射 3D 变换
派生类
TranslateTransform3D
3D 平移变换
主要属性
OffsetX
沿 X 轴平移距离
OffsetY
沿 Y 轴平移距离
OffsetZ
沿 Z 轴平移距离
ScaleTransform3D
3D 缩放变换
主要属性
ScaleX
X 轴缩放比例
ScaleY
Y 轴缩放比例
ScaleZ
Z 轴缩放比例
CenterX
变换中心点 X 坐标
CenterY
变换中心点 Y 坐标
CenterZ
变换中心点 Z 坐标
RotateTransform3D
3D 旋转变换
主要属性
Rotation
3D 旋转信息
类型:Rotation3D
3D 旋转
命名空间:System.Windows.Media.Media3D
派生类
AxisAngleRotation3D
绕坐标轴进行的 3D 旋转
主要属性
Axis
3D 旋转的轴方向
类型:Vector3D
Angle
旋转的度数
正/负旋转方向判定
使用右手定则,将大拇指指向轴的方向,其余四指弯曲所指方向即为正旋转方向。
QuaternionRotation3D
四元数 3D 旋转
主要属性
Quaternion
3D 旋转四元数
类型:System.Windows.Media.Media3D.Quaternion
CenterX
变换中心点 X 坐标
CenterY
变换中心点 Y 坐标
CenterZ
变换中心点 Z 坐标
MatrixTransform3D
3D 仿射矩阵 3D 变换
主要属性
Matrix
3D 变换矩阵结构体
类型:System.Windows.Media.Media3D.Matrix3D
Transform3DGroup
由 Transform3D 对象组成的复合 Transform3D
主要属性
Children
Transform3D 对象集合
例:
一个三角体
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <!--3D 内容容器: --> <Viewport3D> <Viewport3D.Camera> <!--摄像机:--> <PerspectiveCamera Position="6,6,6" LookDirection="-1,-1,-1" UpDirection="0,1,0" FieldOfView="45"/> </Viewport3D.Camera> <Viewport3D.Children> <!--可视 3D 对象:--> <ModelVisual3D> <ModelVisual3D.Content> <!--3D 模型——光源:--> <DirectionalLight Color="White" Direction="-1,-1,-1"/> </ModelVisual3D.Content> </ModelVisual3D> <ModelVisual3D> <ModelVisual3D.Content> <!--3D 模型——3D 几何图形:--> <GeometryModel3D> <GeometryModel3D.Geometry> <!--3D 几何图形网格:--> <MeshGeometry3D Positions="0,0,0 0,2,0 3,0,0 0,0,0 0,0,1 0,2,0 0,2,0 0,0,1 3,0,0 0,0,0 3,0,0 0,0,1" TriangleIndices="0 1 2 3 4 5 6 7 8 9 10 11" Normals="0,0,-1 0,0,-1 0,0,-1 -1,0,0 -1,0,0 -1,0,0 1,1,1 1,1,1 1,1,1 0,-1,0 0,-1,0 0,-1,0"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <!--材质:--> <DiffuseMaterial Color="#FFFFFF" AmbientColor="#FFFFFF"> <DiffuseMaterial.Brush> <SolidColorBrush Color="Orange"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> </Viewport3D.Children> </Viewport3D> </Window>
效果

多种 3D 内容
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="700" Width="1000"> <Window.Resources> <!--光源:--> <DirectionalLight x:Key="directionalLight" Color="White" Direction="1,-1,-1"/> <AmbientLight x:Key="ambientLight" Color="White"/> <PointLight x:Key="pointLight" Color="Blue" Position="-35,35,35" Range="60"/> <SpotLight x:Key="spotLight" Color="Red" Position="-35,35,35" Range="60" Direction="2,-1,-1" InnerConeAngle="10" OuterConeAngle="15"/> <!--材质:--> <DiffuseMaterial x:Key="diffuseMaterial1"> <DiffuseMaterial.Brush> <ImageBrush ImageSource="img.png"/> </DiffuseMaterial.Brush> </DiffuseMaterial> <DiffuseMaterial x:Key="diffuseMaterial2" Brush="DarkBlue"/> <MaterialGroup x:Key="diffuseSpecularMaterial"> <DiffuseMaterial Brush="DarkBlue"/> <SpecularMaterial Brush="Cyan" SpecularPower="30"/> </MaterialGroup> <MaterialGroup x:Key="diffuseEmissiveMaterial"> <DiffuseMaterial Brush="DarkBlue"/> <EmissiveMaterial Brush="Cyan"/> </MaterialGroup> </Window.Resources> <Grid> <Grid.RowDefinitions> <RowDefinition Height="1*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <Viewport3D> <Viewport3D.Camera> <PerspectiveCamera x:Name="perspectiveCamera1" Position="-30,30,30" LookDirection="1.5,-1,-1" UpDirection="0,1,0"/> </Viewport3D.Camera> <Viewport3D.Children> <ModelVisual3D Content="{Binding ElementName=selectLight,Path=SelectedItem.Tag}"/> <!--立方体:--> <ModelVisual3D> <ModelVisual3D.Content> <GeometryModel3D Material="{Binding ElementName=selectMaterial,Path=SelectedItem.Tag}"> <GeometryModel3D.Geometry> <MeshGeometry3D Positions="5,0,0 5,10,0 15,10,0 15,0,0 5,0,10 15,0,10 15,10,10 5,10,10" TriangleIndices="0 1 2 2 3 0 0 4 7 7 1 0 4 5 6 6 7 4 5 3 2 2 6 5 1 7 6 6 2 1 0 3 5 5 4 0" Normals="-1,-1,-1 -1,1,-1 1,1,-1 1,-1,-1 -1,-1,1 1,-1,1 1,1,1 -1,1,1" TextureCoordinates="1,1 1,0 0,0 0,1 0,1 1,1 1,0 0,0"/> </GeometryModel3D.Geometry> <GeometryModel3D.Transform> <Transform3DGroup> <RotateTransform3D CenterX="10" CenterY="5" CenterZ="5"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Axis="1,1,1" Angle="{Binding ElementName=selectAngle,Path=Value}"/> </RotateTransform3D.Rotation> </RotateTransform3D> <RotateTransform3D CenterX="10" CenterY="5" CenterZ="5"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Axis="1,1,-1" Angle="{Binding ElementName=selectAngle,Path=Value}"/> </RotateTransform3D.Rotation> </RotateTransform3D> <RotateTransform3D CenterX="10" CenterY="5" CenterZ="5"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Axis="-1,1,-1" Angle="{Binding ElementName=selectAngle,Path=Value}"/> </RotateTransform3D.Rotation> </RotateTransform3D> <RotateTransform3D CenterX="10" CenterY="5" CenterZ="5"> <RotateTransform3D.Rotation> <AxisAngleRotation3D Axis="-1,1,1" Angle="{Binding ElementName=selectAngle,Path=Value}"/> </RotateTransform3D.Rotation> </RotateTransform3D> </Transform3DGroup> </GeometryModel3D.Transform> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> <!--三角体:--> <ModelVisual3D> <ModelVisual3D.Children> <ModelVisual3D> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D Positions="0.2,0.2,0.2 0.2,1,0.2 1.5,0.2,0.2 0.2,0.2,0.5" TriangleIndices="0 1 2 0 3 1 1,3,2 0,2,3" Normals="-1,-1,-1 0,1,0 1,0,0 0,0,1"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial> <DiffuseMaterial.Brush> <SolidColorBrush Color="Blue" Opacity="0.9"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> <ModelVisual3D> <ModelVisual3D.Content> <GeometryModel3D> <GeometryModel3D.Geometry> <MeshGeometry3D Positions="0,0,0 0,2,0 3,0,0 0,0,1" TriangleIndices="0 1 2 0 3 1 1,3,2 0,2,3" Normals="-1,-1,-1 0,1,0 1,0,0 0,0,1"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <!--注:由于 Z-Buffer 技术的存在,若要观察当前材质的透明效果,需要在绘制当前材质之前先绘制景深更深的材质。(如前面的小三角体比当前三角体先添加到集合,其材质就会先绘制)--> <DiffuseMaterial> <DiffuseMaterial.Brush> <SolidColorBrush Color="Blue" Opacity="0.5"/> </DiffuseMaterial.Brush> </DiffuseMaterial> </GeometryModel3D.Material> </GeometryModel3D> </ModelVisual3D.Content> </ModelVisual3D> </ModelVisual3D.Children> <ModelVisual3D.Transform> <ScaleTransform3D CenterX="0" CenterY="0" CenterZ="0" ScaleX="{Binding ElementName=selectScale,Path=Value}" ScaleY="{Binding ElementName=selectScale,Path=Value}" ScaleZ="{Binding ElementName=selectScale,Path=Value}"/> </ModelVisual3D.Transform> </ModelVisual3D> <!--在 3D 几何形状表面显示 2D 对象:--> <Viewport2DVisual3D> <Viewport2DVisual3D.Geometry> <MeshGeometry3D Positions="5,0,15 5,0,20 5,5,20 5,5,15" TriangleIndices="0 1 2 2 3 0" Normals="-1,0,0 -1,0,0 -1,0,0 -1,0,0" TextureCoordinates="0,1 1,1 1,0 0,0"/> </Viewport2DVisual3D.Geometry> <Viewport2DVisual3D.Material> <DiffuseMaterial Brush="Cyan" Viewport2DVisual3D.IsVisualHostMaterial="True"/> </Viewport2DVisual3D.Material> <Viewport2DVisual3D.Visual> <Border BorderBrush="Blue" BorderThickness="1"> <StackPanel Margin="5"> <TextBlock Text="3D 几何形状表面的 2D 对象:" Background="PaleTurquoise"/> <Button Content="弹跳"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="translateTransform3D1" Storyboard.TargetProperty="OffsetY" From="10" To="0" Duration="0:0:3"> <DoubleAnimation.EasingFunction> <BounceEase Bounces="10" Bounciness="1.5"/> </DoubleAnimation.EasingFunction> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> <TextBox Text="[输入:]" Background="CornflowerBlue"/> </StackPanel> </Border> </Viewport2DVisual3D.Visual> <Viewport2DVisual3D.Transform> <TranslateTransform3D x:Name="translateTransform3D1"/> </Viewport2DVisual3D.Transform> </Viewport2DVisual3D> </Viewport3D.Children> </Viewport3D> <Grid Grid.Row="1"> <Grid.ColumnDefinitions> <ColumnDefinition Width="100"/> <ColumnDefinition Width="1*"/> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <TextBlock Text="光源:"/> <ComboBox x:Name="selectLight" Grid.Column="1"> <ComboBoxItem Content="DirectionalLight" Tag="{StaticResource directionalLight}" IsSelected="True"/> <ComboBoxItem Content="AmbientLight" Tag="{StaticResource ambientLight}"/> <ComboBoxItem Content="PointLight" Tag="{StaticResource pointLight}"/> <ComboBoxItem Content="SpotLight" Tag="{StaticResource spotLight}"/> </ComboBox> <TextBlock Text="立方体材质:" Grid.Row="1"/> <ComboBox x:Name="selectMaterial" Grid.Column="1" Grid.Row="1"> <ComboBoxItem Content="DiffuseMaterial(ImageBrush)" Tag="{StaticResource diffuseMaterial1}" IsSelected="True"/> <ComboBoxItem Content="DiffuseMaterial" Tag="{StaticResource diffuseMaterial2}"/> <ComboBoxItem Content="DiffuseSpecularMaterial" Tag="{StaticResource diffuseSpecularMaterial}"/> <ComboBoxItem Content="DiffuseEmissiveMaterial" Tag="{StaticResource diffuseEmissiveMaterial}"/> </ComboBox> <TextBlock Text="三角体缩放:" Grid.Row="2"/> <Slider x:Name="selectScale" Grid.Column="1" Grid.Row="2" Minimum="1" Maximum="30" Value="1"/> <TextBlock Text="立方体旋转:" Grid.Row="3"/> <DockPanel Grid.Column="1" Grid.Row="3"> <Button Content="自动旋转" Width="100"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <DoubleAnimation Storyboard.TargetName="selectAngle" Storyboard.TargetProperty="Value" From="{Binding ElementName=selectAngle,Path=Minimum}" To="{Binding ElementName=selectAngle,Path=Maximum}" Duration="0:0:6"> </DoubleAnimation> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> <Slider x:Name="selectAngle" Maximum="360"/> </DockPanel> <TextBlock Text="摄像机动画:" Grid.Row="4"/> <Button Content="开始" Grid.Column="1" Grid.Row="4"> <Button.Triggers> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <Point3DAnimation Storyboard.TargetName="perspectiveCamera1" Storyboard.TargetProperty="Position" From="-30,30,30" To="50,30,30" Duration="0:0:10"/> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="Button.Click"> <BeginStoryboard> <Storyboard> <Vector3DAnimation Storyboard.TargetName="perspectiveCamera1" Storyboard.TargetProperty="LookDirection" From="1.5,-1,-1" To="-2,-1,-1" Duration="0:0:10"/> </Storyboard> </BeginStoryboard> </EventTrigger> </Button.Triggers> </Button> </Grid> </Grid> </Window>
效果

命中测试
判断是否命中某个 3D 目标的方式
(1)使用 Viewport3D 对象的鼠标事件,并结合 VisualTreeHelper.HitTest(~) 方法进行命中测试
(2)使用 ModelUIElement3D 对象的鼠标事件。若无需更具体的命中目标判断,可以不再结合 VisualTreeHelper.HitTest(~) 方法进行命中测试
例:
C# 代码
using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Media.Media3D; namespace WpfApp1 { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Viewport3D_MouseDown(object sender, MouseButtonEventArgs e) //Viewport3D 从 UIElement 类继承的 MouseDown 事件 { Viewport3D viewport3D = sender as Viewport3D; if (viewport3D is null) return; Point clickLocation = e.GetPosition(viewport3D); //获取鼠标指针相对于指定元素的位置 HitTestResult hitTestResult = VisualTreeHelper.HitTest(viewport3D, clickLocation); //执行命中测试,获取指定 Point 命中的顶层 Visual 对象信息 if (hitTestResult is null) return; if (hitTestResult is RayMeshGeometry3DHitTestResult rayMeshGeometry3DHitTestResult) //判断是否为命中 3D 几何图形网格的结果 { DoubleAnimation doubleAnimation = new DoubleAnimation(); doubleAnimation.From = 1; doubleAnimation.To = 0.9; doubleAnimation.Duration = new Duration(new System.TimeSpan(0, 0, 0, 0, 200)); doubleAnimation.AutoReverse = true; if (rayMeshGeometry3DHitTestResult.VisualHit == cubeModelUIElement3D1) //判断命中的可视 3D 对象 { if (rayMeshGeometry3DHitTestResult.ModelHit == cubeGeometryModel3D1) //判断命中的 3D 模型 { if (rayMeshGeometry3DHitTestResult.MeshHit == cubeMeshGeometry3D1) //判断命中的 3D 几何图形网格 { scaleTransform3D1.BeginAnimation(ScaleTransform3D.ScaleXProperty, doubleAnimation); scaleTransform3D1.BeginAnimation(ScaleTransform3D.ScaleYProperty, doubleAnimation); scaleTransform3D1.BeginAnimation(ScaleTransform3D.ScaleZProperty, doubleAnimation); } } } else if (rayMeshGeometry3DHitTestResult.VisualHit == cubeModelUIElement3D2) { if (rayMeshGeometry3DHitTestResult.ModelHit == cubeGeometryModel3D2) { if (rayMeshGeometry3DHitTestResult.MeshHit == cubeMeshGeometry3D2) { scaleTransform3D2.BeginAnimation(ScaleTransform3D.ScaleXProperty, doubleAnimation); scaleTransform3D2.BeginAnimation(ScaleTransform3D.ScaleYProperty, doubleAnimation); scaleTransform3D2.BeginAnimation(ScaleTransform3D.ScaleZProperty, doubleAnimation); } } } } } private void cubeModelUIElement3D3_MouseDown(object sender, MouseButtonEventArgs e) //ModelUIElement3D 从 UIElement3D 类继承的 MouseDown 事件 { if (sender as ModelUIElement3D != cubeModelUIElement3D3) //判断命中的可视 3D 对象 return; DoubleAnimation doubleAnimation = new DoubleAnimation(); doubleAnimation.From = 1; doubleAnimation.To = 0.9; doubleAnimation.Duration = new Duration(new System.TimeSpan(0, 0, 0, 0, 100)); doubleAnimation.AutoReverse = true; scaleTransform3D3.BeginAnimation(ScaleTransform3D.ScaleXProperty, doubleAnimation); scaleTransform3D3.BeginAnimation(ScaleTransform3D.ScaleYProperty, doubleAnimation); scaleTransform3D3.BeginAnimation(ScaleTransform3D.ScaleZProperty, doubleAnimation); } private void cubeModelUIElement3D4_MouseDown(object sender, MouseButtonEventArgs e) { if (sender as ModelUIElement3D != cubeModelUIElement3D4) return; DoubleAnimation doubleAnimation = new DoubleAnimation(); doubleAnimation.From = 1; doubleAnimation.To = 0.9; doubleAnimation.Duration = new Duration(new System.TimeSpan(0, 0, 0, 0, 100)); doubleAnimation.AutoReverse = true; scaleTransform3D4.BeginAnimation(ScaleTransform3D.ScaleXProperty, doubleAnimation); scaleTransform3D4.BeginAnimation(ScaleTransform3D.ScaleYProperty, doubleAnimation); scaleTransform3D4.BeginAnimation(ScaleTransform3D.ScaleZProperty, doubleAnimation); } } }
XAML 代码
<Window x:Class="WpfApp1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfApp1" mc:Ignorable="d" Title="MainWindow" Height="500" Width="800"> <Viewport3D MouseDown="Viewport3D_MouseDown"> <Viewport3D.Camera> <PerspectiveCamera Position="-30,30,30" LookDirection="1.6,-1.2,-1" UpDirection="0,1,0"/> </Viewport3D.Camera> <Viewport3D.Children> <ModelVisual3D> <ModelVisual3D.Content> <DirectionalLight Color="White" Direction="1,-1,-1"/> </ModelVisual3D.Content> </ModelVisual3D> <ContainerUIElement3D x:Name="containerUIElement3D"> <ModelUIElement3D x:Name="cubeModelUIElement3D1"> <ModelUIElement3D.Model> <GeometryModel3D x:Name="cubeGeometryModel3D1"> <GeometryModel3D.Geometry> <MeshGeometry3D x:Name="cubeMeshGeometry3D1" Positions="0,0,0 0,5,0 5,5,0 5,0,0 0,0,5 5,0,5 5,5,5 0,5,5" TriangleIndices="0 1 2 2 3 0 0 4 7 7 1 0 4 5 6 6 7 4 5 3 2 2 6 5 1 7 6 6 2 1 0 3 5 5 4 0" Normals="-1,-1,-1 -1,1,-1 1,1,-1 1,-1,-1 -1,-1,1 1,-1,1 1,1,1 -1,1,1" TextureCoordinates="1,1 1,0 0,0 0,1 0,1 1,1 1,0 0,0"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial Brush="Blue"/> </GeometryModel3D.Material> <GeometryModel3D.Transform> <ScaleTransform3D x:Name="scaleTransform3D1" CenterX="2.5" CenterY="2.5" CenterZ="2.5"/> </GeometryModel3D.Transform> </GeometryModel3D> </ModelUIElement3D.Model> </ModelUIElement3D> <ModelUIElement3D x:Name="cubeModelUIElement3D2"> <ModelUIElement3D.Model> <GeometryModel3D x:Name="cubeGeometryModel3D2"> <GeometryModel3D.Geometry> <MeshGeometry3D x:Name="cubeMeshGeometry3D2" Positions="10,0,0 10,5,0 15,5,0 15,0,0 10,0,5 15,0,5 15,5,5 10,5,5" TriangleIndices="0 1 2 2 3 0 0 4 7 7 1 0 4 5 6 6 7 4 5 3 2 2 6 5 1 7 6 6 2 1 0 3 5 5 4 0" Normals="-1,-1,-1 -1,1,-1 1,1,-1 1,-1,-1 -1,-1,1 1,-1,1 1,1,1 -1,1,1" TextureCoordinates="1,1 1,0 0,0 0,1 0,1 1,1 1,0 0,0"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial Brush="Red"/> </GeometryModel3D.Material> <GeometryModel3D.Transform> <ScaleTransform3D x:Name="scaleTransform3D2" CenterX="12.5" CenterY="2.5" CenterZ="2.5"/> </GeometryModel3D.Transform> </GeometryModel3D> </ModelUIElement3D.Model> </ModelUIElement3D> <ModelUIElement3D x:Name="cubeModelUIElement3D3" MouseDown="cubeModelUIElement3D3_MouseDown"> <ModelUIElement3D.Model> <GeometryModel3D x:Name="cubeGeometryModel3D3"> <GeometryModel3D.Geometry> <MeshGeometry3D x:Name="cubeMeshGeometry3D3" Positions="0,0,10 0,5,10 5,5,10 5,0,10 0,0,15 5,0,15 5,5,15 0,5,15" TriangleIndices="0 1 2 2 3 0 0 4 7 7 1 0 4 5 6 6 7 4 5 3 2 2 6 5 1 7 6 6 2 1 0 3 5 5 4 0" Normals="-1,-1,-1 -1,1,-1 1,1,-1 1,-1,-1 -1,-1,1 1,-1,1 1,1,1 -1,1,1" TextureCoordinates="1,1 1,0 0,0 0,1 0,1 1,1 1,0 0,0"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial Brush="Cyan"/> </GeometryModel3D.Material> </GeometryModel3D> </ModelUIElement3D.Model> <ModelUIElement3D.Transform> <ScaleTransform3D x:Name="scaleTransform3D3" CenterX="2.5" CenterY="2.5" CenterZ="12.5"/> </ModelUIElement3D.Transform> </ModelUIElement3D> <ModelUIElement3D x:Name="cubeModelUIElement3D4" MouseDown="cubeModelUIElement3D4_MouseDown"> <ModelUIElement3D.Model> <GeometryModel3D x:Name="cubeGeometryModel3D4"> <GeometryModel3D.Geometry> <MeshGeometry3D x:Name="cubeMeshGeometry3D4" Positions="10,0,10 10,5,10 15,5,10 15,0,10 10,0,15 15,0,15 15,5,15 10,5,15" TriangleIndices="0 1 2 2 3 0 0 4 7 7 1 0 4 5 6 6 7 4 5 3 2 2 6 5 1 7 6 6 2 1 0 3 5 5 4 0" Normals="-1,-1,-1 -1,1,-1 1,1,-1 1,-1,-1 -1,-1,1 1,-1,1 1,1,1 -1,1,1" TextureCoordinates="1,1 1,0 0,0 0,1 0,1 1,1 1,0 0,0"/> </GeometryModel3D.Geometry> <GeometryModel3D.Material> <DiffuseMaterial Brush="Orange"/> </GeometryModel3D.Material> </GeometryModel3D> </ModelUIElement3D.Model> <ModelUIElement3D.Transform> <ScaleTransform3D x:Name="scaleTransform3D4" CenterX="12.5" CenterY="2.5" CenterZ="12.5"/> </ModelUIElement3D.Transform> </ModelUIElement3D> </ContainerUIElement3D> </Viewport3D.Children> </Viewport3D> </Window>