数据、视效与行为
数据
- WPF 中的对象,我觉得大致可以分为两类,一个是前台对象,一个是后台对象,前台对象指的就是 View,后台对象指的就是 ViewModel,View 对象又分为 xaml 状态和 cs 状态,本质是一个 partial 类。
- ViewModel 对象与 View 中的对象是相互独立的,因此需要借助一些手段来让他们关联起来
Resources
- Resources 相当于你当前这个对象下的一个仓库,本质类似于一个 Dictionary,你在其中设置的对象本质就是你实例化了一个对象,然后将其放入了 Dictionary 中
- 当你通过 {StaticResource} 或 {DynamicResource} 去访问时,就会自动去查找最近(父子关系下)的 Resources,如果找不到,就继续往外找,直到找到 APP 对象为止
Binding
-
在 WPF 中,可以通过 “Binding” 机制,来实现将两个数据关联起来,这两个数据既可以是 View 与 ViewModel 之间,也可以是 View 内部的两个对象中的两个属性,并且根据被绑定对象的特点,还能够实现单向通知以及双向通知功能
-
关联机制主要需要注意以下四点
- 目标(Target):UI 元素的依赖属性(如
TextBox.Text
、Button.Content
) - 源(Source):提供数据的对象,通常就是
DataContext
- 路径(Path):指定要绑定到源对象中的哪个属性(如
Person.Name
) - 绑定模式(Mode):决定数据流动方向(OneWay、TwoWay、OneTime 等)
- 目标(Target):UI 元素的依赖属性(如
-
Binding 可以通过以下几种方式选择 Source
- DataContext:这算是一种保底机制,当没有显式指定 Source 时,Binding 会以当前对象或其视觉树外层对象的 DataContext 为目标 Path 的 Source(注意,这里表明了如果没有显式设置 Source,则默认也是去找 DataContext,而不是去找对象自己身上的属性)
- Source:可以通过设置 Source 属性显式绑定一个对象,这个对象可以是在
Resources
中设置好的某个实例,也可以是通过{x: Static }
直接绑定一个静态对象(静态字段、枚举值等) - RelativeSource:这是一种以相对关系去设置 Source 的方法,常用方式有以下三种
- Self:以当前对象自己为 Source
- FindAncestor:向上查找某个特定类型的对象作为 Source
- TemplatedParent:以应用了此模版的对象为 Source
- ElementName:显式指定某个设置了特定 Name 的对象作为 Source
- XPath:当数据源为 XML 时,可以使用的特殊用法
- Reference:某些情况下,当目标对象不在可视化树内时,可能需要通过其和 DataProxy 来进行绑定
- 特殊情况:当父对象是 ListBox 之类的可以设置 ItemSource 的对象时,每个 Item 它们的 Source 默认是这个 ItemSource 中的每个对象
-
可视化树机制:这一点就类似 PhotoShop 之类的图像编辑器中的图层机制一样,WPF 默认有个主可视化树,除此之外,像
Popup
,ContextMenu
,AdornerLayer
等都会形成各自的可视化树(也就类似于其他图层) -
绑定限制:
- 像是
RelativeSource
和ElementName
查找元素时,都会受到可视化树的限制,前者是因为依赖于可视化树的层级结构,当元素不在同一可视化树中时,自然无法通过层级结构查找到,而后者则是受到NameScope
的影响,一个元素在注册x:Name
时,本质是注册到当前可视化树的NameScope
下,因此不同可视化树中无法访问到对方,但是x:Reference
由于是解析 xaml 时读取,因此不受NameScope
影响
- 像是
-
默认值与异步加载:在
Binding
中,可以通过FallbackValue
来为目标属性规定一个源属性未绑定成功或未加载完成(可能此值是需要异步请求的值)时默认显示的值;当FallbackValue
还无法满足需求时,可以使用PriorityBinding
来实现优先级绑定,它会从上到下找到第一个可取值的值作为当前目标属性的值,而无视优先级更低的那些值 -
值转换器(Converter):用于在源值和目标值之间进行类型转换,类需要实现
IValueConveter
来实现不同类型的源属性与目标属性之间的相互转化,如果需要避免重复在Resource
中实现转换实例,可以考虑直接建立一个静态实例 -
UpdateSourceTrigger:用于控制当数据从视图变化传输回 ViewModel 时,它的触发时机,它有以下四个值
PropertyChanged
:当属性发生变化时立刻变化LostFocus
:当控件失去焦点时才变化Explicit
:不自动更新,需要在代码中通过BindingExpression.UpdateSource()
手动触发更新Default
:默认值,大部分可编辑控件是LostFocus
,少部分例如滑动条之类的控件默认为PropertyChanged
-
验证与错误处理
ValidationRules
:通过继承一个ValidationRule
类来实现验证方法,简单,但是只能返回一个错误INotifyDataErrorInfo
:通过实现INotifyDataErrorInfo
接口,能支持复杂的验证逻辑,返回多个错误,但是较为复杂,且Binding
时需要注意将ValidatesOnNotifyDataErrors
设为True
IDataErrorInfo
:也是一个错误接口,比较老的一种实现方法,目前不用,需要将ValidatesOnDataErrors
设为True
ValidatesOnExceptions
:设为True
时支持将setter
中抛出的异常作为错误处理- 可以将
NotifyOnValidationError
设为True
,此时遇到错误时,会触发Validation.Error
事件,如果为其绑定处理逻辑,即可进行一些想要的做(如禁止按钮点击等)
-
多绑定
- 多绑定需要实现
IMultiValueConverter
接口的来决定如何组合多个绑定对象以及输出什么结果
- 多绑定需要实现
-
绑定调试:可以通过在
Binding
时设置PresentationTraceSources.TraceLevel
以及对应等级None, Low, Medium, High
来在输出窗口中显示绑定时的错误信息 -
BindingExpression:它是
Binding
对象在代码中的一个具象对象表示,通常通过控件.GetBindingExpression()
拿到它,你可以通过它来实现UpdateSource()
或UpdateTarget()
来推送给源或目标绑定对象,也可以通过它拿到ResolvedSource
或ResolvedSourceProperty
,当然,非必要的情况下不推荐直接操作它以免打破 Mvvm 自动绑定与更新;你可以通过在代码中尝试拿到它来判断是否绑定成功Binding
的本质可以说是由BindingExpression
持有源对象和目标对象,并且订阅两者中对应的CLR/动态/依赖属性与依赖属性的值变化事件,在收到其中一方的事件时,去修改另一方
// ViewModel中的代码 private string _name; public string Name { get => _name; set { if (_name != value) { _name = value; // 关键:触发事件并携带属性名 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name))); } } } // View 伪代码:WPF内部近似逻辑 void OnPropertyChanged(object sender, PropertyChangedEventArgs e) { if (e.PropertyName == "Name") // 检查是否是绑定的属性 { // 重新获取值并更新UI var newValue = ((ViewModel)sender).Name; TextBlock.SetCurrentValue(TextBlock.TextProperty, newValue); } }
- 关于
BindingExpression
是如何找到源对象和目标对象的属性并获取与修改它们的,首先,目标对象由于必须是依赖属性,所以可以很轻松的通过DepandencyObject.GetValue(TargetDP)
和DependencyObject.SetValue(TargetDP, value)
的方式去处理,而对于CLR 属性
和动态属性(如dynamic
),则一般会通过反射的方式拿到getter
和setter
,并且为了优化考虑,还会使用Expression Trees
或ILGenerator
动态生成代码,将之前拿到的getter
和setter
转换为委托并缓存起来,此时再次访问时就相当于(object instance) => ((User)instance).Name;
属性
- WPF 中的属性大致分为三类:
- 普通属性:既对象身上的属性,一般来说对 WPF 特有功能支持有限,例如数据绑定、样式、动画、属性继承等
- 依赖属性:一种拓展过的属性,支持了更多的 WPF 功能,使用
DependencyProperty.Register
注册到系统内,通过指定ownType
能够控制哪些对象能够使用此依赖属性 - 附加属性:属于依赖属性的一种,本质也是一种拓展过的属性,与依赖属性相比,它主要是用在非
own
的其他对象上
- 依赖属性相当于给一个对象增加一个属性,而附加属性相当于给对象增加一个标记
- 访问依赖属性时,可以直接像访问普通属性一样通过
.
进行访问,也可以通过GetValue()
或SetValue()
进行访问;访问附加属性时,需要通过own
下的静态方法SetXXX()
或GetXXX()
进行访问 - 附加属性通常用来拓展行为,详情在
行为
一节中进行讨论
视效
- WPF 中的视觉效果主要通过控件身上的依赖属性以及其对应值决定
Style
Style
是 WPF 中一种预定义的视效模板,它的本质也是一种对象Style
中可以指定 TargetTpye,以便在定义时,能够设定目标 Type 上的依赖属性值,此外在不定义x:Key
此 Style 会自动成为隐式样式,默认应用于此作用域下的所有对应类型上
模板
- 可以通过模板来规划整个控件的显示结构,目前常见的模板有以下三种
- ControlTemplate:彻底改变控件的视觉树结构, 让你能自定义整个控件的显示,其中有个很关键的子对象
ContentPresenter
,用此对象能够规定控件中能够让外部定义的 “内容” 的所处区域,在ControlTemplate
中,通常会使用到TemplateBinding
来关联到 TargetType 上的依赖属性 - DataTemplate:它的作用是指定被
Content
绑定的对象在界面中是如何显示的,相比于ControlTemplate
只能适用于 ControlContent 及其派生类,它可以适用于任意对象;它通常应用于ContentTemplate
或ItemTemplate
属性;它也类似 Style,能够指定特定类型,它使用的关键字是DataType
,并且它在只使用DataType
而不使用x:Key
也能隐式应用于所有作用域下对应的实例对象 - ItemsPanelTemplate:它主要用于
ItemsControl
派生类,用于规定这些集合类的子对象如何在界面上排布,应用于ItemsPanel
属性
- ControlTemplate:彻底改变控件的视觉树结构, 让你能自定义整个控件的显示,其中有个很关键的子对象
ControlTemplate
中通常使用TemplateBinding
关联控件的属性,而DataTemplate
则更多是用Binding
关联数据对象上的属性- ControlTemplate + TemplateBinding:关联控件自身的属性(模板→控件)。
- DataTemplate + Binding:关联数据对象的属性(模板→数据)。
posted on 2025-07-17 00:13 HutatsuiwaKaede 阅读(11) 评论(0) 收藏 举报