WPF是什么?
WPF是Windows Presentation Foundation的简称,是用来专门编写程序表示层的技术
XAML(Extensible Application Markup Language):可扩展应用程序标记语言,类似于HTML+CSS+JavaScript的组合
XAML是WPF技术中专门用来设计UI的语言
XAML是一种由XML派生而来的语言,所以很多XML中的概念在XAML是通用的
XAML是一种"声明"式语言,当看到一个标签,就意味着声明了一个对象,对象之间的层级关机要么是并列、要么是包含,全都体现在标签的关系上
xmlns是XML-Namespace的缩写,定义名称空间的好处是,当来源不同的类同名时,可以使用名称空间加以区分。XML特征的语法格式:xmlns[:可选的映射前缀]="名称空间"
x:Class这个Attribute的作用是当XAML解析器将包含它的标签解析成C#类后,这个类的类名是什么
在XAML中,每个标签对应.NET Framework类库中的一个控件类
XAML使用树形逻辑结构来描述UI
因为XAML语言不能编写程序的运行逻辑,所以一份XAML文档中除了使用标签声明对象就是初始化对象的属性
XAML中为对象属性赋值共有两种语法:
将逻辑代码与UI代码分离、隐藏在UI代码后面的形式就叫做"代码后置"
语法:xmlns:映射名="clr-namespace:类库中名称空间的名字;assembly=类库文件名"
x名称空间里的成员是专门写给XAML编译器看、用来引导XAML编译器把XAML代码编译成CLR代码的
x名称空间中包含的类均与解析XAML语言相关
与C#语言一样,XAML也有自己的编译器。XAML语言会被解析并编译,最终形成微软中间语言存储程序集中。
x名称空间中包含的工具:
在使用XAML编程的时候,如果你想给它加上一些特殊的标记从而影响XAML编译器对它的解析,这时候就需要额外为它添加一些Attribute。
4.2.1 x:Class
这个Attribute的作用是告诉XAML编译器将XAML标签的编译结果与后台代码中指定的类合并。在使用x:Class时必须遵守以下要求:
4.2.2 x:ClassModifier
这个Attribute的作用是告诉XAML编译由标签编译生成的类具有怎样的访问控制级别
使用这个Attribute时需要注意:
4.2.3 x:Name
XAML是一种声明式语言,那么XAML标签声明的是什么呢?
XAML的标签声明的是对象,一个XAML标签对应着一个对象,这个对象一般是一个空间类的实例
x:Name的作用:
另外注意:如果标签本身有Name的属性(继承自FrameworkElement类),那么该属性和x:Name的作用是一样的,但是一个标签中只能同时用一个,为了代码可读性,后面统一使用x:Name
4.2.4 x:FiledModifier
XAML标签中的使用x:Name产生的实例的访问权限默认是Internal
x:FiledModifier是用来在XAML里改变引用变量访问级别的
4.2.5 x:Key
在WPF中,几乎每个元素都有自己的Resources属性,这个属性是个"Key-Value"式的集合,只要把元素放进这个集合,这个元素就成为资源字典中的一个条目,x:Key的作用就是为资源贴上用于检索的索引
4.2.6 x:Shared
x:Shared一定要与x:Key配合使用,如果
4.3.1 x:Type
当我们在XAML中想表达某个数据类型时就需要使用x:Type标记扩展
例子:
①自定义Button:
②创建一个MyWindow1页面:
②创建一个MyWindow2页面,使用x:Type指定数据类型为MyWindow1:
4.3.2 x:Null
当一个属性具有默认值而我们又不需要这个默认值时,就需要显式地设置null值
例子:
为所有Button设置同样的样式,但是又想让个别Button不具有这个样式时,可以使用x:Null将默认样式设置为null
4.3.4 x:Array
x:Array的作用就是向它的Items属性向使用者暴露一个类型已知的ArrayList实例,ArrayList内成员的类型是通过x:Array的Type指明
4.3.5 x:Static
在XAML文档中使用数据类型的static成员
例子:
①定义一个静态字段和一个静态字段在处理逻辑的partial类中:
②在XAML中获取静态数据:
XAML一共有两个指定元素:
x:Code:可以在此标签中包含一些放置在后置代码中的C#代码,这样做的好处是不用把XAML代码和C#代码分置在两个文件中,但若不是遇到某些极端环境尽量不要这样做,因为这样做产生的问题是代码不好维护且不易调试
例子:
①在XAML中使用x:Code编写逻辑代码:
②之后通过XAML编译器将XAML解析成C#代码之后可以看到这块代码:
x:XData:
在x:XData元素内的元素XAML不会将其中的XML元素视为XAML命名空间或任何其他XAML命名空间的一部分,x:XData可以包含任意格式良好的XML
例子:
①定义数据源:
②使用数据源:
WPF之所以能够称得上是新一代关键在于两点:
控件定义:能够展示数据、响应用户的操作
在WPF中谈控件,我们关注的应该是抽象的数据和行为而不是控件具体的形象
控件的派生关系:
FrameworkElement类在UIElement类的基础上添加了很多专门用于WPF开发的API,所以从这个类开始才算是进入WPF开发框架
WPF UI元素分为如下类型:
对于内容控件,XAML标签的内容区域专门映射了控件的内容属性
①Button(Content):
②StackPanel(Children):
③ListBox(Items):
族:符合某类内容模型的UI元素
每个族用它们共同基类来命名
5.3.1 ContentControl族
特点:
ContentControl族包含的控件:
5.3.2 HeaderedContentControl族
特点:
HeaderedContentControl族包含的控件:
GroupBox例子:
5.3.3 ItemsControl族
特点:
ItemsControl族包含的控件:
在通过XAML编写UI的时候,才发现其实我们用很简单的几个标签就可以表达一个控件,但是通过VisualTreeHelper去查找一个标签的父级标签发现有好多隐藏的标签,这可能已经被XAML编译器给自动补全了(也就是ItemsControl能够使用对应的Item Container自动包装数据),如下例子:
虽然直接在Items下加入各种Item如Button不加任何Panel、Border也说得过去,但是编译器这样优化应该是有一定的理论与逻辑支撑在的
ItemsControl对应的Item Container:
5.3.4 HeaderedItemsControl族
特点:
本族控件有:MenuItem、TreeViewItem、ToolBar
5.3.5 Decorator族
特点:
Decorator族元素:
5.3.6 TextBlock和TextBox
这两个控件最主要的功能是显示文本
TextBlock
TextBox
5.3.7 Shape族元素
用来在UI上绘制图形的一类元素
特点:
绘制一个仿3D球体:
5.3.8 Panel族元素
作用是控制UI布局
特点:
Panel族元素:
5.4.2 Grid
Grid元素会以网格的形式对内容元素们进行布局
特点:
Grid中行的高度和列的宽度的单位:
对于Grid的行高和列宽,我们可以设置三类值:
Grid例子:
5.4.5 DockPanel
DockPanel内的元素会被附加上DockPanel.Dock这个属性,这个属性的数据类型为Dock枚举。Dock枚举可取Left、Top、Right和Bottom四个值。根据Dock属性值,DockPanel内的元素会向指定方向累积、切分DockPanel内部的剩余可用空间,就像船舶靠岸一样。DockPanel还有一个重要属性——bool类型的LastChildFill,它的默认值是True。当LastChildFill属性的值为True时,DockPanel内最后一个元素的DockPanel.Dock属性值会被忽略,这个元素会把DockPanel内部所有剩余空间充满。这也正好解释了为什么Dock枚举类型没有Fill这个值。
例子:
我们可以通过每次添加一个Item来看整个布局的变化,以此了解DockPanel的布局逻辑
①向DockPanel中添加第一个TextBox:
虽然设置了Dock为Top,按理说这个文本框会置顶,但是因为DockPanel有一个LastChildFill属性导致Dock属性失效,然后如果没设置Width,就默认会以整体窗体的Width大小为准
②向DockPanel中添加第二个TextBox:
当添加第二个TextBox之后,第一个TextBox就置顶了,那么第二个TextBox其实又从剩下的位置(除了第一个TextBox的位置)来设置布局,同理,第二个TextBox的Dock的Left失效
③向DockPanel中添加第三个TextBox:
把LastChildFill设为false,最后一个将不会填满剩下的窗体位置:
可以看到,最后一个TextBox停靠在剩余的窗体左边,这里我没有设置TextBox的Height和Width,不清楚这个默认的长和宽是怎么来的
5.4.6 WrapPanel
WrapPanel内部采用的是流式布局。WrapPanel使用Orientation属性来控制流延伸的方向,使用HorizontalAlignment和VerticalAlignment两个属性控制内部控件的对齐。在流延伸的方向上,WrapPanel会排列尽可能多的控件,排不下的控件将会新起一行或一列继续排列。
例子:
第六个Button在第一行的宽度已经不够了,所以他们流向第二行
启动程序之后,改变窗体的宽度,可以看到Button按钮会自适应的满足一行最多可以放几个Button,多余的会继续向下一行流
UI驱动程序:程序是被来自UI的事件(即封装过的消息)驱使向前的,简称“消息驱动”或“事件驱动”
Binding一词更注重表达它是一种像桥梁一样的关联关系,WPF中,正是在这段桥梁上我们有机会为往来流通的数据做很多事情
对于绑定的解释,此书太清晰了:如果把Binding比作数据的桥梁,那么它的两端分别是Binding的源(Source)和目标(Target)。数据从哪里来哪里就是源,Binding是架在中间的桥梁,Binding目标是数据要往哪儿去(所以我们就要把桥架向哪里)。一般情况下,Binding源是逻辑层的对象,Binding目标是UI层的控件对象,这样,数据就会源源不断通过Binding送达UI层、被UI层展现,也就完成了数据驱动UI的过程。“一桥飞架南北,天堑变通途”,我们可以想象Binding这座桥梁上铺设了高速公路,我们不但可以控制公路是在源与目标之间双向通行还是某个方向的单行道,还可以控制对数据放行的时机,甚至可以在桥上架设一些“关卡”用来转换数据类型或者检验数据的正确性。
例子:
①增加一个数据源
②这里要说一个概念,Binding的路径(Path),这个就是UI上的元素关心的那个属性值的变化。那么我们在数据源中做的就是当值变化后属性要有能力通知Binding,让Binding把变化传递给UI元素,方法就是实现INotifyPropertyChanged接口然后在属性的set语句中激发一个PropertyChanged事件,当为Binding设置了数据源后,Binding就会自动侦听来自这个接口的PropertyChanged事件
③在窗体逻辑类中加入Binding代码:
简易写法是因为各个控件都继承了FrameworkElement基类,这个基类有个binding方法
其实这里控件能及时取得更新的数据还是依靠了委托做回调
Binding模型
Binding源:是一个对象并且通过属性公开自己的数据
6.3.1 把控件作为Binding源与Binding标记扩展
有时候为了让UI元素产生一些联动效果也会使用Binding在控件间建立关联
例子:
这里使用了Binding的标记扩展语法 {Binding Value }的写法和{Binding Path=Value}是一样的,因为Path是Binding对象中构造函数的形参所以形参名可忽略不写,ElementName是Binding对象的一个属性,保存的是控件的Name
6.3.2 控制Binding的方向及数据更新
Binding.Mode,这是个枚举对象,用于确定数据流向,有5个枚举值:
Binding.UpdateSourceTrigger,这也是个枚举,什么时刻数据开始流动,有4个枚举值:
例子:
在文本框中写成80,焦点还在文本框中,滑动块也还在初始位置
移除焦点,滑动块移动到指定位置
6.3.3 Binding的路径(Path)
Binding的多级路径:
就是不仅可以将一个TextBox对象的Text作为path还可以将Text.Length作为Path,但要注意,Length是string下面的只读属性,如果现在有从textBox2.length流向textBox1.length的逻辑的时候,就会抛出异常,所以需要使用Binding.Mode来控制数据流向
奇怪的Path:
感觉Path的语法好奇怪呀,没有太get到逻辑在哪里,以一个List为source为例:
path如果是"/"那么取的就是list中的第一个值,为什么??
6.3.4 "没有Path"的Binding
Binding源本身就是数据且不需要Path来指明。典型的,string、int等基本类型就是这样,这时我们只需将Path的值设置为“.”就可以.在XAML代码里这个“.”可以省略不写,但在C#代码里却不能省略。
例子:
XAML:
点(.)也可以直接省去
C#:
6.3.5 为Binding指定源(Source)的几种方法:
把依赖对象(Dependency Object)指定为Source:依赖对象不仅可以作为Binding的目标,同时也可以作为Binding的源。这样就有可能形成Binding链。依赖对象中的依赖属性可以作为Binding的Path。
上面的Binding链举例子就是:
Student.Name改变导致textBox1.TextProperty改变,textBox1的TextProperty改变,导致TextBox2.TextProperty改变...马上,依赖对象在这些控件类中作为静态属性的存在,互相绑定有意义吗..自己绑定自己?
其实目标 <- 目标(源) <- 目标,这样的形式也可以制造Binding链,只要有属性即作为源又作为目标即可
6.3.6 没有Source的Binding-使用DataContext作为Binding的源
首先,DataContext属性是FrameworkElement的属性,而FrameworkElement是控件的基类,所以每个控件都有这个属性,而又因为这个属性是依赖属性、xaml的UI同时也是一个控件树的结构,所以,如果上层的树(控件)节点有对这个DataContext有赋值的话,那么它会判断它的子节点(控件)有没有对这个值赋值,如果没有赋值,就会给这个节点赋上值,这是一种依赖属性的值传递行为,而又因为使用xaml做Binding时,如果不设定源的话,那么xaml就会去找DataContext属性作为Source
例子:
①创建Student类 :
②为外层的StackPanel对象的DataContext属性赋Student对象值,然后为内部的TextBlock控件的Text属性做一个没有Source的Binding:
可以看到,Student的属性正常显示在窗体上
那么我们把StackPanel.DataContext赋于string、int等基础类型的值,Binding可以怎么设置呢?
我们就可以使用{Binding}这种奇怪的语法了
DataContext的用法:
6.3.7 使用集合对象作为列表控件的ItemsSource:
①为ListBox指定一个集合,并选择集合中的某一个元素显示:
这里重点注意DisplayMemberPath这个属性 ,ListBox会用这个属性作为Binding的Path,然后Binding的目标是ListBoxItem的内容插件(TextBox)
在使用集合类型作为列表控件的ItemsSource时一般会考虑使用ObservableCollection
6.3.9 使用XML数据作为Binding的源
XML文本是树形结构的,所以XML可以方便地用于表示线性集合和树形结构数据
当使用XML数据作为Binding的Source时我们将使用XPath属性而不是Path属性来指定数据的来源
例子:
①一个包含学生列表的XML文件:
②在XAML中为列表控件ListView的内容指定Binding的Path
③为Binding指定Source和Path,并将ListView.ItemsSourceProperty作为Binding的target
我们还可以直接将XML内容写在XAML中,只需要使用x:XData指令元素即可:
效果:
6.3.10 使用LINQ检索结果作为Binding的源
使用LINQ的目的是用于对集合数据做一下处理而已,其实和Binding本身关系并不大,那么这里只要看一下如何使用Linq做数据处理就好了
使用Linq处理集合:
目前习惯使用lambda的方式来使用Linq,用起来也非常爽,原生的这种类似sql的语法结构就不太懂了
使用Linq处理XML集合,这里因为XAML binding的是Student对象的Id和Name所以要从XML对象中取出相应的值到Student对象中:
这里顺便梳理了一下Linq的Where、Foreach和Select:
6.3.11 使用ObjectDataProvider对象作为Binding的Source
ObjectDataProvider对应与被包装对象的关系:
一个例子:三个TextBox框,对上面两个框输入值之后,两个框的值的和会立马体现在第三个框上:
①定义一个Calculator类,内部有一个Add方法:
②使用XAML画出三个TextBox文本框:
③对三个TextBox文本框设置Binding:
前两个Binding的数据流向主要是从TextBox.Text流向Source,这里主要是注意Binding的UpdateSourceTrigger属性,这个属性是个枚举值,意思是数据更新到Source的时机,对于TextBox来说,默认是输入值到TextBox释放焦点的时候,但是通过为这个值设置成PropertyChanged之后,就是输入值那一刻就会更新Source,这样两个文本框的值的和就会立刻回显到第三个TextBox文本框中;第三个Binding的path是点(.),点的意思是Source本身就是数据
结果:
上面的前面两个Binding很明显,ObjectDataProvider看上去更像Binding的Target,因为数据总是从TextBox -> ObjectDataProvider,但是不将ObjectDataProvider设置为Binding的Target有两个原因:
6.3.12 使用Binding的RelativeSource:
有些时候我们不能确定作为Source的对象叫什么名字,但知道它与作为Binding目标的对象在UI布局上有相对关系,比如控件自己关联自己的某个数据、关联自己某级容器的数据。这时候我们就要使用Binding的RelativeSource属性
画一个被很多布局嵌套的TextBox,然后为这个TextBox的Text属性使用Binding语法赋值:
RelativeSource的构造函数接受一个RelativeSourceMode参数,这个参数定义Binding的target(在这是TextBox)和Binding的Source(这里是Grid)的相对位置,AncestorType说明Binding的是什么类型的控件,AncestorLevel属性指的是以Binding目标控件为起点的层级偏移量
使用代码做binding(or 上面直接在XAML做Binding):
如果想Binding自身属性,可以使用RelativeSourceMode.Self
6.4.1 Binding的数据校验
Binding的ValidationRules属性是Collection
例子:
①实现一个ValidationRule的派生类:
②界面两个控件,一个TextBox一个Slider:
③将Slider的Value值Binding到TextBox的Text属性,并将RangeValidationRule加入到Binding.ValidationRules中:
④运行程序:
当输入1000后,文本框显示红色边框,提醒用户输入有误。通过调试,发现这个ValidationRule是从target -> source的一个校验,当然,ValidationRule类的Summary也是这么说的:User input
6.4.2 Binding的数据转换
当某些数据在从Binding的Target到Source或Source到Target数据类型不一致并且wpf类库不能做到自动帮我们转型时,那么我们需要手动做转型,这就会用到一个接口IValueConverter,里面有两个方法,一个是Convert,一个是ConvertBack,从Source到Target是实现Convert方法,从Target到Source是实现ConvertBack方法
例子:在一个列表中向用户显示飞机的状态:
①定义数据类型:
②加入两个飞机图片资源
③实现转换接口的派生类:
④界面:
⑤后台代码:
⑥运行程序:
例子:比较常见的一个场景,就是有些Button需要我们输入多个文本框且文本框的内容相同时Button才可点
①需要一个转换器,将输入的多个结果转换为一个bool值:
4个TextBox,一个Button,当第一个的TextBox和第二个的Text并且第三个TextBox和第四个的Text一样时,Button可点,否则置灰状态(Button.IsChecked=false)
②界面:
③代码:
④运行程序:
依赖属性就是一种可以自己没有值,并能通过使用Binding从数据源获得值(依赖在别人身上)的属性
拥有依赖属性的对象被称为依赖对象
依赖属性的好处:
7.2.1 依赖对内存的使用方式
传统的.NET开发中,一个对象所占用的内存空间在调用new操作符进行实例化的时候就已经决定了,而WPF允许对象在被创建的时候并不包含用于存储数据的空间(即字段所占用的空间)、只保留在需要用到数据时能够获得默认值、借用其他对象数据或实时分配空间的能力——这种对象就称为依赖对象(Dependency Object)而它这种实时获取数据的能力则依靠依赖属性(Dependency Property)来实现。WPF开发中,必须使用依赖对象作为依赖属性的宿主,使二者结合起来,才能形成完整的Binding目标被数据所驱动
在WPF系统中,依赖对象的概念被DepencyObject类实现(控件都隐式实现),依赖属性则由DepencyProperty类所实现
DependencyProperty必须以DependencyObject为宿主、借助它的SetValue和GetValue方法进行写入与读取,因此想要自定义DependencyProperty,宿主一定是DependencyObject的派生类
例子:
修饰符的约定是public static readonly
获取实例是使用DependencyProperty.Register方法,方法的第一个参数是来指明以哪个CLR属性作为这个依赖属性的包装器
包装器(CLR属性)的作用:
以实例属性的形式向外界暴露依赖属性,这样一个依赖属性才能成为数据源的一个Path
使用一个包装器(CLR属性)去包装NameProperty:
这就是为什么我们在Binding的时候:
被要求target是DependencyObject的,属性是DenpendencyProperty,而我们在XAML中又使用的是CLR属性,因为CLR属性就是对依赖属性做了一层封装
使用propdp + 2次tab可以快速声明一个依赖属性
7.2.3 依赖属性值存取的秘密
在程序中为一个依赖属性获取实例是使用的DenpendencyProperty.Register方法,那么可以从这个Register方法内部了解到更多:
public static DependencyProperty Register(string name, Type propertyType, Type ownerType)
{
return RegisterCommon(name, propertyType, ownerType);
}
private static DependencyProperty RegisterCommon(string name, Type propertyType, Type ownerType)
{
FromNameKey key = new FromNameKey(name, ownerType);
//FromNameKey的构造器
//public FromNameKey(string name,Type ownerType)
//{
// _name = name;
// _ownerType = ownerType;
// _hashCode = _name.GetHashCode() ^ _ownerType.GetHashCode();
//}
if (PropertyFromName.Contains(key))
{
//throw new ....
}
DependencyProperty dp = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, validateValueCallback);
PropertyFromName[key] = dp;
}
public override int GetHashCode()
{
return _hashCode;
}
一句话概括DependencyProperty对象的创建与注册,那就是:创建一个DependencyProperty实例并用它的CLR属性名和宿主类型名生成hash code,最后把hashcode和DependencyProperty实例作为Key-Value对存入全局的、名为PropertyFromName的Hashtable中。这样,WFP属性系统通过CLR属性名和宿主类型名就可以从这个全局的Hashtable中检索出对应的DependencyProperty实例
可以通过DependencyObject.GetValue来了解一下依赖属性的作用:
public object GetValue(DependencyProperty dp)
{
......
return GetValueEntry(LookupEntry(dp.GlobalIndex),dp,null,RequestFlags.FullyResolved)
.value;
//上面的展开:
EntryIndex entryIndex = LookupEntry(dp.GlobalIndex);
EfffectiveValueEntry valueEntry = GetValueEntry(entryIndex,dp,null....)
return valueEntry.Value;
}
public EffectiveValyeEntry GetValueEntry(...)
{
...
return EffectiveValueEntry[dp.GlobalIndex];
}
在DependencyObject有这样一个成员变量:
private EffectiveValueEntry[] _effectiveValues;
这个数组就存的是每个依赖属性的值,然后取的话就通过DependencyProperty.GlobaIndex(本质是hash code,而hash code又由其CLR包装器名和宿主类型名共同决定)去数组中索引找到值
所以,依赖属性的static关键字所修饰的依赖属性对象其作用是用来检索真正的属性值,而不是用来存储值,然后为了保证GlobalIndex属性值的稳定性,又对依赖属性使用了readonly修饰符(想一想,如果不加readonly,那么就可以在程序中随便修改某个依赖属性的实例,那么它的GlobalIndex就会改变,GlobalIndex改变,hashcode就会改变,随之后续EffectiveValueEntry[]对于同一个依赖属性就会有多个实例)
DependencyObject.SetValue基本上就是往DependencyObject._effectiveValues数组中写数据了
WPF利用依赖属性其实是一种空间换时间的做法,虽然减少属性消耗的内存,但增加了属性存值取值时的算法时间,而本书作者也说了WPF在性能上不尽人意,微软也在不断完善这个机制
附加属性是说一个属性本来不属于某个对象,但由于某种需求而被后来附加上。也就是把对象放入一个特定环境后对象才具有的属性(表现出来就是被环境赋予的属性)就称为附加属性(Attached Properties)
附加属性的作用就是将属性与数据类型(宿主)解耦
附加属性的本质是依赖属性
快速创建一个附加属性:propa+2次tab
附加属性的例子:
可以看到,附件属性在代码中的写法和依赖属性基本相同,不同点在于:
因为附加属性也是DependencyProperty的一个实例,所以也可以作为Binding的对象,下面是用附加属性来做Binding的例子:
可以在XAML以Canvas.Top和Canvas.Left为Target做Binding
也可以用C#代码做Binding:
这种代码与在XAML中写起来同样奇怪,谁叫它是附加属性..
附加属性工作流程(我的理解):当我们在XAML像Canvas.Left=3这样复制之后,Canvas的实例(以上面的XAML为例就是包裹了两个Slider和Rectangle的Canvas)下的一个数组就会存一个3的值,而键就是附加到那个控件对象的hashcode值,这样Canvas就在布局的时候就可以通过这个数组以键和值的方式确定它所包含的所有控件的布局位置
事件系统在WPF中也有升级,进化为路由事件,并在其基础上衍生出命令传递机制
好处:使程序的设计和实现更加灵活,模块之间的耦合度也进一步降低
路由(Route):起点与终点间有若干个中转站,从起点出发后经过每个中转站时要做出选择,最终以正确(比如最短或者最快)的路径到达终点
(Windows本身就是一种消息驱动的操作系统)
WPF中有两种“树”:一种叫逻辑树(Logical Tree);一种叫可视元素树(Visual Tree):
Template可以理解为控件的骨架,可以保证控件功能不丢失的情况下为控件换一副新骨架,让它更漂亮
事件模型:
参考我的另一篇事件学习文章:https://blog.csdn.net/qq_38312617/article/details/105494149
在这种模型里,事件的响应者通过订阅关系直接关联在事件拥有者的事件上,为了与WPF的路由事件模型区分开,我把这种事件模型称为直接事件模型或者CLR事件模型。因为CLR事件本质上是一个用event关键字修饰的委托实例
8.3.1 使用WPF内置路由事件
①画一个多层布局,内部含有2个Button的界面:
②在代码中为gridRoot绑定Button.Click路由事件(或者在XAML中绑定,如上):
当像这样做了绑定之后,那么gridRoot布局内部的button的所有click事件都会被它接受并触发它的事件响应函数
8.3.2 自定义路由事件
创建自定义路由事件大体可分为三步:
ButtonBase类中Click事件的三个步骤的体现:
为路由事件添加CLR事件包装是为了把路由事件暴露得像一个传统的直接事件,所以平时在使用路由事件的时候同样可以使用与直接事件相同的语法+=与-=
自定义一个可以报告事件的Button路由事件:
①继承Button类,创建一个ReportTime事件:
开始没有明白为什么点击Button就会触发ReportTime事件,其实应该就是点击Button之后会去调用OnClick方法,方法内部定义一些事件调用即可
②界面:
③事件响应方法:
④效果:
另外可以看到,当事件路由到grid_2这个对象之后就没有再继续往上走了,这是因为我在③中的事件响应方法中做了判断,如果事件通知到grid_2,那么e.Handled = true,则事件通知停止
9.1.1 命令系统的基本元素
WPF的命令系统由几个基本要素构成,它们是:
命令基本元素的关系图
暂时跳过命令的学习,因为感觉之前的例子和场景都没有用到命令这个概念,所以先学习重要的知识点...
为了避免丢失或损坏,编译器允许我们把外部文件编译进程序主体、成为程序主体不可分割的一部分,这是传统的程序资源
每个界面元素都可以携带自己的资源并可被自己的子级元素共享,这是对象级资源
静态资源使用(StaticResource)指的是在程序载入内存时对资源的一次性使用,之后就不再去访问这个资源了
动态资源使用(DynamicResource)指的是在程序运行过程中仍然会去访问资源
所以对于StaticResource访问资源来说,后续即使资源被更新,使用者也不会同步;而使用DynamicResource访问资源,后续资源被更新,使用者也会感知资源更新并且同步更新
例子:
①定义两个资源,一个使用静态资源访问,一个使用动态资源访问
②Button.Click事件导致资源被更新:
③效果:
触发一次Click事件,DynamicResource访问的资源被同步更新,而StaticResource访问的资源保持不变
10.3 向程序添加二进制文件
如果要添加的资源是字符串而非文件,我们可以使用应用程序Properties名称空间中的Resources.resx资源文件
如图:
①向Resources.resx中添加一个Username-name的键值对:
注意要将访问权限设置为Public,否则访问该资源会抛出异常
③效果:
新一代的设计理念:模板
11.1 模板的内涵
在WPF中,通过引入模板微软将数据和算法的“内容”与“形式”解耦