zoukankan      html  css  js  c++  java
  • 【C#】妈妈再也不用担心自定义控件如何给特殊类型的属性添加默认值了,附自定义GroupBox一枚

    ------------------更新:201411190903------------------

    经过思考和实践,发现套路中的第1条是不必要的,就是完全可以不用定义一个名为Default+属性名的字段或属性,只要实现Reset和ShouldSerialize这俩方法就可以了。关于这俩方法,应该是有相关文档的,果然,在MSDN找到说法:http://msdn.microsoft.com/zh-cn/library/53b8022e(v=vs.80).aspx

    ------------------原文:201411182108------------------

    标题有点那啥,但确实能表达我掌握此法后的心情。

    写自定义控件时往往会有一个需求,就是给属性指定一个默认值(就是可以在VS中右键该属性→重置),如果该属性的类型是内置值类型还好,直接使用DefaultValue特性就好,例如:

    [DefaultValue(false)]
    public bool CanSelect
    {
        get;
        set;
    }

    对于能够根据字符串常量转换得到的类型也还好,可以这样:

    [DefaultValue(typeof(Font), "宋体, 9pt")]
    public Font TitleFont
    {
        get;
        set;
    }

    但这种情况下,DefaultValue的第2个参数必须是字符串常量,不能是变量、字段、属性、方法返回值啥的。题外,一个类型能否从字符串转换得到,依赖的是该类型的TypeConverter特性指定的转换类中的实现。有关TypeConverter的更多信息请参看:

    http://msdn.microsoft.com/zh-cn/library/system.componentmodel.typeconverter(v=vs.80).aspx

    回到正题,那么问题来了,如果我想让TitleFont的默认值是SystemFonts.DefaultFont咋办?刚学了一招,下面通过一个自定义控件示例说明:

    /// <summary>
    /// 增强型GroupBox
    /// </summary>
    /// <remarks>
    /// Author:AhDung
    /// Update:201411181832,可独立设置标题颜色和字体
    /// </remarks>
    public class GroupBoxEx : GroupBox
    {
        static Font defaultTitleFont; //定义一个静态字段
        /// <summary>
        /// 默认标题字体
        /// </summary>
        public static Font DefaultTitleFont //封装该静态字段,其实不封装直接使用字段也行,但字段命名必须是DefaultXXX
        {
            get { return defaultTitleFont ?? (defaultTitleFont = SystemFonts.DefaultFont); }
        }
    
        Color titleColor;
        /// <summary>
        /// 获取或设置标题颜色
        /// </summary>
        [Description("获取或设置标题颜色")]
        [DefaultValue(typeof(Color), "0, 70, 213")]
        public Color TitleColor
        {
            get { return titleColor; }
            set
            {
                if (titleColor != value)
                {
                    titleColor = value;
                    this.Invalidate();
                }
            }
        }
    
        Font titleFont;
        /// <summary>
        /// 获取或设置标题字体
        /// </summary>
        [Description("获取或设置标题字体")]
    public Font TitleFont { get { return titleFont; } set { titleFont = value ?? DefaultTitleFont; //防止属性被设为null this.Invalidate(); } } /// <summary> /// 重置标题字体 /// </summary> [EditorBrowsable(EditorBrowsableState.Never)] protected virtual void ResetTitleFont() //实现一个重置属性默认值的方法,命名须为ResetXXX { this.TitleFont = null; //属性setter中有null处理 } /// <summary> /// 是否显式设置标题字体 /// </summary> [EditorBrowsable(EditorBrowsableState.Never)] protected virtual bool ShouldSerializeTitleFont() //实现一个指示是否把属性值写入窗体Designer文件的方法,命名须是ShouldSerializeXXX { return !titleFont.Equals(DefaultTitleFont); } /// <summary> /// 重绘 /// </summary> protected override void OnPaint(PaintEventArgs e) { if ((Application.RenderWithVisualStyles && (Width >= 10)) && (Height >= 10)) { TextFormatFlags flags = TextFormatFlags.PreserveGraphicsTranslateTransform | TextFormatFlags.PreserveGraphicsClipping | TextFormatFlags.TextBoxControl | TextFormatFlags.WordBreak; if (!this.ShowKeyboardCues) { flags |= TextFormatFlags.HidePrefix; } if (this.RightToLeft == RightToLeft.Yes) { flags |= TextFormatFlags.RightToLeft | TextFormatFlags.Right; } GroupBoxRenderer.DrawGroupBox( e.Graphics, this.ClientRectangle, this.Text, this.TitleFont, this.Enabled ? this.TitleColor : SystemColors.ControlDark, flags, this.Enabled ? System.Windows.Forms.VisualStyles.GroupBoxState.Normal : System.Windows.Forms.VisualStyles.GroupBoxState.Disabled); } else { base.OnPaint(e); } } /// <summary> /// 初始化该控件 /// </summary> public GroupBoxEx() { SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.UserPaint | ControlStyles.OptimizedDoubleBuffer, true); titleColor = Color.FromArgb(0, 70, 213); ResetTitleFont(); //直接调用重置方法以初始化属性值 } }

    说明一下,写这个控件的本意是让GroupBox在NT6下,标题变得显眼一点。NT5下默认就是显眼的蓝色,但NT6是黑色,不那么显眼,影响程序体验。固然可以直接设置GroupBox的ForeColor和Font属性达到目的,但这样的话,它里面的子控件会继承,还得把子控件的这俩属性改回来~蛋疼。所以为了能独立设置GroupBox的标题的颜色和字体,增加了TitleColor和TitleFont这俩自定义属性,也正是想把TitleFont的默认值设为SystemFonts.DefaultFont时遇到了本文的问题,几经搜索,看了些有用的帖子,后来又从Control类的源码中得到正果(上述例子参考的就是Control类中的标准做法),那么既然解决了,就想着把招法和控件一起与大家分享一下。控件实现没什么好说的,下面主要就为非常规类型的属性指定默认值的套路说一下。

    就用上述控件中类型为Font、名为TitleFont的属性来说事:

    - 要有一个同类型的字段或属性,命名必须为Default+属性名,即DefaultTitleFont,并且为static。为该字段/属性赋值想要的默认值,本例为SystemFonts.DefaultFont,可见这里就不像DefaultValue只能赋值内置值类型或字符串常量那么蛋疼了,可以随意赋值~不然还说个球

    - 要实现一个Reset+属性名的无参无返回方法,即ResetTitleFont()。该方法的作用是重新把属性赋值为默认值。本例因为在属性的setter中有处理,即赋值为null时就替换为默认值,所以直接赋值null无碍,如果setter没有这种处理,就需要赋值为上面的DefaultTitleFont~切记。至于修饰符无所谓,Control中是public virtual,考虑到这个方法没必要让外部调用,所以本例是protected virtual。至于加上[EditorBrowsable(EditorBrowsableState.Never)]特性是为了让用户在使用控件时,避免在VS智能提示中出现该方法,这也是Control中的做法。原因很显然,这种方法是给设计器用的,不是给人用的,显它做甚~碍眼

    - 再实现一个ShouldSerialize+属性名的方法,无参,返回bool。即ShouldSerializeTitleFont(),这个方法从字眼上是跟序列化有关的,我没测试序列化,不知道是否有关,但可以肯定与是否把默认值写入窗体的Designer文件有关,就是VS为窗体自动生成的那个含有InitializeComponent()方法的文件,不止如此,没有这方法你根本玩不转属性重置,缺它不可。方法的逻辑是,如果为属性赋的值就是默认值,那么就告诉VS不要在InitializeComponent中显式为该属性赋值了。需要注意的是,返回true代表要显式赋值,所以在写该方法的return时请注意逻辑。修饰符什么的与Reset方法一样,没要求

    - 最后是在构造函数中为属性赋初始值,由于Reset方法就是干这个的,所以本例直接调用这方法。这不是Control的做法,Control的构造函数中没见到调用Reset方法,但有很多处理,包括调用一些internal方法,懒得追踪了,也没试过不赋初始值会不会有问题,保险起见,还是赋了一下。这里再扯点题外,就是通过DefaultValue指定的默认值其实只是在VS中右键→重置时,让VS不再往InitializeComponent显式赋值,同时在PropertyGrid中让值不再粗体显式,并不代表属性的初始值已经设置为DefaultValue指定的值,什么意思,比如本例,虽然为TitleColor指定了DefaultValue,但如果不在构造函数中初始化titleColor = Color.FromArgb(0, 70, 213)的话,TitleColor值就会是default(Color),即Color.Empty,所以在用DefaultValue后别忘了还得赋初始值,要记住DefaultValue是不负责赋值的。但是对于用Reset这种方法会不会一样,没试验过,我猜也是不会自动赋初始值的,毕竟初始化是构造函数的工作,VS再强大再智能,也不太可能自作主张见到Reset就自动往构造函数中插一条~不合适也不科学。所以保险起见,构造函数中我还是对TitleFont赋了

    最后,晒一下成果:

    美白前:

    美白后:

    - 文毕 -

  • 相关阅读:
    thinkphp5 tp5 命名空间 报错 Namespace declaration statement has to be the very first statement in the script
    开启 php 错误 提示 php-fpm 重启 nginx 500错误 解决办法 wdlinux lnmp 一键包 php脚本无法解析执行
    js 设置 cookie 定时 弹出层 提示层 下次访问 不再显示 弹窗 getCookie setCookie setTimeout
    php 二维数组 转字符串 implode 方便 mysql in 查询
    nginx 重启 ps -ef|grep nginx kill -HUP 主进程号
    jquery bootstrap help-block input 表单 提示 帮助 信息
    jquery 倒计时 60秒 短信 验证码 js ajax 获取
    jQuery如何获取同一个类标签的所有的值 遍历
    linux下C语言文件操作相关函数
    gcc,gdb用法
  • 原文地址:https://www.cnblogs.com/ahdung/p/4106745.html
Copyright © 2011-2022 走看看