分类目录归档:WPF

在WPF中使用变通方法实现枚举类型的XAML绑定

问题缘起

WPF的分层结构为编程带来了极大便利,XAML绑定是其最主要的特征。在使用绑定的过程中,大家都普遍的发现枚举成员的绑定是个问题。一般来说,枚举绑定多出现于与ComboBox配合的情况,此时我们希望实现的目标有:

  1. 建立选择项与ItemsSource的对应关系;
  2. 自动获取用于ItemsSource的枚举源;
  3. 自定义下拉框中显示的内容。

对于目标1,考虑最简单的模式,即枚举的定义采用从0开始的连续整数,可以使用IValueConverter接口来实现从枚举到整型的双向转换,以使得枚举成员绑定到SelectedIndex上。

有些朋友提出使用静态ObjectDataProvider资源作为ItemsSource的来源,这种方式可实现枚举成员的直接绑定,不需要值转换,其缺点是对于每一个枚举类型都要添加一个提供者,当项目较大、枚举类型多时使用起来很不方便。

考虑到枚举的比较是值类型比较,Broculos想到了比较聪明的方法同时实现了目标1和目标2:定义一个返回枚举兄弟成员的标记拓展(MarkupExtension)。使用时向标记拓展中提供枚举类型即可。相比于上一种方法,该方法更加简单明了,只是当在XAML中提供枚举类型时,需要引用其命名空间,而XAML中的命名空间引用缺少自动完成机制,有时需要搜索一番。

当然,上面所有解答都没有实现目标3。网友ding.li使用代码方式对下拉框内容进行设置,虽然实现了目标3,但违背了目标2。

Mgen通过定义一个提供附加属性的类实现了所有3个目标。在Selector要素中,设置EnumSelector.EnumType和EnumSelector.BindingPath附加属性来指定ItemsSource和SelectedItem,而非直接设置要素的相应属性。该方法实际上发展了标记拓展的思路,为实现目标3而进行了较复杂的设计。这是非常好的实现方案,缺点是违反直观感受。

变通方案

本文介绍使用封装枚举类型的方法同时实现3个愿望。主要思路是,在XAML中尽可能少的写入代码,通过上下文绑定直接设置下拉框的ItemsSource,SelectedItem,以及显示内容。

注意到DisplayMemberPath和Binding拓展的Path属性,要同时由上下文提供多个属性用来绑定,分别是:作为ItemsSource源的集合、作为SelectedItem的实例、及作为“DisplayMember”的字符串。显然直接使用枚举实例无法提供所需属性成员,因此设计封装类型并在其中定义成员如下:

这样在XAML中的下拉框代码绑定可写为:

可以通过EnumShell实例上下文获取所有必要信息。

下面简述该类型的实现。

泛型类EnumShell<T>的构造函数接受类型T的参数,该参数是特定枚举类型的实例,因此T即为某个枚举类型。该参数由Instance属性保存,并从枚举类型获取到所有可用的枚举值,均封装为EnumShell<T>实例,并作为数组由Brothers属性公开。Description属性获取枚举值定义的DescriptionAttribute特定指定的文本,作为显示的内容。

考虑到ComboBox的项比较实际是EnumShell<T>实例的比较,因此不能单纯的使用其构造函数来得到Brothers集合,解决方法是定义一个静态的字典用于保存EnumShell<T>实例,使得通过一个枚举值永远得到唯一的一个EnumShell<T>实例。

完整的代码如下所示:

定义抽象的EnumShell类型作为基类型,可以在定义实体类型时不指明泛型类型参数,由此支持任意枚举类型的取值;定义静态的GetShell泛型函数,将新的EnumShell实例注册添加到全局字典;将EnumShell<T>构造函数的可见性进行限制,避免了自行实例化导致字典中没有注册的情况;EnumShell类公开了其他属性成员,以方便各种XAML绑定的情况。

使用时,将实体类型中的原枚举属性替换为EnumShell或EnumShell<T>属性,并使用EnumShell.GetShell进行实例化赋值。如,有枚举定义:

定义某实体类型,用EnumShell表示该枚举:

这样定义后即可使用,当需要执行switch分支时,可用Instance属性获取被封装的真正的枚举值。

结语

本文介绍的封装方法实现枚举绑定,适用于自定义实体类型的情况,对于直接使用第三方实体类型的情况,则无法直接使用,必须对实体类型本身进行再次封装,而这大大降低了便利性。

