WPF 介绍
WPF 介绍
参考资料
- 官方文档:https://learn.microsoft.com/zh-cn/dotnet/desktop/wpf
主要特征
- Windows Presentation Foundation (WPF) 是一个可创建桌面客户端应用程序的 UI 框架。
- WPF 使用基于矢量的呈现引擎,构建用于利用现代图形硬件。
- WPF 开发平台支持广泛的应用开发功能,包括应用模型、资源、控件、图形、布局、数据绑定、文档和安全性。
- WPF 提供一套完善的应用程序开发功能,这些功能包括 Extensible Application Markup Language (XAML)、控件、数据绑定、布局、二维和三维图形、动画、样式、模板、文档、媒体、文本和版式。
- WPF 是 .NET Framework 的子集,可以生成整合 .NET API 其他元素的应用程序
- WPF 使用 Extensible Application Markup Language (XAML),为应用编程提供声明性模型。
XAML
什么是 XAML
- XAML 是一种声明性标记语言。
- 声明性 XAML 标记中创建可见的 UI 元素,然后使用代码隐藏文件(这些文件通过分部类定义与标记相联接)将 UI 定义与运行时逻辑相分离。
XAML 语法
主要语法基于 XML 标记语言
特性语法(属性)
使用属性名 = 属性值 的形式
<Button Background="Blue" Foreground="Red" Content="This is a button"/>
属性元素语法
无法在特性语法的引号和字符串限制内充分地表达的属性值可以使用属性元素语法
使用 <TypeName.PropertyName> 标签,在标签内提供该属性值
<Button> <Button.Background> <SolidColorBrush Color="Blue"/> </Button.Background> <Button.Foreground> <SolidColorBrush Color="Red"/> </Button.Foreground> <Button.Content> This is a button </Button.Content> </Button>
集合语法
如果某个特定属性采用集合类型,则在标记中声明为该属性的值内的子元素的项将成为集合的一部分。
<LinearGradientBrush> <LinearGradientBrush.GradientStops> <!-- no explicit new GradientStopCollection, parser knows how to find or create --> <GradientStop Offset="0.0" Color="Red" /> <GradientStop Offset="1.0" Color="Blue" /> </LinearGradientBrush.GradientStops> </LinearGradientBrush>
内容属性
类可以指定它的一个且仅有一个属性为 XAML 内容属性
仅对内容属性而言,可以在 XAML 标记中设置该属性时省略属性元素,并在标记中生成更直观的父级/子级形式。
<Border> <TextBox Width="300"/> </Border> <!-- 等效于 --> <Border> <!-- Child 属性为 Border 的内容属性,可以省略 --> <Border.Child> <TextBox Width="300"/> </Border.Child> </Border>
XAML 内容属性的值必须完全在该对象元素的其他任何属性元素之前或之后指定
<!-- 内容属性值 "I am a blue button" 不能被其它元素打断,否则无法编译 --> <Button> I am a <Button.Background>Blue</Button.Background> blue button </Button>
XAML 中的大小写和空白
- XAML 区分大小写,按照 CLR 区分大小写的相同规则区分大小写。
- XAML 将空格、换行符和制表符转化为空格,如果它们出现在一个连续字符串的任一端,则保留一个空格。
标记扩展
- 用于提供特性语法的值时,大括号(
{
和}
)表示标记扩展用法。 - 最常用的标记扩展是
Binding
(用于数据绑定表达式)以及资源引用StaticResource
和DynamicResource
。
类型转换器
特性值必须能够通过字符串进行设置
可以将更复杂的对象类型的实例指定为字符串和特性
<Button Margin="10,20,10,30" Content="Click me"/> <!-- 等效为 --> <Button Content="Click me"> <Button.Margin> <!-- 将 Thickness 的四个关键属性设置为新实例的特性 --> <Thickness Left="10" Top="20" Right="10" Bottom="30"/> </Button.Margin> </Button>
根元素和命名空间
一个 XAML 文件只能有一个根元素,页面的 Window 或 Page、外部字典的 ResourceDictionary 或应用定义的 Application 标签
根元素还包含特性 xmlns 和 xmlns:x
xmlns
特性明确指示默认的 XAML 命名空间。 在默认的 XAML 命名空间中,可以不使用前缀指定标记中的对象元素。<Page x:Class="index.Page1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="PageTitle"> </Page>
x: 前缀
- x:Key:为 ResourceDictionary(或其他框架中的类似字典概念)中的每个资源设置唯一的键。
- x:Class:向为 XAML 页提供后台代码的类指定 CLR 命名空间和类名。
- x:Name:处理对象元素后,为运行时代码中存在的实例指定运行时对象名称。
- x:Static:启用一个返回静态值的引用,该静态值不是与 XAML 兼容的属性。
- x:Type:根据类型名称构造 Type 引用。
附加属性和附加事件
允许对任何元素指定某些属性或事件,即使要设置属性或事件的元素的类型定义中不存在该属性或事件。
采用 ownerType.propertyName 的形式指定附加属性
<DockPanel> <!-- DockPanel.Dock 作为附加属性,控制 Button 位置 --> <Button DockPanel.Dock="Left" Width="100" Height="20">I am on the left</Button> <Button DockPanel.Dock="Right" Width="100" Height="20">I am on the right</Button> </DockPanel>
布局
布局原则
- 一个窗口中只能包含一个元素
- 不应显示设置元素尺寸
- 不应使用坐标设置元素的位置
- 可以嵌套布局容器
布局容器
- StackPanel: 水平或垂直排列元素、Orientation 属性分别: Horizontal / Vertical
- WrapPanel : 水平或垂直排列元素、针对剩余空间不足会进行换行或换列进行排列
- DockPanel : 根据容器的边界、元素进行Dock.Top、Left、Right、Bottom设置
- Grid : 类似table表格、可灵活设置行列并放置控件元素、比较常用
- UniformGrid : 指定行和列的数量, 均分有限的容器空间
- Canvas : 使用固定的坐标设置元素的位置、不具备锚定停靠等功能。
继承关系

