在WPF中自定义控件
合集下载
相关主题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
成一个 UserControl 并修改其外观,然后后台逻辑代码直接访问这些子元素.其最大的弊端在于:其对模板样式等支持度不好,其重复使用的范围有限. CustomControl, 其开发出来的控件才真正具有 WPF 风格,其对模板样式有着很好的支持,这是因为打造 CustomControl 时做到了逻辑代码与外观相分离,
承,打造 CustomControl 时其会为从 System.Windows.Controls.Control 继承.但实际情况下,也许我们从他们的衍生类别开始继承会得到更多的好处(更好的重 用已 有的逻辑),比如你的控件拥有更多的类似于 Button 的某些特性,那么从 Button 开始继承就比从 Control 继承少写很多代码.
二,UserControl 还是 CustomControl? 要在 WPF 中自定义一个控件,使用 UserControl 与 CustomControl 都是 不错的选择(除此之外,还有更多选择,比如打造一个自定义的面板,但这不在本文
的讨论范围),他们的区别在于: UserControl,其更像 WinForm 中自定义控件的开发风格,在开发上更简单快速,几乎可以简单地理解为:利用设计器来将多个已有控件作为子元素来拼凑
在 VS 中右键单击你的项目,点击"添加新项目",在出现的选择列表中选择"UserControl",VS 会自动为你生成一个*.xaml 文件以及其对应 的后台代码文件
2
(*.cs 或其它). 值得注意的是,自动生成的代码中,你的控件是继承于 erControl 类的,这对应你的控件而 言并不一定是最恰当的基 类,你可以修改它,但注意你应该同时修改*.cs 文件和*.xaml 文件中的基类,而不只是修改*.cs 文件,否则当生成项目时会 报错"不是继承于同一基类".修改 *.xaml 文件的方法是:将该文件的第一行和最后一行的"UserControl" 改成与你认为恰当的基类名称.
3
[Description("获 取或设置当前日期和时间")] [Category("Common Properties")] public DateTime Time
{ get { return (DateTime)this.GetValue(TimeProperty); } set { this.SetValue(TimeProperty, value); }
1,为控件添加属性(依赖属性,DependencyProperty) 正如下面的代码所示:
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof(DateTime), typeof(ClockUserCtrl), ew FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)));
在 WPF 中自定义控件(1):概述
周银辉
一, 不一定需要自定义控件 在使用 WPF 以前,动辄使用自定义控件几乎成了惯性思 维,比如需要一个带图片的按钮,但在 WPF 中此类任务却不需要如此大费周章,因为控件可以嵌
套使用以及可以为控件外观打造一套新的样式就可以了.是否需要 我们来自定义控件,这需要你考虑目前已有控件的真正逻辑功能而不要局限于外观 ,如果 目前的控件都不能直觉地表达你的想法,那么你可以自己来打造一个控件, 否则,也许我们仅仅改变一下目前控件的模板等就可以完成任务.很多人在自定 义控件上经常犯的错误是:重复撰写已有的逻辑
private static void TimePropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs arg) { if (sender != null && sender is ClockUserCtrl) { ClockUserCtrl clock = sender as ClockUserCtrl; clock.OnTimeUpdated((DateTime)arg.OldValue, (DateTime)arg.NewValue);
} GetValue 和 SetValue 方法来自于 DependencyObject 类,其用于获取或设置类的某属性值.
注意:在将依赖属性包装成普通属性时,在 get 和 set 块中除了按部就班的调用 GetValue 和 SetValue 方法外,不要进行任何其它的操作.下面的代码
是不恰当的:
[Description("获 取或设置当前日期和时间")] [Category("Common Properties")] public DateTime Time
{ get { return (DateTime)this.GetValue(TimeProperty); } set { 4
this.SetValue(TimeProperty, value); this.OnTimeUpdated(value);//Error } } 在以前这或许是很多人的惯用写法,但在 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)), 我们为属性 的变化指定了一个回调函数,当该属性变化时该回调函数就会被执行:
我们为控件(或者任何一个 WPF 类)添加的依赖属性都是"公开的","静态的","只读的",其命名 方式是"属性名+Property",这是依赖属性一成不变的 书写方式.对于依赖属性的注册可以在声明该属性时就调用 DependencyProperty.Register()方法注册,也可以在其静态构造方法中 注册.上面的 DependencyProperty.Register 方法的几个参数 分别是:属性名(该属性名与声明的依赖属性名称"XXXProperty"相比仅仅是少了"Property" 后缀,其它完全一样,否则在运行时会报异 常),属性的数据类型,属性的拥有者的类型,元数据. 关 于 参 数 中 传 递 的 元 数 据 : 如 果 是 普 通 的 类 则 应 该 传 递 PropertyMetadata, 如 果 是 FrameworkElement 则 可 以 传 递 FrameworkPropertyMetadata, 其中 FrameworkPropertyMetadata 中可以制定一些标记表明该属 性发生变化时控件应该做出什么反应,比如某 属 性 的 变 化 会 影 响 到 该 控 件 的 绘 制 , 那 么 就 应 该 像 这 样 书 写 该 属 性 的 元 数 据 : new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);这样当该属性发生变化时系统会考虑重绘该控件.另外 元数据中还保护很多内容,比 如默认值,数据验证,数据变化时的回调函数,是否参与属性"继承"等. 然后,我们将该依赖属性包装成普通属性:
{ add { this.AddHandler(TimeUpdatedEvent, value); } remove { this.RemoveHandler(TimeUpdatedEvent, value); }
其支持方法 EventManager.RegisterRoutedEvent()对 应的几个参数分别为:事件名称,事件传阅的方式(向上传阅,向下传阅或不传阅),事件对应的 EventHandler 的类型,事件拥有者的类型) 然后将事件包装成普通的.NET 事件:
[Description("日 期或时间被更新后发生")] public event RoutedPropertyChangedEventHandler<DateTime> TimeUpdated
即使换上一 套完全不同的视觉树其同样能很好的工作,就像 WPF 内置的控件一样. 在使用 Visual Studio 打造控件时,UserControl 与 CustomControl 的差别就更加明显,在项目中添加一个 UserControl 时,我们会发现 设计器为我们添加了
一个 XAML 文件以及一个对应的.CS 文件(或.VB 等),然后你就可以像设计普通窗体一样设计该 UserControl; 如果我们是在项目中添加一个 CustomControl, 情况却不是这样,设计器会为我们生成一个.CS 文件(或.VB 等),该文件用于编写控件的后台逻 辑,而控件的外观却定义在了软件的应用主题(Theme)中了(如 果你没有为软件定义通用主题,其会自动生成一个通用主题 themes\generic.xaml, 然后主题中会自动为你的控件生成一个 Style),并将通用主题与该控件关
在接下来的几节中,我们会逐步讨论如何打造 UserControl 与 CustomControl 以及让它们更好支持 WPF 新特性.
在 WPF 中自定义控件(2) UserControl
周银辉
在这里我们将将打造一个 UserControl(用户控件)来逐步讲解如何在 WPF 中自定义控件,并将 WPF 的一些新特性引入到自定义控件中来. 我们制作了一个带语音报时功能的钟表控件, 效果如下:
} }
5
2,为控件添加事件(传阅事件,RoutedEvent) 添加传阅事件的方法与添加依赖属性的方法很类似:
public static readonly RoutedEvent TimeUpdatedEvent = EventManager.RegisterRoutedEvent("TimeUpdated", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<DateTime>), typeof(ClockUserCtrl));
1
联了起来.这也就是 CustomControl 对样式的支持度比 UserControl 好的原因.
三,继承于 UserContorl,Control 还是其它? 如果你准备打造一个控件,并使用像 Visual Studio 这样的工具来开发的话,打造 UserControl 时其会自动为你从 erControl 继
承,打造 CustomControl 时其会为从 System.Windows.Controls.Control 继承.但实际情况下,也许我们从他们的衍生类别开始继承会得到更多的好处(更好的重 用已 有的逻辑),比如你的控件拥有更多的类似于 Button 的某些特性,那么从 Button 开始继承就比从 Control 继承少写很多代码.
二,UserControl 还是 CustomControl? 要在 WPF 中自定义一个控件,使用 UserControl 与 CustomControl 都是 不错的选择(除此之外,还有更多选择,比如打造一个自定义的面板,但这不在本文
的讨论范围),他们的区别在于: UserControl,其更像 WinForm 中自定义控件的开发风格,在开发上更简单快速,几乎可以简单地理解为:利用设计器来将多个已有控件作为子元素来拼凑
在 VS 中右键单击你的项目,点击"添加新项目",在出现的选择列表中选择"UserControl",VS 会自动为你生成一个*.xaml 文件以及其对应 的后台代码文件
2
(*.cs 或其它). 值得注意的是,自动生成的代码中,你的控件是继承于 erControl 类的,这对应你的控件而 言并不一定是最恰当的基 类,你可以修改它,但注意你应该同时修改*.cs 文件和*.xaml 文件中的基类,而不只是修改*.cs 文件,否则当生成项目时会 报错"不是继承于同一基类".修改 *.xaml 文件的方法是:将该文件的第一行和最后一行的"UserControl" 改成与你认为恰当的基类名称.
3
[Description("获 取或设置当前日期和时间")] [Category("Common Properties")] public DateTime Time
{ get { return (DateTime)this.GetValue(TimeProperty); } set { this.SetValue(TimeProperty, value); }
1,为控件添加属性(依赖属性,DependencyProperty) 正如下面的代码所示:
public static readonly DependencyProperty TimeProperty = DependencyProperty.Register("Time", typeof(DateTime), typeof(ClockUserCtrl), ew FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)));
在 WPF 中自定义控件(1):概述
周银辉
一, 不一定需要自定义控件 在使用 WPF 以前,动辄使用自定义控件几乎成了惯性思 维,比如需要一个带图片的按钮,但在 WPF 中此类任务却不需要如此大费周章,因为控件可以嵌
套使用以及可以为控件外观打造一套新的样式就可以了.是否需要 我们来自定义控件,这需要你考虑目前已有控件的真正逻辑功能而不要局限于外观 ,如果 目前的控件都不能直觉地表达你的想法,那么你可以自己来打造一个控件, 否则,也许我们仅仅改变一下目前控件的模板等就可以完成任务.很多人在自定 义控件上经常犯的错误是:重复撰写已有的逻辑
private static void TimePropertyChangedCallback(DependencyObject sender, DependencyPropertyChangedEventArgs arg) { if (sender != null && sender is ClockUserCtrl) { ClockUserCtrl clock = sender as ClockUserCtrl; clock.OnTimeUpdated((DateTime)arg.OldValue, (DateTime)arg.NewValue);
} GetValue 和 SetValue 方法来自于 DependencyObject 类,其用于获取或设置类的某属性值.
注意:在将依赖属性包装成普通属性时,在 get 和 set 块中除了按部就班的调用 GetValue 和 SetValue 方法外,不要进行任何其它的操作.下面的代码
是不恰当的:
[Description("获 取或设置当前日期和时间")] [Category("Common Properties")] public DateTime Time
{ get { return (DateTime)this.GetValue(TimeProperty); } set { 4
this.SetValue(TimeProperty, value); this.OnTimeUpdated(value);//Error } } 在以前这或许是很多人的惯用写法,但在 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)), 我们为属性 的变化指定了一个回调函数,当该属性变化时该回调函数就会被执行:
我们为控件(或者任何一个 WPF 类)添加的依赖属性都是"公开的","静态的","只读的",其命名 方式是"属性名+Property",这是依赖属性一成不变的 书写方式.对于依赖属性的注册可以在声明该属性时就调用 DependencyProperty.Register()方法注册,也可以在其静态构造方法中 注册.上面的 DependencyProperty.Register 方法的几个参数 分别是:属性名(该属性名与声明的依赖属性名称"XXXProperty"相比仅仅是少了"Property" 后缀,其它完全一样,否则在运行时会报异 常),属性的数据类型,属性的拥有者的类型,元数据. 关 于 参 数 中 传 递 的 元 数 据 : 如 果 是 普 通 的 类 则 应 该 传 递 PropertyMetadata, 如 果 是 FrameworkElement 则 可 以 传 递 FrameworkPropertyMetadata, 其中 FrameworkPropertyMetadata 中可以制定一些标记表明该属 性发生变化时控件应该做出什么反应,比如某 属 性 的 变 化 会 影 响 到 该 控 件 的 绘 制 , 那 么 就 应 该 像 这 样 书 写 该 属 性 的 元 数 据 : new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);这样当该属性发生变化时系统会考虑重绘该控件.另外 元数据中还保护很多内容,比 如默认值,数据验证,数据变化时的回调函数,是否参与属性"继承"等. 然后,我们将该依赖属性包装成普通属性:
{ add { this.AddHandler(TimeUpdatedEvent, value); } remove { this.RemoveHandler(TimeUpdatedEvent, value); }
其支持方法 EventManager.RegisterRoutedEvent()对 应的几个参数分别为:事件名称,事件传阅的方式(向上传阅,向下传阅或不传阅),事件对应的 EventHandler 的类型,事件拥有者的类型) 然后将事件包装成普通的.NET 事件:
[Description("日 期或时间被更新后发生")] public event RoutedPropertyChangedEventHandler<DateTime> TimeUpdated
即使换上一 套完全不同的视觉树其同样能很好的工作,就像 WPF 内置的控件一样. 在使用 Visual Studio 打造控件时,UserControl 与 CustomControl 的差别就更加明显,在项目中添加一个 UserControl 时,我们会发现 设计器为我们添加了
一个 XAML 文件以及一个对应的.CS 文件(或.VB 等),然后你就可以像设计普通窗体一样设计该 UserControl; 如果我们是在项目中添加一个 CustomControl, 情况却不是这样,设计器会为我们生成一个.CS 文件(或.VB 等),该文件用于编写控件的后台逻 辑,而控件的外观却定义在了软件的应用主题(Theme)中了(如 果你没有为软件定义通用主题,其会自动生成一个通用主题 themes\generic.xaml, 然后主题中会自动为你的控件生成一个 Style),并将通用主题与该控件关
在接下来的几节中,我们会逐步讨论如何打造 UserControl 与 CustomControl 以及让它们更好支持 WPF 新特性.
在 WPF 中自定义控件(2) UserControl
周银辉
在这里我们将将打造一个 UserControl(用户控件)来逐步讲解如何在 WPF 中自定义控件,并将 WPF 的一些新特性引入到自定义控件中来. 我们制作了一个带语音报时功能的钟表控件, 效果如下:
} }
5
2,为控件添加事件(传阅事件,RoutedEvent) 添加传阅事件的方法与添加依赖属性的方法很类似:
public static readonly RoutedEvent TimeUpdatedEvent = EventManager.RegisterRoutedEvent("TimeUpdated", RoutingStrategy.Bubble, typeof(RoutedPropertyChangedEventHandler<DateTime>), typeof(ClockUserCtrl));
1
联了起来.这也就是 CustomControl 对样式的支持度比 UserControl 好的原因.
三,继承于 UserContorl,Control 还是其它? 如果你准备打造一个控件,并使用像 Visual Studio 这样的工具来开发的话,打造 UserControl 时其会自动为你从 erControl 继