其实在早些时候有传言说C# 5.0会支持拓展属性,试想这要是真的,对枚举进行绑定将是轻而易举的事情,真的希望C#能够实现这一功能。

本文中的EnumShell类型在Heroius.Extension.WPF库中包含。更多可免费使用的程序集引用,请使用Heroius的Nuget服务源:http://heroius.com:8686/app/nuget。详细信息请访问http://heroius.com:8686/app

XAML UserControl的继承

前言

相信不少学习WPF和Silverlight的同学们都出于Winform的习惯,希望能够在新展示层框架中实现控件的继承。本文就是说明如何实现这一点。

但是在正文开始之前,必须要指明,一般情况下,在WPF/SL中并不推荐使用自定义控件或控件继承(当然,使用模板生成的Window, UserControl, Page不在此限),因为基于XAML的前台设计语言本身具有丰富的表现能力,且框架支持样式(Style)、模板(Template)等控制外观和行为的方式—-这些特性足以构建出任何形式的界面UI。反过来说,创建自定义控件或实现控件继承在WPF/SL中不仅不受推崇,其实现难度也要比Winform中大。

那么当什么时候才不得不使用自定义控件呢?就是这个控件需要在多处实例化使用,而本身内容较为复杂时。相比较而言,控件的继承使用的情况就更少了,它使用的必要性一般要满足如下几条:

  1. 只在原控件不满足新需求,但同时又有可资利用的价值;
  2. 新控件需要直接访问原控件成员(否则在新控件中包含原控件即可,不必继承);
  3. 新控件同样需要在多处使用。

如果你面临的情况满足这几个条件,阅读本文可能会提供帮助。

问题分析

在WPF/SL中实现控件继承之所以会比Winform困难,是因为在底层框架设计上WPF/SL将表示层彻底从逻辑中分离了出去,控件外观几乎均有XAML标记定义,而在对控件进行继承时,XAML部分对应的类型成员是无法被继承的。

以WPF为例,使用UserControl模板新建用户控件,得到一个.cs文件和一个.xaml文件。注意在.cs文件中类定义有partial修饰,说明是分部类,代码中的InitializeComponent函数即是在另外一部分代码(设计器生成代码)中定义的。这部分由设计器生成的代码和Winform中设计器的代码相差很大。

在生成文件夹中可以看到设计器生成的.g.i.cs文件,其中包含对应于XAML内命名成员的相应变量的定义,以及InitializeComponent方法实现。

可见于Winform设计器代码相比,其中不包含C#代码形式的控件初始化逻辑,所有界面表达均在xaml文件中,代码通过System.Windows.Application.LoadComponent方法从xaml文件实例化。

在.xaml文件中,可见<UserControl>标签及其属性x:class,此两者指明了XAML文档对应的类型信息,其中根元素是当前类型的基类(UserControl),x:class属性指定当前类型(UserControl1)。

注意到Application.LoadComponent方法包含两个重载:

  1. object (Uri) – 接受xaml资源的定位符,返回其根元素决定的实例;
  2. void (object, Uri) – 接受根元素类型实例和资源定位符。

自动生成代码采用了第2个重载,并传递当前类实例作为第一个参数,也就是说,XAML加载得到了拓展的UserControl1类型。

这种在xaml中指定类型信息的类(UserControl1)被称为是“由XAML定义的”。而XAML渲染器无法识别由XAML定义的根类型,也就是说当控件继承时,若在子类型控件(如UserControl2)设计器中指定其为根元素时,编译过程将失败。

但假如子类型不包含XAML代码,如新建Class1继承自UserControl1,则没有问题。

现在的问题是,在设计控件,尤其是结构较复杂时,我们往往需要借助设计器,这就要求必须使用XAML代码,这种情况应该如何应对呢?

XAML UserControl的继承

命题:两个带有设计界面的类型UserControl1和UserControl2,其中后者继承于前者。

思路:

  1. 对于xaml代码,利用Application.LoadComponent方法可获取根元素决定的实例;
  2. UserControl属于Content Control,其显示内容由Content决定;
  3. 基于以上两点,分离控件xaml部分,并修改为以某FrameworkElement为根,如此可得到用于设置Content属性的可视化内容。

UserControl2继承代码修改

修改UserControl2的代码,使其继承自UserControl1

修改xaml代码,将根元素设置为基类,注意引用本地程序集命名空间

 UserControl1 xaml和代码分离