DispatcherObject 类
- Object -> DispatcherObject
- WPF 中大多数对象的基类
- 提供用于处理并发和线程的基本构造
DependencyObject 类
- Object -> DispatcherObject -> DependencyObject
- 参与依赖属性系统的对象,以及作为依赖项属性实现的附加属性。
- GetValue 和 SetValue 支持以及一般的属性系统支持
Visual 类
- Object -> DispatcherObject -> DependencyObject -> Visual
- 提供呈现支持,包括命中测试、坐标转换、边界框计算
UIElement 类
- Object -> DispatcherObject -> DependencyObject -> Visual -> UIElement
- 对动画属性值的基本支持。有关更多信息,请参见动画概述。
- 对基本输入事件和命令的支持。有关更多信息,请参见输入概述和命令概述。
- 可以重写以便为布局系统提供信息的虚方法。
FrameworkElement 类
- Object -> DispatcherObject -> DependencyObject -> Visual -> UIElement -> FrameworkElement
- 对样式设置和演示图板的支持。参见 Style 和演示图板概述。
- 对数据绑定的支持。参见数据绑定概述。
- 对动态资源引用的支持。参见资源概述。
- 对属性值继承以及元数据中有助于向框架服务报告属性的相关情况(如数据绑定、样式或布局的框架实现)的其他标志的支持。参见框架属性元数据。
- 逻辑树的概念。参见 WPF 中的树。
- 对布局系统的实际 WPF 框架级实现的支持,包括 OnPropertyChanged 重写。
ContentElement 类
- Object -> DispatcherObject -> DependencyObject -> ContentElement
- 对动画的支持。有关更多信息,请参见动画概述。
- 对基本输入事件和命令的支持。有关更多信息,请参见输入概述和命令概述。
FrameworkContentElement 类
Object -> DispatcherObject -> DependencyObject -> ContentElement -> FrameworkContentElement
对数据绑定的支持。有关更多信息,请参见数据绑定概述。
对动态资源引用的支持。有关更多信息,请参见资源概述。
对属性值继承以及元数据中有助于向框架服务报告属性情况(如数据绑定、样式或布局的框架实现)的其他标志的支持。有关更多信息,请参见框架属性元数据。
您不会继承对布局系统修改(如 ArrangeOverride)的访问权限。布局系统实现只在 FrameworkElement 上提供。但是,您会继承 OnPropertyChanged 重写
Freezable 类
- Object -> DispatcherObject -> DependencyObject -> Freezable
- Freezable 类型为某些图形元素(如几何形状、画笔以及动画)提供了一个通用的基础。
- Freezable 不是一个 Visual;当应用 Freezable 以填充另一个对象的属性值时,它包含的属性将变成子属性,而这些子属性可能会影响呈现。
Animatable 类
- Object -> DispatcherObject -> DependencyObject -> Freezable -> Animatable
- 使当前动画的属性可以与未动画的属性区分开。
Brush 类
- Object -> DispatcherObject -> DependencyObject -> Freezable -> Brush
- 定义用于绘制图形对象的基类
标准控件

