WPF - 自定义标记扩展
在使用WPF进行编程的过程中,我们常常需要使用XAML的标记扩展:{Binding},{x:Null}等等。那么为什么WPF提供了XAML标记扩展这一功能,我们又如何创建自定义的标记扩展呢。这就是本文将要讨论的内容。
一.从标记扩展的分析说起
在WPF中,软件开发人员需要以类似于XML的格式编写XAML。如下面代码所示:
1 <Window …>2 <StackPanel …>3 <TextBlock …/>4 </StackPanel>5 </Window>
但是在实际开发过程中,我们却常常需要使用标记扩展,如对绑定的使用:
1 <Window …>2 <StackPanel>3 <TextBlock Text="{Binding src:DataSource.Description}"/>4 </StackPanel>5 </Window>
您会好奇,为什么提供这种特殊的语法?其实这是因为XAML本身无法完成某些特定的功能所导致的。如果需要深刻地了解产生该问题的原因,我们就需要从XAML编译器是如何对XAML进行解析的讲起。
无论XAML的最终表示形式是怎样,编译器在处理XAML文件时所得到的都是一个个字符串。一个XML元素的开始常常表示类型实例,而以属性(Attribute)或子元素所表示的XML组成则是在对该类型实例的属性进行设置。在分析对XML属性(Attribute)进行赋值的字符串时,XAML处理器会根据字符串的内容决定自身的分析逻辑。
对于普通的属性赋值字符串,XAML处理器会根据属性的类型决定是否需要执行对字符串的转化。如果属性的类型不是字符串,那么XAML处理器会调用相应的转化逻辑,如对于枚举类型的属性,XAML处理器将通过Enum的Parse方法得到相应类型的数值。而对于自定义类型,XAML会根据该自定义类型声明或属性声明上所标明的TypeConverter将字符串转换为该自定义类型。
也就是说,可以被XAML编译器正确解释的自定义类型需要满足如下条件:属性的类型需要是值类型,具有默认构造函数的类型或者标明了专用类型转换器的类型,即标明了特性TypeConverterAttribute。
如果一个类型不能提供满足上面条件的实线,那该怎么办呢?解决问题的方法就是使用XAML标记扩展。XAML编译器会按照如下方式分析XAML标记扩展:如果XAML处理器遇到一个大括号,或者遇到一个从MarkupExtension派生的对象元素时,那么XAML编译器将按照标记扩展分析该字符串,直至遇到表示结束的花括号。首先,编译器会根据字符串决定标记扩展所对应的MarkupExtension类派生类。接下来,编译器将按照下面的规则对扩展标记字符串进行处理:1) 逗号代表各个标记的分隔符。2) 如果分隔的标记没有任何等号赋值,那么它将被视为构造函数的参数。这些参数需要与构造函数的参数个数匹配。如果两个构造函数的参数个数相同,那么XAML编译器将无法分析。该行为没有定义。3) 如果每个标记都包含等号,那么XAML处理器将首先调用默认构造函数并对这些属性进行赋值。4) 如果标记扩展同时使用了构造函数参数以及属性赋值,那么XAML处理器内部将调用对应的构造函数并对属性进行赋值。最后,编译器会在应用程序加载时调用该类型的ProvideValue()函数,用来定义该标记应该返回哪个对象。该函数调用会传入有关当前上下文的信息,以允许ProvideValue()函数根据该上下文创建相应的对象。
如果标记扩展之间存在着嵌套,那么XAML编译器将首先计算标记扩展的最内层,如下面示例将首先计算x:Static:
1 <Setter Property="Background" Value="{DynamicResource {x:Static SystemColors.Control}}"/>
可以看到,XAML编译器对属性赋值进行分析的方式主要会根据其是否是标记扩展而分为使用转化或调用标记扩展的ProvideValue()函数两种。这两种方法之间的最大不同在于ProvideValue()函数可以根据上下文提供更复杂的实例创建或引用逻辑。另外,标记扩展允许软件开发人员在XAML中使用带有一个参数的非默认构造函数。这也是标记扩展的一个优点。
二.WPF中的标记扩展
在开始讲解之前,您最好得到WPF的实现代码。虽然说本文会提供必要的代码片断,但能从全局层面上分析可能会给您更多的收获。在“从Dispatcher.PushFrame()说起”一文中,我们已经介绍了如何获得.net的源码,而在“资源下载”一文中,我们也提供了这些源码的下载地址。
首先来看看比较典型的标记扩展{x:Type}的实现:
1 [MarkupExtensionReturnType(typeof(System.Type)), 2 TypeConverter(typeof(TypeExtensionConverter))] 3 public class TypeExtension : MarkupExtension 4 { 5 …… 6 public TypeExtension(System.Type type) 7 { 8 …… 9 this._type = type;10 }11 12 public override object ProvideValue(IServiceProvider serviceProvider)13 {14 if (this._type == null)15 {16 ……17 IXamlTypeResolver service = serviceProvider.GetService(18 typeof(IXamlTypeResolver)) as IXamlTypeResolver;19 ……20 this._type = service.Resolve(this._typeName);21 ……22 }23 return this._type;24 }25 26 [ConstructorArgument("type"), DefaultValue((string) null)]27 public System.Type Type28 {29 get { return this._type; }30 set31 {32 ……33 this._type = value;34 this._typeName = null;35 }36 }37 ……38 }
首先来看看最重要的组成ProvideValue()函数。该函数首先会通过GetService()函数得到IXamlTypeResolver服务。该服务所提供的Resolve()函数会根据TypeName属性所记录的字符串解析出TypeName属性所指定的Type实例对象。
标记扩展{x:Type}的实现所展示的ProvideValue()函数实现是标记扩展实现中的典型实现。通过GetService()函数所可能得到的常用服务有:IProvideValueTarget服务,以知晓标记扩展所在的目标元素和属性;IUriContext,即可获得当前上下文中的基准Uri;IXamlTypeResolver,用来将XAML元素名称解析为.net类型实例,最典型的例子就是x:Type标记扩展。
同时上面所展示的代码使用了三个特性:ConstructorArgument、TypeConverter以及MarkupExtensionReturnType。接下来,我们就来看看这三个特性各自的功能。
首先就是ConstructorArgument特性。该特性用来提示XAML编译器标记扩展中所标示的构造函数参数实际上与哪个属性相对应。通过该特性所关联的属性则必须是一个可读写的属性。
那么问题接踵而至:ConstructorArgument特性是使用在类型为Type的属性之上,而XAML编译器所输入的则是字符串类型。为了解决这种类型上的不匹配,标记扩展TypeExtension使用了另一个特性TypeConverter提示XAML编译器使用类型转换器类型TypeExtensionConverter处理标记扩展声明中所标示的字符串类型参数。
最后一个要提及的特性就是MarkupExtensionReturnType。该特性用来标明ProvideValue()函数所返回的类型。
三.自定义标记扩展
现在我们就来开始编写自定义标记扩展。自定义标记扩展常常从MarkupExtension派生,并重写该类的ProvideValue()函数。在本节中,我们就以延迟绑定为例演示如何创建一个自定义绑定。
想象下面一种情况:在一个程序的XAML中声明的绑定会在程序启动时加载,并请求绑定源属性的值。对该源属性值的求解将会导致其它功能被加载。试想一下,如果Ribbon所罗列的所有功能都会在程序启动时被加载,那么程序的启动性能将变得非常差。
这也就是延迟绑定所需要解决的问题。只有在程序界面变为可见时,绑定才会被添加到界面元素中并对其进行求解。
可能您的第一反应是创建一个自定义绑定以解决该问题。的确,BindingBase类提供了虚函数CreateBindingExpressionOverride()以供自定义绑定实现者提供自定义功能。但是本文不采用该方法,其原因有二:该函数所提供的灵活性较差;该函数具有较强的语义特征。其用于创建BindingExpression类型实例,而并不适用于延迟绑定的实现。
因此,使LazyBinding派生自MarkupExtension并重写它的ProvideValue()函数可能是一个更好的选择。下面就是实现LazyBinding的代码:
1 [MarkupExtensionReturnType(typeof(object))] 2 public class LazyBindingExtension : MarkupExtension 3 { 4 public LazyBindingExtension() 5 { } 6 7 public LazyBindingExtension(string path) 8 { 9 Path = new PropertyPath(path);10 }11 12 public override object ProvideValue(IServiceProvider serviceProvider)13 {14 IProvideValueTarget service = serviceProvider.GetService15 (typeof(IProvideValueTarget)) as IProvideValueTarget;16 if (service == null)17 return null;18 19 mTarget = service.TargetObject as FrameworkElement;20 mProperty = service.TargetProperty as DependencyProperty;21 if (mTarget != null && mProperty != null)22 {23 // 侦听IsVisible属性的更改,以在界面元素显示时通过OnIsVisibleChanged24 // 函数添加绑定25 mTarget.IsVisibleChanged += OnIsVisibleChanged;26 return null;27 }28 else29 {30 Binding binding = CreateBinding();31 return binding.ProvideValue(serviceProvider);32 }33 }34 35 private void OnIsVisibleChanged(object sender, 36 DependencyPropertyChangedEventArgs e)37 {38 // 添加绑定39 Binding binding = CreateBinding();40 BindingOperations.SetBinding(mTarget, mProperty, binding);41 }42 43 private Binding CreateBinding() // 创建绑定类型实例44 {45 Binding binding = new Binding(Path.Path);46 if (Source != null)47 binding.Source = Source;48 if (RelativeSource != null)49 binding.RelativeSource = RelativeSource;50 if (ElementName != null)51 binding.ElementName = ElementName;52 binding.Converter = Converter;53 binding.ConverterParameter = ConverterParameter;54 return binding;55 }56 57 #region Fields58 private FrameworkElement mTarget = null;59 private DependencyProperty mProperty = null;60 #endregion61 62 #region Properties63 public object Source…64 public RelativeSource RelativeSource…65 public string ElementName…66 public PropertyPath Path…67 public IValueConverter Converter…68 public object ConverterParameter…69 #endregion70 }
在这里,LazyBinding仅仅探测IsVisibileChanged事件,以在UI元素显示时动态添加绑定。在该类的真正实现中,以何种方式完成延迟功能则需要您根据需求决定。
在XAML中,软件开发人员可以像普通绑定一样使用它。但需要注意的一个问题就是MarkupExtension的嵌套使用。如果您按照下面的方法使用LazyBinding:
1 <TextBlock Text="{local:LazyBinding ElementName=mMainWindow, Path=Source, Converter={StaticResource testConverter}}"/>
那么编译器会在编译时报错。从网络上的讨论来看,这是一个Bug,但是无论在VS2008还是VS2010中,其都没有得到修正。如果我是错误的,请通知我。
作为一个变通的方法,我们可以在程序中通过XML元素的方法完成对LazyBinding的使用:
1 <TextBlock>2 <TextBlock.Text>3 <local:LazyBinding ElementName="mMainWindow" Path="Source" Converter="{StaticResource testConverter}"/>4 </TextBlock.Text>5 </TextBlock>
四.命名空间管理
其实这本不属于与标志扩展关联密切的话题。只是由于WPF中的众多标记扩展都使用了x:作为前缀,并且其在编写类库中非常常见,因此在本文中,我们将以一小部分篇幅完成对该功能的介绍。
在开发WPF程序时,XAML一般包含两个xmlns声明:
1 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"2 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
第一个声明用来指定WPF命名空间为默认命名空间,而第二个声明用来指定x:前缀对应XAML命名空间。这两个声明的关系是:XAML是实现标准,用来定义为实现兼容而要实现的元素,而WPF是将XAML作为语言而使用的实现。如x:Type等就是标准的标记扩展,而StaticResource则是WPF的特定扩展。因此,有些派生自MarkupExtension类的标记扩展实际上是XAML的语言规范的一部分。它们通常使用x:前缀。
软件开发人员可以通过XmlnsDefinitionAttribute特性将多个CLR命名空间映射到单个XML命名空间。为了达到该目的,软件开发人员仅需要将该特性声明置于AssemblyInfo中即可,并标以assembly范围。该特性可重复使用,以将多个CLR命名空间映射到一个XML命名空间。
需注意的是,如果使用该映射命名空间的XAML文件与该特性处于同一项目中,那么该特性声明的XML命名空间将不包含同一项目中的类型。这是因为编译时原程序集已清空而其中的类型无法在编译时解析的缘故。我并没有找到在官方文档中对该问题的说明,因此如果您找到解释该行为的文档,请告知。
转载请注明原文地址:http://www.cnblogs.com/loveis715/archive/2012/02/06/2340669.html
商业转载请事先与我联系:silverfox715@sina.com
转载于:https://www.cnblogs.com/loveis715/archive/2012/02/06/2340669.html
WPF - 自定义标记扩展相关推荐
- wpf中xaml的类型转换器与标记扩展
wpf中xaml的类型转换器与标记扩展 原文:wpf中xaml的类型转换器与标记扩展 这篇来讲wpf控件属性的类型转换器 类型转换器 类型转换器在asp.net控件中已经有使用过了,由于wpf的界面是 ...
- [No0000130]WPF 4.5使用标记扩展订阅事件
自从我上次写到关于标记扩展的时候已经有一段时间了...... Visual Studio 11 Developer Preview的发布给WPF带来了一些新功能,让我有理由再次使用它们.我要在这里讨论 ...
- 2.6 wpf标记扩展
1.什么是标记扩展?为什么要有标记扩展? 标记扩展是扩展xmal的表达能力 为了克服现存的类型转换机制存在的 常用的标记扩展有如下: x:Array 代表一个.net数组,它的子元素都是数组元素.它必 ...
- WPF学习:4.类型转换和标记扩展
在上一章,主要介绍了Border和Brush,这一章主要介绍下类型转换和标记扩展.相关代码链接如下: http://files.cnblogs.com/keylei203/4.WPFSampleDem ...
- WPF 自定义 MessageBox (相对完善版 v1.0.0.6)
基于WPF的自定义 MessageBox. 众所周知WPF界面美观.大多数WPF元素都可以简单的修改其样式,从而达到程序的风格统一.可是当你不得不弹出一个消息框通知用户消息时(虽然很不建议在程序中频繁 ...
- WPF 自定义TabControl控件样式(转)
WPF 自定义TabControl控件样式 一.前言 程序中经常会用到TabControl控件,默认的控件样式很普通.而且样式或功能不一定符合我们的要求.比如:我们需要TabControl的标题能够居 ...
- WPF自定义空心文字
原文:WPF自定义空心文字 首先创建一个自定义控件,继承自FrameworkElement,"Generic.xaml"中可以不添加样式. 要自定义空心文字,要用到绘制格式化文本F ...
- WPF 自定义标题栏 自定义菜单栏
原文:WPF 自定义标题栏 自定义菜单栏 自定义标题栏 自定义列表,可以直接修改WPF中的ListBox模板,也用这样类似的效果.但是ListBox是不能设置默认选中状态的. 而我们需要一些复杂的UI ...
- [WPF]自定义鼠标指针
[WPF]自定义鼠标指针 原文:[WPF]自定义鼠标指针 [WPF]自定义鼠标指针 周银辉 看看WPF Cursor类的两个构造函数吧: public Cursor(Stream cursorStre ...
最新文章
- python程序实例电话本-Python基于递归实现电话号码映射功能示例
- JavaScript中属性name与方法名不能相同
- axure文件如何加密_怎么样给PDF加密?PDF文件如何加密?
- JAVA——使用Spring Boot Scheduled时注入simple-robot Bot解决方案
- 【翻译】Ext JS最新技巧——2015-8-11
- 怎样封装一个自己的mvc框架(五)
- vim 寄存器 操作_vim指令
- hdu 4125 Moles(kmp+树状数组)
- 使用BaaS更快地构建Xamarin应用程序
- 人头检测 模型 c++_常熟市房屋建筑检测鉴定服务单位 房屋鉴定中心
- 树莓派桌面没有时间_树莓派3B/3B+开启手机远程桌面和终端,没有屏幕和电脑的伙伴们有福啦!...
- UVA 10246 Asterix and Obelix
- 线程之线程池(ExecutorService)
- poi excel 插入批注
- DECOUPLED WEIGHT DECAY REGULARIZATION
- 微信小程序 - requestSubscribeMessage:fail can only be invoked by user TAP gesture.
- Poto Editor for Mac(mac照片编辑器)
- php服务器监控系统,91 Monitor
- AD域批量的导入账号
- 【Jquery】Jquery判断客户端请求来源于PC端还是移动端
热门文章
- AtCoder Grand Contest 023 C - Painting Machines
- https证书/即SSL数字证书申请途径和流程
- Notes of the scrum meeting(2013/10/23)
- C# ASP.NET MVC 图片上传的多种方式(存储至服务器文件夹,阿里云oss)
- bit不是java基本类型吗_Java bit / byte 基本数据类型
- 微信支付—微信H5支付「微信内部浏览器」
- Eclipse 创建第一个 springboot 应用
- Android Service与IntentService区别
- 计算机硬件基础大纲,计算机硬件基础教学大纲..docx
- spring boot集成oss