UserControl1控件包含的.xaml和.xaml.cs文件由VS管理,为了使两者分开,需要重命名。先将项从项目中移除,分别重命名:

  1. UserControl1.xaml -> UserControl1_skin.xaml
  2. UserControl1.xaml.cs -> UserControl1.cs

重新加载到项目中,修改xaml文件根元素,使用Grid代替UserControl

移除UserControl.cs中类定义前的partial修饰符,并手动添加InitializeComponent函数,在其中利用Application.LoadComponent的第一个重载获取如上修改之后xaml编译得到的Grid实例,将其设置给Content:

 UserControl2的代码调整

为避免UserControl2自动生成代码覆盖基类的Content内容,在调用UserControl2的InitializeComponent函数之前需要获取基类Content,即上文中的Grid实例,并将其插入到当前UserControl2的最下层。

此时在主窗体中拖放UserControl2,程序运行效果如下:

其他注意事项

关于Silverlight

Silverlight中不包含 Application.LoadComponent的第一个重载,可事先创建Grid实例,之后将其作为参数调用 Application.LoadComponent第二重载,效果和WPF一样。

关于界面设计

若UserControl1界面中有交互内容,设计UserControl2时需要注意避让。

实际上,完全可以通过代码控制界面元素的布局,例如可以尝试将UserControl2构造函数的代码改成如下内容:

 示例代码

请移步百度网盘获取示例代码(基于VS2013)

  • 链接: http://pan.baidu.com/s/1ntG7AC5
  • 提取密码: k3k1

配置浏览器以使用WPF XBAP网络应用程序

写在前面
本文涉及对计算机安全设置的修改,不当操作可能会对计算机带来安全隐患,请操作之前认真了解相关内容,并且在您确定目标应用程序是安全的前提下进行操作。
本文中的一些知识来自其他作者分享的文章,列出如下:
Hanselman:《Firefox, WPF andXBAP》http://www.hanselman.com/blog/FirefoxWPFAndXBAP.aspx
Ouch:《使用IE9、FireFox与Chrome浏览WPF BrowserApplication(.XBAP)的方式》http://www.dotblogs.com.tw/ouch1978/archive/2011/03/16/wpf-view-xbap-in-browsers.aspx
冰戈:《WPF的XBAP文件也能在Firefox中查看》http://www.cnblogs.com/Hedonister/archive/2006/11/24/571235.html

WPF XBAP网络应用程序
WPF应用程序是运行在微软.NET Framework上的应用程序,它可以被部署到网络浏览器上运行,文件以打包形式发布并由客户端计算机的WPF主机加载程序进行加载和运行,即XAML浏览器应用程序(XBAP)。
为能够运行发布在网络上的WPF应用程序,必须安装相应版本的.NET Framework。

想要了解更多关于WPF浏览器应用程序的信息,可参考:
关于WPF:http://zh.wikipedia.org/wiki/Windows_Presentation_Foundation
关于XBAP:http://zh.wikipedia.org/wiki/XAML_Browser_Application或 http://www.xbap.org
关于.NET Framework:http://www.microsoft.com/zh-cn/download/details.aspx?id=17718
其他参考页面:
http://msdn.microsoft.com/zh-cn/library/cc716877(v=vs.110).aspx
http://social.msdn.microsoft.com/Forums/zh-CN/747c6b78-a821-4498-a37f-1b79197de803/xbap-plugin-for-firefox-in-windows-7?forum=wpf
http://stackoverflow.com/questions/17443883/firefox-v22-0-wont-download-and-display-xbap-anymore

正常运行的WPF网络程序加载时如图所示:

浏览器对WPF XBAP程序进行加载,需要调用相应的WPF插件并进行安全设置。对于微软自家产品,IE对XBAP程序的支持最好,因此推荐使用IE浏览器运行此程序;火狐浏览器和Chrome浏览器据说也可以运行XBAP,但笔者只尝试使用了火狐浏览器,并且没有成功。下面首先介绍站点信任和权限内容,随后分别针对IE和火狐(Firefox)浏览器的设置进行简要说明。

信任权限
在计算机程序在运行时,操作系统会为其分配一定的权限,使程序在有限的资源(内存、本地硬盘、注册表等)范围内进行操作,以防止意外或恶意代码破坏系统或造成其他危害。
运行WPF XBAP程序和本地程序相似,由于可访问计算机资源,往往需要较高的权限级别,因此必须“信任”其来源后,程序才能正常运行,否则可能发生.xbap文件不加载(WPF宿主不会启动,而是由浏览器对.xbap进行处理,提示打开或保存),或加载程序运行时发生权限错误。

