科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件在WPF中自定义控件 UserControl

在WPF中自定义控件 UserControl

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

在这里我们将通过打造一个UserControl(用户控件)来逐步讲解如何在WPF中自定义控件,并将WPF的一些新特性引入到自定义控件中来.

作者:周银辉 来源:周银辉的博客 2007年11月20日

关键字: WPF 自定义控件 UserControl

  • 评论
  • 分享微博
  • 分享邮件
在这里我们将通过打造一个UserControl(用户控件)来逐步讲解如何WPF中自定义控件,并将WPF的一些新特性引入到自定义控件中来.

  我们制作了一个带语音报时功能的钟表控件, 效果如下:

在WPF中自定义控件

  在VS中右键单击你的项目,点击"添加新项目",在出现的选择列表中选择"UserControl",VS会自动为你生成一个*.xaml文件以及其对应的后台代码文件(*.cs或其它).

  值得注意的是,自动生成的代码中,你的控件是继承于System.Windows.Controls.UserControl类的,这对应你的控件而言并不一定是最恰当的基类,你可以修改它,但注意你应该同时修改*.cs文件和*.xaml文件中的基类,而不只是修改*.cs文件,否则当生成项目时会报错"不是继承于同一基类".修改*.xaml文件的方法是:将该文件的第一行和最后一行的"UserControl"改成与你认为恰当的基类名称.

  1,为控件添加属性(依赖属性,DependencyProperty)

  正如下面的代码所示:

以下是引用片段:
  public static readonly DependencyProperty TimeProperty =
   DependencyProperty.Register("Time", typeof(DateTime), typeof(ClockUserCtrl),
   new FrameworkPropertyMetadata(DateTime.Now,new PropertyChangedCallback(TimePropertyChangedCallback)));

  我们为控件(或者任何一个WPF类)添加的依赖属性都是"公开的","静态的","只读的",其命名方式是"属性名+Property",这是依赖属性一成不变的书写方式.对于依赖属性的注册可以在声明该属性时就调用DependencyProperty.Register()方法注册,也可以在其静态构造方法中注册.上面的DependencyProperty.Register方法的几个参数分别是:属性名(该属性名与声明的依赖属性名称"XXXProperty"相比仅仅是少了"Property"后缀,其它完全一样,否则在运行时会报异常),属性的数据类型,属性的拥有者的类型,元数据.

  关于参数中传递的元数据:如果是普通的类则应该传递PropertyMetadata,如果是FrameworkElement则可以传递FrameworkPropertyMetadata,其中FrameworkPropertyMetadata中可以制定一些标记表明该属性发生变化时控件应该做出什么反应,比如某属性的变化会影响到该控件的绘制,那么就应该像这样书写该属性的元数据: new FrameworkPropertyMetadata(defauleValue, FrameworkPropertyMetadataOptions.AffectsRender);这样当该属性发生变化时系统会考虑重绘该控件.另外元数据中还保护很多内容,比如默认值,数据验证,数据变化时的回调函数,是否参与属性"继承"等.

  然后,我们将该依赖属性包装成普通属性:

以下是引用片段:
   [Description("获取或设置当前日期和时间")]
   [Category("Common Properties")]
   public DateTime Time
   {
   get
   {
   return (DateTime)this.GetValue(TimeProperty);
   }
   set
   {
   this.SetValue(TimeProperty, value);
   }
   }

  GetValue和SetValue方法来自于DependencyObject类,其用于获取或设置类的某属性值.

  注意:在将依赖属性包装成普通属性时,在get和set块中除了按部就班的调用GetValue和SetValue方法外,不要进行任何其它的操作.下面的代码是不恰当的:

以下是引用片段:
   [Description("获取或设置当前日期和时间")]
   [Category("Common Properties")]
   public DateTime Time
   {
   get
   {
   return (DateTime)this.GetValue(TimeProperty);
   }
   set
   {
   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);语句(因为该语句会引发时间被更新了的事件),还是在传递的依赖属性元数据做文章:

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章