基类
- 派生自 Object -> DispatcherObject -> DependencyObject -> Visual -> UIElement -> FrameworkElement
- Panel 类:布局控件元素基类
- Control 类:使用 ControlTrmplate 来定义外观的 UI 元素基类
- ContentControl 类:包含单项内容的控件基类
- ItemsControl 类:集合控件元素控件的基类
- Decorator 类:单个子元素应用效果的基类
- Sharp 类:形状元素的基类
虚拟化技术
- 虚拟化技术能提高 UI 元素的渲染性能,只有容器的可见项在屏幕上呈现
- VirtualizingStackPanel 用于实现虚拟化,其行为与 StackPanel 一样
- IsVirtualizing 属性:是否激活虚拟化
- VirtualizationMode 属性:
- Standard : 为每个可见项目创建一个项目容器,在不需要的时候丢弃
- Recycling : 不需要的容器会进行回收,减少频繁创建和丢弃容器的性能损失
控件列表汇总
- 按钮: Button 和 RepeatButton。
- 数据显示:DataGrid、ListView 和 TreeView。
- 日期显示和选项: Calendar 和 DatePicker。
- 对话框: OpenFileDialog、 PrintDialog和 SaveFileDialog。
- 数字墨迹: InkCanvas 和 InkPresenter。
- 文档: DocumentViewer、 FlowDocumentPageViewer、 FlowDocumentReader、 FlowDocumentScrollViewer和 StickyNoteControl。
- 输入: TextBox、 RichTextBox和 PasswordBox。
- 布局: Border、 BulletDecorator、 Canvas、 DockPanel、 Expander、 Grid、 GridView、 GridSplitter、 GroupBox、 Panel、 ResizeGrip、 Separator、 ScrollBar、 ScrollViewer、 StackPanel、 Thumb、 Viewbox、 VirtualizingStackPanel、 Window和 WrapPanel。
- 媒体: Image、 MediaElement和 SoundPlayerAction。
- 菜单: ContextMenu、 Menu和 ToolBar。
- 导航: Frame、 Hyperlink、 Page、 NavigationWindow和 TabControl。
- 选项: CheckBox、 ComboBox、 ListBox、 RadioButton和 Slider。
- 用户信息: AccessText、 Label、 Popup、 ProgressBar、 StatusBar、 TextBlock和 ToolTip。
窗口类
Window
- 创建无框窗口:修改窗口样式 None、SingleBorderWindow、ThreeDBorderWindow、ToolWindow
子窗口 ChildWindow
- 一个轻量级窗口,可以作为弹窗、模式对话框
- 当子窗口活动时,父窗口会自动禁用
- 使用 Show() 方法显示子窗口
ContentControl 类
设置内容的属性为 Content
控件目录下只允许设置一次 Content,设置多个控件需要用布局控件包裹起来
<Button Content="btn"></Button> <Button> <StackPanel> <Image/> <Label/> </StackPanel> </Button>
HeaderedContentControl 类
可设置 Content 和标题 Header
<GroupBox Header="Test"> <StackPanel> <TextBlock>Text 1</TextBlock> <TextBlock>Text 2</TextBlock> <TextBlock>Text 3</TextBlock> </StackPanel> </GroupBox>
ItemsControl 类
显示列表类的数据
设置数据源的方式一般通过 ItemSource 设置
<TabControl> <TabItem Header="page 1"/> <TabItem Header="page 2"/> <TabItem Header="page 3"/> </TabControl>
TextBlock
- 用于显示文本, 不允许编辑的静态文本。
- Text 属性设置显示文本的内容。
TextBox
用于输入/编辑内容的控件。
Text 属性设置显示文本的内容。
Text 默认在失去焦点时触发 Changed 事件,可以通过 UpdateSourceTrigger 修改
<TextBox Text="{Binding Info,UpdateSourceTrigger=PropertyChanged}"/>
Button
- 简单按钮,Content显示文本
- Click 可设置点击事件
- Command 可设置后台的绑定命令
- ClickMode 点击事件触发模式(Hover,Press,Release)
ComboBox
- 下拉框控件
- ItemSource 设置下拉列表的数据源, 也可以显示设置
DataGrid
CurrentItem 和 SelectedItem 区别:CurrentItem 在选获取焦点后被设置为选择项,与 SelectedItem 一致,当失去去焦点,在下一次选择之前被设置为 null,但是此时 SelectedItem 仍然存在。
- 如下代码:如果 CommandParameter 的 Path = CurrentItem,则在选中并执行命令后,如果不切换选择,此时参数为空;如果 Path=SelectedItem ,每次参数都是当前的选中项。
<ContextMenu x:Key="ContextMenu" ItemsSource="{Binding ContextMenuItems}"> <ContextMenu.ItemTemplate> <DataTemplate> <StackPanel> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <i:InvokeCommandAction Command="{Binding Command}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=SelectedItem}" /> </i:EventTrigger> </i:Interaction.Triggers> <TextBlock Margin="12,0,0,0" Text="{Binding Name}" /> </StackPanel> </DataTemplate> </ContextMenu.ItemTemplate> </ContextMenu>
样式
样式资源
支持控件元素的样式、行为、触发器
内联样式优先于 Style 样式
如果不使用 x:Key 命名样式,则此样式将应用于所有 TargetType 指定类型的控件
<Window.Resources> <Style x:Key="textStyle" TargetType="{x:Type TextBlock}"> <Setter Property="Foreground" Value="Red"/> <Setter Property="FontSize" Value="50"/> </Style> </Window.Resources> <StackPanel> <TextBlock Style="{StaticResource textStyle}" Text="text 01"/> <TextBlock Style="{StaticResource textStyle}" FontSize="30" Text="text 02"/> </StackPanel>
裁剪区域
UIElement 类的 Clip 属性
<Image Width="100" Height="100"> <Image.Clip> <EllipseGeometry Center="50,50" RadiusX="50" RadiusY="30"/> </Image.Clip> </Image>
笔刷对象 Brush
- SolidColorBrush
- LinerGrientBrush
- RadialGradientBrush
- DrawingBrush
- VisualBrush
- ImageBrush
位图效果 BitmapEffect
位图效果将 BitmapSource 作为输入
5 种效果:
模糊效果 BlurBitmapEffect
外发光效果 OuterGlowBitmapEffect
投影效果 DropShadowBitmapEffect
切角效果 BevelBitmapEffect
浮雕效果 EmbossBitmapEffect
资源
静态资源和动态资源
- 静态资源只加载一次,动态资源随着资源数据修改而更新
- 静态资源使用 "{ StaticResource ... }"
- 动态资源使用 "{ DynamicResource ... }"
资源字典
将资源从 View 中抽离出来形成单独的资源文件
可以方便多个 View 共享相同的资源
<!-- ResourceDictionry.xaml --> <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Style x:Key="buttonStyle" TargetType="{x:Type Button}"> <Setter Property="FontSize" Value="20"/> <Setter Property="Width" Value="100"/> <Setter Property="Height" Value="50"/> <Setter Property="Margin" Value="20"/> </Style> </ResourceDictionary>
在主界面中使用 MergedDictionaries 方法加载资源字典
<Window.Resources> <ResourceDictionary> <!-- 合并资源字典 --> <ResourceDictionary.MergedDictionaries> <ResourceDictionary Source="ResourceDictionary.xaml"/> </ResourceDictionary.MergedDictionaries> <!-- 此处添加其它内嵌资源 --> </Window.Resources>
列表资源
定义列表资源项目
<CompositeCollection x:Key="FileContextMenuItems"> <MenuItem Command="{Binding DownloadCommand}" Header="下载" /> <!-- 其他 Item 项 --> </CompositeCollection>
所有列表类型控件可以添加列表资源, 使用 CompositeCOllection 可以组合多个列表
<ContextMenu x:Key="CellContextMenu"> <ContextMenu.ItemsSource> <CompositeCollection> <CollectionContainer Collection="{Binding Source={StaticResource FileContextMenuItems}}" /> <Separator /> <CollectionContainer Collection="{Binding Source={StaticResource GenericContextMenuItems}}" /> </CompositeCollection> </ContextMenu.ItemsSource> </ContextMenu>
资源共享
如果资源 x:Shared = "True" (默认),则各控件调用的是同一个资源实例
实现上下文菜单和常规菜单之间共享菜单定义,要设置为 x:Shared = "False",不同地方使用时将得到副本
<!-- 通用上下文菜单项设置 x:Shared="False" --> <CompositeCollection x:Key="GenericContextMenuItems" x:Shared="False"> <MenuItem Command="{Binding RefreshCommand}" Header="刷新列表"/> </CompositeCollection> <!-- 单元格上下文菜单 --> <ContextMenu x:Key="CellContextMenu" DataContext="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}, Mode=FindAncestor}, Path=DataContext}"> <ContextMenu.ItemsSource> <CompositeCollection> <CollectionContainer Collection="{Binding Source={StaticResource FileContextMenuItems}}" /> <Separator /> <CollectionContainer Collection="{Binding Source={StaticResource GenericContextMenuItems}}" /> </CompositeCollection> </ContextMenu.ItemsSource> </ContextMenu>
创建实例对象资源
<ItemsControl.ItemsSource>
<sys:ArrayList xmlns:sys="clr-namespace:System.Collections;assembly=mscorlib">
<sys:Int32 xmlns:sys="clr-namespace:System;assembly=mscorlib">1</sys:Int32>
<sys:Int32 xmlns:sys="clr-namespace:System;assembly=mscorlib">2</sys:Int32>
</sys:ArrayList>
</ItemsControl.ItemsSource>
依赖属性 DP 和附加属性 AP
依赖属性是一种属性,其值由外部源决定
附加属性是允许将属性附加到任何对象的依赖属性
定义附加属性的类型可以是目标类型的父元素,也可以是内容控件的子元素,也可以是某个服务
绑定 Binding
绑定源 Source 和 绑定路径 Path
- Source是个object类型
- Path 用于设置绑定源[属性]的路径
- Path 是个PropertyPath 类型,只能用于绑定属性,而不能绑定普通字段,普通字段无法实现数据变更之后的通知功能。
- 绑定附加属性时,附加属性需要放在小括号中
Path=(ItemsControl.AlternationIndex)
ObjectDataProvider
ObjectDataProvider 可以在XAML中将方法的返回结果包装和创建为绑定数据源。
该方法常用于包装枚举类型本身提供的枚举值数组用作数据绑定源。
ObjectDataProvider 属性:
- ConstructorParameters:获取要传递给该构造函数的参数列表。
- MethodName: 获取或设置要调用的方法的名称。
- MethodParameters: 获取要传递给方法的参数列表。
- ObjectInstance: 获取或设置用作绑定源的对象。
- ObjectType: 获取或设置要创建其实例的对象的类型。
ObjectInstance 和 ObjectType 不可同时赋值。ObjectType 的类型是 Type,ObjectInstance 的类型是 Object。
<!-- 枚举基类Enum有一个静态方法Enum.GetValues(Type),该方法可以检索返回指定枚举类型中的常数值数组Array --> <!-- 添加命名控件引用 xmlns:local="clr-namespace:WpfDemo" --> <Window.Resources> <ObjectDataProvider x:Key="GenderXML" ObjectType="{x:Type sys:Enum}" MethodName="GetValues"> <ObjectDataProvider.MethodParameters> <x:Type TypeName="local:Gender"/> </ObjectDataProvider.MethodParameters> </ObjectDataProvider> </Window.Resources> <StackPanel Orientation="Horizontal"> <TextBlock Margin="5" Text="性别:" /> <ComboBox ItemsSource="{Binding Source={StaticResource GenderXML}}" Width="100"/> </StackPanel>
namespace WpfDemo { /// <summary> /// 性别 /// </summary> public enum Gender { Male, Female } }
绑定的模式
- OneTime(单次) : 根据第一次源属性设置目标属性, 在此之后所有改变都无效
- OneWay(单向绑定) : 当源属性发生变化更新目标属性
- OneWayToSource : 和OneWay类型, 只不过整个过程倒置,目标属性发生变化时更新源属性
- TwoWay(双向绑定) : 当源属性发生变化更新目标属性, 目标属性发生变化也更新源属性
- Default : 既可以是双向,也可以是单项, 除非明确表明某种模式, 否则采用该默认绑定
绑定的方式
绑定到元素
<!-- ElementName 指定源名称 -->
<!-- Path 指定绑定到源的属性 -->
<!-- Mode 指定绑定模式 -->
<StackPanel>
<Slider x:Name="slider"/>
<TextBlock Text="{Binding ElementName=slider,Path=Value,Mode=TwoWay}" HorizontalAlignment="Center"/>
</StackPanel>
绑定到资源
<Window.Resources>
<TextBox x:Key="text">text box</TextBox>
</Window.Resources>
<StackPanel Width="600">
<!-- Source : 用 Source 指向一个静态资源 -->
<TextBox Text="{Binding Source={StaticResource text},Path=Text,Mode=OneWay}"/>
</StackPanel>
绑定到 Item 自身
<!-- Paths 为字符串列表 -->
<ListBox ItemsSource="{Binding Paths}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Style="{StaticResource MaterialDesignSubtitle1TextBlock}" Text="{Binding}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
绑定 DataContext 对象属性
<DataGrid ItemsSource="{Binding Students}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}" Header="名字"/>
<DataGridTextColumn Binding="{Binding Age}" Header="年龄"/>
<DataGridTextColumn Binding="{Binding Gender}" Header="性别"/>
</DataGrid.Columns>
</DataGrid>
<!-- RelativeSource : 从当前的元素树向上查找到第一个非空的 DataContext 属性为源对象 -->
<TextBox Text="{Binding Name}"/>
绑定 RelativeSource
使用 RelativeSourceMode 指定相对资源模式:PreviousData 、TemplatedParent 、Self 、FindAncestor
Self : 定位到当前元素
<Rectangle Fill="Red" Height="100" Width="{Binding RelativeSource={RelativeSource Self}, Path=Height}"/> <TextBlock Width="{Binding RelativeSource={RelativeSource Self}, Path=Parent.ActualWidth}"/>
FindAncestor : 向父元素层级查找
<!-- 查找父元素为 StackPanel 的 Width 值 --> <TextBox Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type StackPanel}, Mode=FindAncestor},Path=Width}" /> <!-- 查找父元素为 Canvas 的 Name 值, AncestorLevel 指定父对象级别:向上找到的第几个 Canvas 控件,非 Canvas 不会计入层数 --> <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type Canvas}, Mode=FindAncestor, AncestorLevel=2}, Path=Name}" />
TemplatedParent : 绑定到应用该模板的控件的属性
<!-- TemplateBinding 是在编译期进行,TemplatedParent 是在运行时进行 --> <Window.Resources> <ControlTemplate x:Key="template"> <Grid> <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Background}" /> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding RelativeSource={RelativeSource Mode=TemplatedParent}, Path=Content}" /> </Grid> </ControlTemplate> </Window.Resources> <Button Width="200" Height="100" Background="Red" Content="Click" FontSize="50" Template="{StaticResource template}" />
PreviousData : 绑定到当前控件的前一个控件的数据,常用于 Items 控件
UpdateSourceTrigger
- 用于控制何时触发源更新操作
- Default : 默认操作,大多控件是 LostFocus
- LostFocus : 失去焦点时
- PropertyChanged : 属性变化时
- Explicit : 延迟更新,直到单击按钮强制更新
绑定列表
- 使用 List 或者 Array:
- 没有实现 INotifyPropertyChanged
- 无法在插入或删除元素时通知
- 使用 ObservableCollection
<T>
:- 插入或删除元素时会通知
- 触发 CollectionChanged 事件
- 修改元素属性时无法通知
- 使用 BindingList
<T>
:- 插入或删除元素时会通知
- 修改元素属性时也会通知
- 触发 ListChanged 事件
- 利用 CollectionChanged 、ListChanged 事件,可以在列表变化时通知其他属性修改
// 即使使用自动属性,不实现INotifyPropertyChanged接口;只要集合改变(项目加减),就能自动实现数据的更新
public ObservableCollection<string> Paths {get;set;}= new () ;
// 内部元素无需实现 INotifyPropertyChanged 接口
public BindingList<User> Users {get;set;}= new();
public int TotalUser => Users.Count;
public ViewModel(){
Users.ListChanged += (_,_) => OnPropertyChanged(nameof(TotalUser));
}
绑定通知更改
INotifyPropertyChanged、INotifyCollectionChanged 接口
INotifyPropertyChanged 可以通知前端控件属性发生改变
如果使用 Prism 框架,ViewModel 可以继承 Bindale,属性使用 SetProperty() 或 RaisePropertyChanged() 方法实现通知
<StackPanel> <StackPanel Orientation="Horizontal" Margin="10"> <TextBlock Text="点击次数:"/> <TextBlock Text="{Binding Count}"/> </StackPanel> <Button Command="{Binding CountCommand" Content="Click"/> </StackPanel>
/// <summary> /// 主视图模型类 /// </summary> class MainViewModel : ViewModelBase { private int count = 0; public MainViewModel() { CountCommand = new MyCommand((obj) => this.Count++); // 定义命令 } public MyCommand CountCommand { get; set; } public int Count { get => count; set { count = value; OnPropertyChanged(); // 通知属性修改 } } }
/// <summary> /// 定义视图基类实现通知 /// </summary> internal class ViewModelBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; // [CallerMemberName] 可以获取调用该方法的属性名字 public void OnPropertyChanged([CallerMemberName]string propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } }
控件模板 ControlTemplate
选 Buttom 按钮, 右键选择编辑模板>编辑副本,打开标准控件的默认样式
<ControlTemplate TargetType="{x:Type Button}"> <Border x:Name="border" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" SnapsToDevicePixels="true"> <ContentPresenter x:Name="contentPresenter" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/> </Border> <ControlTemplate.Triggers> ...默认触发器... </ControlTemplate.Triggers> </ControlTemplate>
ContentPresenter 为内容显示控件和部分属性控制,修改 Border 和 ContentPresenter 将会修改 Button 的显示
ControlTemplate 中的 TemplateBinding 的作用:通过模板绑定关联到 Button 实例中指定的样式、属性
自定义 ControlTemplate:
- 创建一个 ControlTemplate ,设定一个键名称, 指定其模板的类型
- 创建一个 Border 用于设置按钮边样式
- 创建一个内容呈现的控件 ContentPresenter, 设置 TemplateBinding.
- 按钮的 Template 绑定该模板
<Window.Resources> <ControlTemplate x:Key="ButtonStyle" TargetType="{x:Type Button}"> <Border CornerRadius="15" Background="LightGray"> <ContentPresenter VerticalAlignment="{TemplateBinding VerticalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"></ContentPresenter> </Border> </ControlTemplate> </Window.Resources> <Button Template="{StaticResource ButtonStyle}" Width="100" Height="50" FontSize="10" Content="Click me!" VerticalAlignment="Center" HorizontalAlignment="Center"/>
数据模板 DataTemplate
数据模板常用在 3 种类型的控件:
- Grid这种列表表格中修改Cell的数据格式, CellTemplate 可以修改单元格的展示数据的方式。
- 针对列表类型的控件, 例如树形控件,下拉列表,列表控件, 可以修改其中的 ItemTemplate。
- 修改 ContentTemplate, 例UserControl控件的数据展现形式。
image-20230130144735892
CellTemplate
使用 DataGridTemplateColumn.CellTemplate 增操作列
<DataGrid ItemsSource="{Binding Students}" AutoGenerateColumns="False" CanUserAddRows="False"> <DataGrid.Columns> <DataGridTextColumn Binding="{Binding Name}" Header="名字"/> <DataGridTextColumn Binding="{Binding Age}" Header="年龄"/> <DataGridTextColumn Binding="{Binding Gender}" Header="性别"/> <DataGridTemplateColumn Header="操作"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Button Content="增加" Margin="5"/> <Button Content="删除" Margin="5"/> </StackPanel> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid>
ItemTemplate
修改列表控件的显示
<!--创建一个颜色框加颜色代码的下拉框--> <Window.Resources> <DataTemplate x:Key="colors" > <StackPanel Orientation="Horizontal"> <Border Background="{Binding Code}" Width="10" Height="10" Margin="5,0"/> <TextBlock Text="{Binding Code}"/> </StackPanel> </DataTemplate> </Window.Resources> <ComboBox ItemTemplate="{StaticResource colors}" ItemsSource="{Binding Codes}" Width="300" Height="30" />
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); ViewModel view = new ViewModel(); view.Codes = new List<Color> { new Color() { Code = "#FF8C00" }, new Color() { Code = "#ff0088" }, new Color() { Code = "#55ff66" } }; DataContext = view; } } public class ViewModel { public List<Color> Codes { get; set; } } public class Color { public string Code { get; set; } }
ItemsControl
设置 ItemsPanel 容器, 用于容纳列表的最外层容器
定义子项的 DataTemplate, 用于容纳组件
<ItemsControl x:Name="test" ItemsSource="{Binding Codes}"> <ItemsControl.ItemsPanel> <ItemsPanelTemplate> <WrapPanel Orientation="Horizontal"/> </ItemsPanelTemplate> </ItemsControl.ItemsPanel> <ItemsControl.ItemTemplate> <DataTemplate> <Button Width="100" Height="30" Content="{Binding Code}" Background="{Binding Code}"/> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
ContentTemplate(不常用)
事件
路由事件特点
路由事件是一个特殊的事件功能, 路由事件使一个元素可以处理另一个元素引发的事件
类处理:路由事件支持类事件处理程序
引用无反射的事件:每个路由事件都会创建一个 RoutedEvent 字段标识符,以提供可靠的事件标识技术,不需要静态或运行时反射来标识事件
单一处理程序附件点:可以为每个按钮 Click 的事件注册处理程序,但使用路由事件,可以附加单个处理程序
<Border Height="30" Width="200" BorderBrush="Gray" BorderThickness="1"> <StackPanel Background="LightBlue" Orientation="Horizontal" Button.Click="YesNoCancelButton_Click"> <Button Name="YesButton">Yes</Button> <Button Name="NoButton">No</Button> <Button Name="CancelButton">Cancel</Button> </StackPanel> </Border>
private void YesNoCancelButton_Click(object sender, RoutedEventArgs e) { FrameworkElement sourceFrameworkElement = e.Source as FrameworkElement; switch (sourceFrameworkElement.Name) { case "YesButton": // YesButton logic. break; case "NoButton": // NoButton logic. break; case "CancelButton": // CancelButton logic. break; } e.Handled = true; }
路由策略
浮点:调用事件源上的事件处理程序 --> 调用连续的父元素事件处理程序 --> 直到到达元素树根。
隧道:调用元素树根目录中的事件处理程序 --> 调用连续的子元素事件处理程序 --> 直到到达事件源。 隧道路由后面的事件也称为 预览 事件。
直接:仅调用事件源上的事件处理程序。 与 CLR 事件不同,直接路由事件支持 类处理 ,并且可由 EventSetters 和 EventTriggers 使用。
阻止事件继续路由:
RoutedEventArgs.Handled = true;
命令 ICommand
实现了 ICommand 接口的类实例可以绑定到前端控件的 Command 属性值,实现前后端解耦
使用控件的 CommandParameter 属性可以向命令传递参数
前端控件触发 Command 命令,Command 实例调用 Execute 方式,执行 Command 中得 Action 对象。
Command 的 CanExecute 控制是否可以调用,因此,View 中应该使用 CanExecute 控制,而不是使用控件的 IsEnable 属性
<Button Command="{Binding ShowCommand}" CommandParameter="{Binding 需要传递的参数}" Content="Click"/>
class MainViewModel : ViewModelBase { public MainViewModel() { ShowCommand = new MyCommand(BtnClick); } public MyCommand ShowCommand { get; set; } private void BtnClick(object? obj){/* 需要执行的操作 */} }
public class MyCommand : ICommand { public event EventHandler CanExecuteChanged; private Action<object> action; private Func<bool> canExecute; public MyCommand(Action<object> action,Func<bool> canExecute) { this.action = action; this.canExecute = canExecute; } public bool CanExecute(object parameter) { return this.canExecute?.Invoke() != false; } public void Execute(object parameter) { if(this.action != null) this.action(parameter); } }
MVVM 架构
MVVM 概念
- MVVM 架构:Model-View-ViewModel,MVVM模式其实是MVP模式与WPF结合的应用方式时发展演变过来的一种新型架构模式。
- 核心思想:界面和业务功能进行分离
- Model:程序中要操纵的实际对象的抽象,可包含属性和行为
- View:负责如何显示数据及发送命令,接收用户输入,把数据展现给用户
- **ViewModel:**是一个C#类,负责收集需要绑定的数据和命令,聚合Model对象,通过View类的DataContext属性绑定到View,同时也可以处理一些UI逻辑。显示的数据对应着ViewMode中的Property,执行的命令对应着ViewModel中的Command。
- View 对应一个 ViewModel,ViewModel 可以聚合多个 Model,ViewModel 可以对应多个 View

MVVM 的优势
- 随着功能地增加,系统越来越复杂,相应地程序中会增加View和ViewModel文件,将复杂的界面分离成局部的View,局部的View对应局部的ViewModel。
- 易维护(低耦合),视图 View 可以独立于 Model 变化和修改,ViewModel 可以绑定到不同的 View 上
- 灵活扩展(可重用性),可以把相同视图逻辑放在一个ViewModel里面,让多个 view 重用视图逻辑
- 易测试,界面向来是比较难于测试的,而现在测试可以针对ViewModel来写
- 独立开发,开发人员可以专注于业务逻辑和数据的开发 ViewModel,设计人员可以专注于页面设计 View
注意事项和常见误区
- ViewModel 应该能够实现跨平台,因此,不应该包含 View 中的内容和引用,也不应该包含前端显示效果以及效果控制
- 前端的交互操作以及控制应该在前端 View 层解决,不应该由 ViewModel 处理
- ViewModel 不应该引用特定系统的类库,应该将对应功能抽象到接口,利用 IOC 注入,在 App 中注册接口的具体实现
MVVM 项目架构
Commands :放置所有的基命令和其他封装的可复用命令类
Converters :放置所有的转换器
Models :放置模型/抽象实体类
Pages :放置所有的页面Page
Styles :放置所有的自定义控件样式
Utils :放置所有的工具类
ViewModels :放置所有的页面绑定的ViewModel
Views :放置所有的窗口类
动画 Animation
综述
动画只是临时更改属性的值,并不真正的改变属性值。
只能为依赖属性应用动画.
本质上,WPF动画只不过是在一段时间间隔内修改渲染方式和依赖项属性值。
WPF动画,分为三种:插值动画、关键帧动画、路径动画
继承关系
插值动画
- 插值动画是指,属性值从某一个值,经过一段时间后,连续变化值另一个值的动画。
- 命名规则:依赖属性类型名+Animation,例如 double 类型动画:DoubleAnimation
- 任何使用线性插值的动画最少需要三个细节: 开始值(From)、结束值(To)、整个动画执行的时间(Duration)
- By 属性用于创建按设置的数量改变值的动画,By 和 From / To 任意设置一个
关键帧动画
对于属性类型为离散量类型的,因为无法进行插值运算。因此只能填充“帧”。
命名规则:依赖属性类型名+AnimationUsingKeyFrames
帧对象命名规则:<插值方法><类型>KeyFrame
插值方法: 是帧对象使用的插值方法, 如: 离散(Discrete)、线性(Linear) 、样条(Spline) 等。
类型: 是动画的值类型, 例如Double、Decimal等。
关键帧对象可以指定关键时间 KeyTime
- 可以指定为时间值、百分比或特殊值Uniform或Paced
- Uniform 每个关键帧花费相同的时间
<LinearDoubleKeyFrame Value="100" KeyTime="Uniform" />
- Paced 以恒定速率进行动画处理
如果两个具有不同内插的关键帧动画彼此跟随,第二个关键帧的内插方法将用于创建从第一个值到第二个值的过渡
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="ComboAnimatedTranslateTransform" Storyboard.TargetProperty="X" Duration="0:0:15" RepeatBehavior="Forever"> <DiscreteDoubleKeyFrame Value="500" KeyTime="0:0:7" /> <LinearDoubleKeyFrame Value="200" KeyTime="0:0:10" /> <SplineDoubleKeyFrame Value="350" KeyTime="0:0:15" KeySpline="0.25,0.5 0.75,1" /> </DoubleAnimationUsingKeyFrames>
路径动画
- 指让某个元素用来沿着路径的方向进行变换的动画。
- 依赖属性类型名+AnimationUsingPath
Timeline类
所有动画类继承自 Timeline 类
属性名称 说明 BeginTime 设置将被添加到动画开始之前的延迟时间(TimeSpan类型) Duration 使用Duration对象设置动画从开始到结束的运行时间 SpeedRatio 提高或减慢动画速度 AccclerationRatio
DecelerationRatio使动画不是线性的,从而开始时较慢,然后增速(AcelerationRatio 属性值),结束时降低速度(DecelerationRatio属性值)。
这两个属性的值都在0~1之间。AutoReverse 如果为true, 当动画完成时会自动反向播放,返回到原始值。 FillBehavior 当动结束时如何操作 RepeatBehavior 使用指定的次数或时间间隔重复动画
代码创建动画
基本步骤
// 1. 创建故事板 Storyboard storyboard = new Storyboard(); // 2. 定义动画内容 DoubleAnimation doubleAnimation = new DoubleAnimation(); // 创建插值动画 doubleAnimation.From = 0; // 起始值 doubleAnimation.To = 100; // 目标值 doubleAnimation.Duration = TimeSpan.FromSeconds(1); // 动画持续时间 doubleAnimation.AutoReverse = true; // 自动反转播放 doubleAnimation.RepeatBehavior = RepeatBehavior.Forever; // 永远循环 // 3. 将动画绑定到控件 Storyboard.SetTarget(doubleAnimation, btn_sample); // 绑定到指定按钮 Storyboard.SetTargetProperty(doubleAnimation, new System.Windows.PropertyPath("Width")); // 绑定到按钮的 Width 依赖属性 // 4. 添加到故事板并播放 storyboard.Children.Add(doubleAnimation); storyboard.Begin();
旋转动画
// 为控件添加旋转属性 btn_sample.RenderTransform = new RotateTransform(0,0,0); // 创建动画修改属性 DoubleAnimation doubleAnimation2 = new DoubleAnimation(); doubleAnimation2.By = 360; doubleAnimation2.RepeatBehavior = RepeatBehavior.Forever; doubleAnimation2.Duration = TimeSpan.FromSeconds(3); // 绑定到指定按钮 Storyboard.SetTarget(doubleAnimation2, btn_sample); // 绑定到依赖属性 Storyboard.SetTargetProperty(doubleAnimation2, new PropertyPath("(UIElement.RenderTransform).(RotateTransform.Angle)")); storyboard.Children.Add(doubleAnimation2);
平移动画(与旋转动画类似)
// 为控件添加属性 btn_sample.RenderTransform = new TranslateTransform(0,0); // 绑定到依赖属性 Storyboard.SetTargetProperty(doubleAnimation2, new PropertyPath("(UIElement.RenderTransform).(TranslateTransform.X)"));
Xaml 创建动画
基本步骤
<Window.Resources> <!-- 1. 创建画板 --> <Storyboard x:Key="WidthScaleAnimation"> <!-- 2. 创建动画并设置目标及属性 --> <DoubleAnimation AutoReverse="True" RepeatBehavior="Forever" Storyboard.TargetName="WidthScale" Storyboard.TargetProperty="Width" From="0" To="100" Duration="0:0:3" /> </Storyboard> </Window.Resources> <!-- 3. 创建动画元素 --> <Rectangle x:Name="WidthScale" Grid.Row="1" Width="100" Height="30" Fill="Red"> <!-- 4. 创建触发器 --> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.MouseLeftButtonDown"> <!-- 5. 绑定静态资源并开始动画 --> <BeginStoryboard Storyboard="{StaticResource WidthScaleAnimation}" /> </EventTrigger> </Rectangle.Triggers> </Rectangle>
使用关键帧创建移动动画
<Window.Resources> <Storyboard x:Key="TranslateAnimation"> <!-- 关键帧动画没有 From 、 To 属性 --> <DoubleAnimationUsingKeyFrames AutoReverse="True" RepeatBehavior="Forever" Storyboard.TargetName="MyAnimatedTranslateTransform" Storyboard.TargetProperty="X" Duration="0:0:5"> <!-- 定义关键时间点对应的参数值 --> <LinearDoubleKeyFrame KeyTime="0:0:0" Value="0" /> <LinearDoubleKeyFrame KeyTime="0:0:2" Value="50" /> <LinearDoubleKeyFrame KeyTime="0:0:5" Value="100" /> </DoubleAnimationUsingKeyFrames> </Storyboard> </Window.Resources> <Rectangle x:Name="WidthScale" Grid.Row="1" Width="100" Height="30" Fill="Red"> <Rectangle.RenderTransform> <TranslateTransform x:Name="MyAnimatedTranslateTransform" /> </Rectangle.RenderTransform> <Rectangle.Triggers> <EventTrigger RoutedEvent="Rectangle.MouseLeftButtonDown"> <BeginStoryboard Storyboard="{StaticResource TranslateAnimation}" /> </EventTrigger> </Rectangle.Triggers> </Rectangle>
行为 Behavior
综述
- 构建封装了一些通用用户界面功能的行为,可以是基本功能或复杂功能,将它们添加到任意应用程序的另一控件中,通过将该控件链接到适当的行为并设置行为属性来实现。
- 安装依赖:
Microsoft.Xaml.Behaviors.Wpf
快速入门
创建行为类,继承 Behavior
// 创建组件高亮显示行为
public class LightedEffectBehavior : Behavior<FrameworkElement>
{
protected override void OnAttached()
{
// 关联对象时执行事件绑定
AssociatedObject.MouseEnter += AssociatedObject_MouseEnter;
AssociatedObject.MouseLeave += AssociatedObject_MouseLeave;
}
protected override void OnDetaching()
{
// 解除对象关联时取消事件绑定
AssociatedObject.MouseEnter -= AssociatedObject_MouseEnter;
AssociatedObject.MouseLeave -= AssociatedObject_MouseLeave;
}
private void AssociatedObject_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
// 鼠标离开时的具体行为
var ele = sender as FrameworkElement;
ele.Effect = new DropShadowEffect() { Color = Colors.Transparent, ShadowDepth = 0 };
}
private void AssociatedObject_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
{
// 鼠标进入时的具体行为
var ele = sender as FrameworkElement;
ele.Effect = new DropShadowEffect() { Color = Colors.Red, ShadowDepth = 0 };
}
}
关联到控件
<Window
...
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:local_behaviors="clr-namespace:MyApp.Behaviors">
<StackPanel Orientation="Vertical">
<TextBlock Text="Behavior Test">
<i:Interaction.Behaviors>
<local_behaviors:LightedEffectBehavior />
</i:Interaction.Behaviors>
</TextBlock>
</StackPanel>
</Window>
验证器 ValidationRule
快速入门
创建验证规则,继承 ValidationRule
internal class InputValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (string.IsNullOrEmpty(value?.ToString()))
return new ValidationResult(false, "请输入内容");
return new ValidationResult(true, value.ToString());
}
}
添加验证规则到控件
- NotifyOnValidationError : 验证失败时进行通知,可以在其他行为中接收到错位
- UpdateSourceTrigger : 每一次绑定源进行更新时将进行控件更新
- ValidatesOnTargetUpdated : 当控件更新时进行验证
<TextBox Width="100">
<TextBox.Text>
<Binding NotifyOnValidationError="True" Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local_vm:InputValidationRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
验证错误通知
// 创建行为,用于接收验证错误并处理
internal class InputValidationErrorBehavior:Behavior<FrameworkElement>
{
protected override void OnAttached()
{
AssociatedObject.AddHandler(Validation.ErrorEvent, new EventHandler<ValidationErrorEventArgs>(OnValidationError));
}
private void OnValidationError(object sender, ValidationErrorEventArgs e)
{
// 错误处理程序
MessageBox.Show(e.Error.ErrorContent.ToString());
}
protected override void OnDetaching()
{
AssociatedObject.RemoveHandler(Validation.ErrorEvent, new EventHandler<ValidationErrorEventArgs>(OnValidationError));
}
}
<Window ...>
<!-- 将处理行为关联到 Window 窗口 -->
<i:Interaction.Behaviors>
<local_behaviors:InputValidationErrorBehavior />
</i:Interaction.Behaviors>
...
</Window>
转换器 Converter
实现源数据和目标对象数据的转换
分为值转化器和多值转换器
值转化器 实现IValueConverter
多值转换器需要实现 IMultiValueConverter 接口,具体实现与 IValueConverter 类似,只是传递的 value 是数组 values
当 Mode=TwoWay 时,使用 ConvertBack 将界面输入字符转换为绑定的类型
Binding 的 ConverterParameter 属性可以向 Converter 传递参数
基本使用方法
实现转化器接口
internal class SexValueConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (bool)value ? "男" : "女"; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value?.ToString() == "男") return true; else if (value?.ToString() == "女") return false; else return null; } }
添加到资源
<Window.Resources> <converter:SexValueConverter x:Key="SexValueConverter" /> </Window.Resources>
控件添加转换器
<TextBox Text="{Binding Sex, Mode=TwoWay, Converter={StaticResource SexValueConverter}, ConverterParameter=SexInputParam}" />
带属性的转化器
internal class BooleanToVisibilityConverter : IValueConverter
{
public bool Reversed { get; set; } = false;
public bool UseHidden { get; set; } = false;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is bool b)
{
if (Reversed) b = !b;
if (b) return Visibility.Visible;
else if (UseHidden) return Visibility.Hidden;
else return Visibility.Collapsed;
}
throw new ArgumentException(nameof(value));
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value;
}
<Window.Resources>
<converters:BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" Reversed="False" UseHidden="True" />
</Window.Resources>
<Border Width="100" Height="100" Background="Red"
Visibility="{Binding ElementName=Check, Path=IsChecked, Converter={StaticResource BooleanToVisibilityConverter}}" />
<CheckBox x:Name="Check" Margin="10" Content="show border" IsChecked="True" />
继承 MarkupExtension 的转换器
internal class BooleanToVisibilityConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider) => this;
/* other code */
}
<!-- 可以使用标记表达式,无需再使用 Resources 定义 -->
<Border Width="100" Height="100" Background="Red" Visibility="{Binding ElementName=Check, Path=IsChecked, Converter= {converters:BooleanToVisibilityConverter UseHidden=True}}" />
装饰器 Adorner
编写装饰器类
internal class ButtonAdorner : Adorner { public ButtonAdorner(UIElement adornedElement) : base(adornedElement) { } // 渲染控件时执行函数 protected override void OnRender(DrawingContext drawingContext) { // 获取控件尺寸及角点 Rect rect = new Rect(this.AdornedElement.RenderSize); var tl_pt = new Point (rect.TopLeft.X - 3, rect.TopLeft.Y - 3 ); var tr_pt = new Point (rect.TopRight.X + 3, rect.TopRight.Y - 3); var bl_pt = new Point(rect.BottomLeft.X - 3, rect.BottomLeft.Y + 3); var br_pt = new Point (rect.BottomRight.X + 3, rect.BottomRight.Y + 3); // 创建笔触 Pen pen = new Pen(new SolidColorBrush(Colors.Red), 1); // 绘制图像 drawingContext.DrawLine(pen,tl_pt,new Point(tl_pt.X + 5, tl_pt.Y)); drawingContext.DrawLine(pen,new Point(tl_pt.X, tl_pt.Y + 5),tl_pt); drawingContext.DrawLine(pen, tr_pt, new Point(tr_pt.X - 5, tr_pt.Y)); drawingContext.DrawLine(pen, new Point(tr_pt.X, tr_pt.Y + 5), tr_pt); drawingContext.DrawLine(pen, bl_pt, new Point(bl_pt.X , bl_pt.Y-5)); drawingContext.DrawLine(pen, new Point(bl_pt.X+5, bl_pt.Y), bl_pt); drawingContext.DrawLine(pen, br_pt, new Point(br_pt.X, br_pt.Y - 5)); drawingContext.DrawLine(pen, new Point(br_pt.X - 5, br_pt.Y), br_pt); } }
控件
<!-- 将控件关联到 Command,并传入 TextBlock 到调用函数 --> <TextBlock x:Name="text_box" FontSize="30" Text="Text Adorner"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <i:InvokeCommandAction Command="{Binding AddAdornerCommand}" CommandParameter="{Binding ElementName=text_box}" /> </i:EventTrigger> </i:Interaction.Triggers> </TextBlock>
添加到控件
// 注册命令 AddAdornerCommand = new DelegateCommand<object>(AddAdorner); // 添加装饰器 public void AddAdorner(object e) { var vis = e as UIElement; var layer = AdornerLayer.GetAdornerLayer(vis); layer.Add(new ButtonAdorner(vis)); }
触发器 Trigger
概念
当达到了触发的条件, 那么就执行预期内的响应, 可以是样式、数据变化、动画等。
触发器通过 Style.Triggers集合连接到样式中,Triggers可以设置EventTrigger,但是只能是 RouteEvent(路由事件)
Interaction.Triggers 更加灵活,但是不能定义为资源
触发器都是 System.Windows.TriggerBase的派生类实例
类型
- Trigger : 监测依赖属性的变化
- MultiTrigger : 监测多个条件
- DataTrigger : 监测数据的变化
- MultiDataTrigger : 监测多个数据条件
- EventTrigger : 事件触发器, 触发了某类事件时, 触发器生效
实例
<Window.Resources>
<Style x:Key="textStyle" TargetType="TextBlock">
<!-- 当鼠标位于控件上时,将背景设置为灰色 -->
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="Gray"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<StackPanel>
<TextBlock Style="{StaticResource textStyle}" Text="text 01"/>
<TextBlock Style="{StaticResource textStyle}" FontSize="30" Text="text 02"/>
</StackPanel>
Interaction.Triggers
安装依赖:
Microsoft.Xaml.Behaviors.Wpf
引入命名空间
<window xmlns:i="http://schemas.microsoft.com/xaml/behaviors" xmlns:local_behaviors="clr-namespace:MyApp.Behaviors"> </window>
绑定方法
<Button Width="100" Margin="30" Content="click"> <i:Interaction.Triggers> <i:EventTrigger EventName="Click"> <i:CallMethodAction MethodName="BtnClick" TargetObject="{Binding}" /> </i:EventTrigger> </i:Interaction.Triggers> </Button>
绑定 Command
<DataGrid ItemsSource="{Binding FileItems}" SelectionUnit="FullRow"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseDoubleClick"> <i:InvokeCommandAction Command="{Binding OpenDirCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=CurrentItem}" /> </i:EventTrigger> </i:Interaction.Triggers> </DataGrid>
public DelegateCommand<object> OpenDirCommand { get => new DelegateCommand<object>((e) => { var file = (FileItem)e; if(file != null && file.IsDir==true) { currentPath = Path.Combine(currentPath!, file.Name!); SetFileItems(currentPath); } }); }