为使WPF XBAP程序获得“完全信任权限”,有两种情况:

能够获得证书的情况
站点证书是站点可信性的保证,一般来说,证书由互联网上的权威证书签发机构颁发,使用权威机构自身的证书(根证书)对站点证书进行签名加密。
信任一个证书,意味着对持有该证书和该证书签名的其他数字证书的安全网站或程序的信任,也就意味着允许这些网站对信息的传输,或代码在计算机上的运行。
对于XBAP程序,其部署信息(ClickOnce清单)可能被一个包含私钥的个人信息交换文件(.pfx)签名,而这样的一个私钥往往有与之对应的公钥,且包含在证书文件(.cer)里。
所以在浏览器中添加对这个证书的信任后,运行对应的XBAP程序时就具有完全信任权限了。
对于一个在CA认证机构注册的有效证书,一般是不需要手动添加信任的;但若证书或其根证书尚未得到信任,则要手动添加证书(或根证书)到计算机。
在Windows中添加一个证书,打开Internet选项,转到“内容”选项卡,在“证书”栏中点击“证书”,弹开证书对话框。根据需要,分别在“中级证书颁发机构”、“受信任的根证书颁发机构”、“受信任的发布者”3个分页中导入证书。点击“导入”按钮启动证书导入向导,按照向导提示进行操作。

 

一般网站
若XBAP程序没有进行ClickOnce清单签名、或无法获取相应证书文件,则要运行程序,就必须信任其来源(站点地址)。
操作系统依据访问系统资源的级别和范围,将操作者来源分为几个区域。在Internet选项的“安全”选项卡中,可以看到这些可配置的区域。

在“将该网站添加到区域”文本框中输入站点地址,并点击“添加”。若站点不是SSL站点,则注意不要勾选下方的“对该区域中的所有站点要求服务器验证(https:)”选项。
虽然添加了站点信任,首次运行XBAP程序时,系统仍会发出安全警告,询问是否确定要运行。

若相信程序是安全的,则点击“运行”,程序开始执行,且之后的执行将不再提示安全警告。

IE浏览器设置
IE浏览器提供原生技术支持,因此无需安装额外插件,但要运行WPF XBAP程序,必须设置浏览器安全配置并为其赋予合适的权限。
若浏览器禁用了XAML应用程序,程序加载后将提示“此应用程序类型已禁用”的错误:

启用XBAP应用程序,打开IE浏览器的Internet选项,进入到“安全”选项卡,在安全区域中选择“Internet”,之后点击“该区域的安全级别”框中的“自定义级别”。在弹开的“安全设置-Internet区域”对话框中,设置“XAML浏览器应用程序”为“启用”。

注意,若“受信任的站点”域安全设置中的相应项被禁用,则也需要进行设置。

Firefox浏览器设置
网络上关于使用Firefox运行XBAP的帖子时间都比较久了,但介绍的方法都基于微软提供的解决方案:
在Firefox中运行WPF XBAP程序需要安装插件,但该插件无法从网络单独获取,它被集成到.NET Framework 3.5 SP1中。要获取插件:可在安装Firefox后安装.NET Framework 3.5 SP1,插件将自动安装到浏览器中;若安装顺序相反,则可选择重装.NET Framework 3.5 SP1,但更聪明的方法是在C:\Windows\Microsoft.NET\Framework\v3.5\Windows PresentationFoundation\NPWPF.dll位置复制到Firefox的插件文件夹中。
解决方法没有提到证书的问题,这是需要特殊留意的,因为火狐浏览器拥有自己的证书存储区(独立于IE,可在火狐浏览器的设置选项中找到,导入证书的方法和IE类似)。
很遗憾地,根据网上一些资料显示,在Firefox 22版本之后该插件已无法正常工作(笔者动笔时火狐浏览器版本号已是31.0),较古老的火狐浏览器可能会工作,但笔者没有测试(这种测试几乎是无意义的,对于不支持HTML5等网络新技术的浏览器,其使用者已经微乎其微)。
一个变通方法是在Firefox中安装IE Tab插件以集成IE内核,并以IE内核方式浏览XBAP程序页面,此时设置方法与在IE中相同。
关于Firefox WPF加载项:http://msdn.microsoft.com/zh-cn/library/cc716877%28v=vs.100%29.aspx