在这里咱们将将打造一个UserControl(用户控件)来逐步讲解如何在WPF中自定义控件,并将WPF的一些新特性引入到自定义控件中来.
咱们制做了一个带语音报时功能的钟表控件, 效果以下:
在VS中右键单击你的项目,点击"添加新项目",在出现的选择列表中选择"UserControl",VS会自动为你生成一个*.xaml文件以及其对应的后台代码文件(*.cs或其它).
值得注意的是,自动生成的代码中,你的控件是继承于System.Windows.Controls.UserControl类的,这对应你的控件而言并不必定是最恰当的基类,你能够修改它,但注意你应该同时修改*.cs文件和*.xaml文件中的基类,而不仅是修改*.cs文件,不然当生成项目时会报错"不是继承于同一基类".修改*.xaml文件的方法是:将该文件的第一行和最后一行的"UserControl"改为与你认为恰当的基类名称.
1,为控件添加属性(依赖属性,DependencyProperty)
正以下面的代码所示:
html
咱们为控件(或者任何一个WPF类)添加的依赖属性都是"公开的","静态的","只读的",其命名方式是"属性名+Property",这是依赖属性一成不变的书写方式.对于依赖属性的注册能够在声明该属性时就调用DependencyProperty.Register()方法注册,也能够在其静态构造方法中注册.上面的DependencyProperty.Register方法的几个参数分别是:属性名(该属性名与声明的依赖属性名称"XXXProperty"相比仅仅是少了"Property"后缀,其它彻底同样,不然在运行时会报异常),属性的数据类型,属性的拥有者的类型,元数据.
关于参数中传递的元数据:若是是普通的类则应该传递PropertyMetadata,若是是FrameworkElement则能够传递FrameworkPropertyMetadata,其中FrameworkPropertyMetadata中能够制定一些标记代表该属性发生变化时控件应该作出什么反应,好比某属性的变化会影响到该控件的绘制,那么就应该像这样书写该属性的元数据: new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);这样当该属性发生变化时系统会考虑重绘该控件.另外元数据中还保护不少内容,好比默认值,数据验证,数据变化时的回调函数,是否参与属性"继承"等.
而后,咱们将该依赖属性包装成普通属性:
函数
GetValue和SetValue方法来自于DependencyObject类,其用于获取或设置类的某属性值.
注意:在将依赖属性包装成普通属性时,在get和set块中除了循序渐进的调用GetValue和SetValue方法外,不要进行任何其它的操做.下面的代码是不恰当的:
ui
在之前这或许是不少人的惯用写法,但在WPF中,这样的写法存在潜在的错误,缘由以下:咱们知道继承于DependencyObject的类拥有GetValue和SetValue方法来获取或设置属性值,那为何咱们不直接使用该方法来获取或设置属性值,而要将其包装成普通的.NET属性呢,事实上在这里两种方式都是能够的,只不过包装成普通的.NET属性更符合.NET开发人员的习惯,使用GetValue和SetValue更像JAVA开发人员的习惯,但XAML在执行时彷佛于JAVA开发人员同样,其不会调用.NET属性而是直接使用GetValue或SetValue方法,这样一来,咱们写在get块和set块中的其它代码根本不会被XAML执行到.因此说,就上面的Time属性而言,C#(或其它)对该属性的调用不会出现任何问题,但该属性被用在XAML中时(好比在XAML对该属性进行数据绑定等),其set块中的this.OnTimeUpdated(value);语句不会被执行到.
那么,当Time属性发生变化时的确须要调用this.OnTimeUpdated(value);语句(由于该语句会引起时间被更新了的事件),仍是在传递的依赖属性元数据作文章:
new FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)),咱们为属性的变化指定了一个回调函数,当该属性变化时该回调函数就会被执行:
this
2,为控件添加事件(传阅事件,RoutedEvent)
添加传阅事件的方法与添加依赖属性的方法很相似:spa
其支持方法EventManager.RegisterRoutedEvent()对应的几个参数分别为:事件名称,事件传阅的方式(向上传阅,向下传阅或不传阅),事件对应的EventHandler的类型,事件拥有者的类型)
而后将事件包装成普通的.NET事件:.net
注意,与依赖属性同样,不要在add与remove块中添加除AddHandler与RemoveHandler之外的代码.
题外话,事件参数中的e.Handled=true并非终止事件的传阅,这只是为事件作一个标记而已,以便在默认状况下的让那些事件处理函数在该标记为true的状况下不被调用,要为该标记为true的事件注册处理方法并让该方法获得执行,请使用AddHandler方法,并把最后一个参数handlerEventsToo设置为true,以下:htm
而后编写惯用的OnXXX方法:对象
3,为控件添加命令(Commands)
能为自定义控件添加如WPF内置控件同样的命令是一件很不错的事情(事实上这也是在CustomControl中下降界面和后台逻辑耦合度的一种方法,本系列随笔中的下一篇中将会具体谈谈).
WPF中内置的命令有两大类型:RoutedCommand以及RoutedUICommand,后者比前者多了一个Text属性用于在界面上自动本地化地显示该命令对应的文本,更多的能够参考WPF中的命令与命令绑定(一)以及WPF中的命令与命令绑定(二).
这里咱们来定义一个命令,其功能是控件的语音报时.首先咱们定义一个命令:blog
参数分别为命名的显示名称,命令的名称,命令的拥有者类型.
而后在控件的静态函数中定义一个命令绑定,该命令绑定定义了命令的具体细节:对应的命令是什么?其完成什么样的功能,当前环境下其能执行吗?继承
CanExecuteRoutedEventArgs的CanExecute属性用于指示当前命令是否可用,也就是说系统会不断地检视该命令与该命令的做用对象,并根据你所提供的条件来判断当前命令是否可用,好比文本框状态变为"只读"后,其"粘贴"命令将不可用,做用于该文本框的粘贴按钮会自动被禁用,反之则启用.
new ExecutedRoutedEventHandler(ExecuteSpeak)委托指定了当该命令被执行时所要完成的任务,这经过回调ExcuteSpeak函数来实现.
咱们也能够为命令添加快捷键,这是经过InputBinding来实现的,其将命令与命令的快捷键关联起来,好比:
这样,当咱们鼠标点击控件时就会引起控件的Speak命令,从而调用SpeakTheTime函数进行语音播报.
快捷键能够经过MouseGesture或KeyGesture来定义.
4,优势与缺点:
正如在在WPF中自定义控件(1) 中谈到的同样,UserControl能比较快速的打造自定义控件,但其对模板样式等缺少很好的支持,打造出来的控件不如WPF内置控件同样灵活,在本系列随笔的下一篇中,咱们将介绍如何打造能对WPF新特性提供彻底支持的CustomControl.
转自: http://www.cnblogs.com/zhouyinhui/archive/2007/10/27/939920.html 做者:周银辉