欢迎访问 生活随笔!

凯发k8官方网

当前位置: 凯发k8官方网 > 编程语言 > asp.net >内容正文

asp.net

[转]在wpf中自定义控件 usercontrol -凯发k8官方网

发布时间:2024/10/12 asp.net 32 豆豆
凯发k8官方网 收集整理的这篇文章主要介绍了 [转]在wpf中自定义控件 usercontrol 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

在这里我们将将打造一个usercontrol(用户控件)来逐步讲解如何在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);语句(因为该语句会引发时间被更新了的事件),还是在传递的依赖属性元数据做文章:
new frameworkpropertymetadata(datetime.now,new propertychangedcallback(timepropertychangedcallback)),我们为属性的变化指定了一个回调函数,当该属性变化时该回调函数就会被执行:

        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);
                
            }
        }


2,为控件添加事件(传阅事件,routedevent)
添加传阅事件的方法与添加依赖属性的方法很类似:

        public static readonly routedevent timeupdatedevent = 
            eventmanager.registerroutedevent("timeupdated",
             routingstrategy.bubble, typeof(routedpropertychangedeventhandler), typeof(clockuserctrl));


其支持方法eventmanager.registerroutedevent()对应的几个参数分别为:事件名称,事件传阅的方式(向上传阅,向下传阅或不传阅),事件对应的eventhandler的类型,事件拥有者的类型)
然后将事件包装成普通的.net事件:

        [description("日期或时间被更新后发生")]
        public event routedpropertychangedeventhandler timeupdated
        {
            add
            {
                this.addhandler(timeupdatedevent, value);
            }
            remove
            {
                this.removehandler(timeupdatedevent, value);
            }
        }

注意,与依赖属性一样,不要在add与remove块中添加除addhandler与removehandler以外的代码.
题外话,事件参数中的e.handled=true并不是终止事件的传阅,这只是为事件做一个标记而已,以便在默认情况下的让那些事件处理函数在该标记为true的情况下不被调用,要为该标记为true的事件注册处理方法并让该方法得到执行,请使用addhandler方法,并把最后一个参数handlereventstoo设置为true,如下:

this.myinkcanvas.addhandler(
      inkcanvas.mouseleftbuttondownevent,
      new mousebuttoneventhandler(
          myinkcanvas_mouseleftbuttondown),
      true);

private void myinkcanvas_mouseleftbuttondown(
       object sender, mousebuttoneventargs e)
{
       //do something
}


然后编写惯用的onxxx方法:

        protected virtual void ontimeupdated(datetime oldvalue, datetime newvalue)
        {
            routedpropertychangedeventargs arg = 
                new routedpropertychangedeventargs(oldvalue, newvalue,timeupdatedevent);
            this.raiseevent(arg);
            
        }


3,为控件添加命令(commands)
能为自定义控件添加如wpf内置控件一样的命令是一件很不错的事情(事实上这也是在customcontrol中降低界面和后台逻辑耦合度的一种方法,本系列随笔中的下一篇中将会具体谈谈).
wpf中内置的命令有两大类型:routedcommand以及routeduicommand,后者比前者多了一个text属性用于在界面上自动本地化地显示该命令对应的文本,更多的可以参考wpf中的命令与命令绑定(一)以及wpf中的命令与命令绑定(二). 
这里我们来定义一个命令,其功能是控件的语音报时.首先我们定义一个命令:

        public static readonly routeduicommand speakcommand = new routeduicommand("speak", "speak", typeof(clockuserctrl));

参数分别为命名的显示名称,命令的名称,命令的拥有者类型.
然后在控件的静态函数中定义一个命令绑定,该命令绑定定义了命令的具体细节:对应的命令是什么?其完成什么样的功能,当前环境下其能执行吗?

            commandbinding commandbinding =
                new commandbinding(speakcommand, new executedroutedeventhandler(executespeak),
                new canexecuteroutedeventhandler(canexecutespeak));         private static void executespeak(object sender, executedroutedeventargs arg)
        {
            clockuserctrl clock = sender as clockuserctrl;
            if (clock != null)
            {
                clock.speakthetime();
            }
        }

        private static void canexecutespeak(object sender, canexecuteroutedeventargs arg)
        {
            clockuserctrl clock = sender as clockuserctrl;
            arg.canexecute = (clock != null);
        }

canexecuteroutedeventargscanexecute属性用于指示当前命令是否可用,也就是说系统会不断地检视该命令与该命令的作用对象,并根据你所提供的条件来判断当前命令是否可用,比如文本框状态变为"只读"后,其"粘贴"命令将不可用,作用于该文本框的粘贴按钮会自动被禁用,反之则启用.
new executedroutedeventhandler(executespeak)
委托指定了当该命令被执行时所要完成的任务,这通过回调excutespeak函数来实现.

        private static void executespeak(object sender, executedroutedeventargs arg)
        {
            clockuserctrl clock = sender as clockuserctrl;
            if (clock != null)
            {
                clock.speakthetime();
            }
        }         private void speakthetime()
        {
            datetime localtime = this.time.tolocaltime();
            string texttospeak = "现在时刻,"   
                localtime.toshortdatestring()  ","
                localtime.toshorttimestring()    
                ",星期"   (int)localtime.dayofweek;

            this.speecher.speakasync(texttospeak);
        }

我们也可以为命令添加快捷键,这是通过inputbinding来实现的,其将命令与命令的快捷键关联起来,比如:

            inputbinding inputbinding = new inputbinding(speakcommand, new mousegesture(mouseaction.leftclick));
            commandmanager.registerclassinputbinding(typeof(clockuserctrl), inputbinding);

这样,当我们鼠标点击控件时就会引发控件的speak命令,从而调用speakthetime函数进行语音播报.
快捷键可以通过mousegesture或keygesture来定义.

4,优点与缺点:
正如在在wpf中自定义控件(1) 中谈到的一样,usercontrol能比较快速的打造自定义控件,但其对模板样式等缺乏很好的支持,打造出来的控件不如wpf内置控件一样灵活,在本系列随笔的下一篇中,我们将介绍如何打造能对wpf新特性提供完全支持的customcontrol.

demo

 

转自: http://www.cnblogs.com/zhouyinhui/archive/2007/10/27/939920.html 作者:周银辉

转载于:https://www.cnblogs.com/luohengstudy/p/9888075.html

与50位技术专家面对面20年技术见证,附赠技术全景图

总结

以上是凯发k8官方网为你收集整理的[转]在wpf中自定义控件 usercontrol的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得凯发k8官方网网站内容还不错,欢迎将凯发k8官方网推荐给好友。

  • 上一篇:
  • 下一篇:
网站地图