【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赋了

最后,晒一下成果:

美白前:

美白后:

- 文毕 -

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Google Dart

AngularDart4.0 指南- 模板语法一 顶

学习如何编写显示数据并在数据绑定的帮助下使用用户事件的模板。 Angular应用程序管理用户看到和可以做的事情,通过组件类实例(组件)和面向用户的模板的交互来...

1401
来自专栏NetCore

struts技术的logic标签

struts技术的logic标签-- - 开源项目最好的是可以让我们从项目的源码本身角度看项目,通过对源码的了解更多的是对设计思想融会贯通达提升整体能力的目的。...

1888
来自专栏python3

tkinter -- Spinbox

只是创建了一个 Spinbox,其它的什么也做不了,与 Scale 不同,Scale 使用缺省值就可以控制 值的改变

973
来自专栏纯洁的微笑

springboot(四):thymeleaf使用详解

在上篇文章springboot(二):web综合开发中简单介绍了一下thymeleaf,这篇文章将更加全面详细的介绍thymeleaf的使用。thymeleaf...

77310
来自专栏互联网杂技

SpringBoot ( 四 ) :thymeleaf 使用详解

简单说, Thymeleaf 是一个跟 Velocity、FreeMarker 类似的模板引擎,它可以完全替代 JSP 。相较与其他的模板引擎,它有如下三个极吸...

1783
来自专栏飞扬的花生

JQuery常用方法总结

1.json的创建方式 <script> $(function () { //第一种 var my = new Peop...

2307
来自专栏极客编程

用Vue.js递归组件构建一个可折叠的树形菜单

递归组件常用于在blog上显示注释、嵌套的菜单,或者基本上是父和子相同的类型,尽管具体内容不同。例如:

1.4K1
来自专栏三丰SanFeng

字节对齐

什么是对齐,以及为什么要对齐: 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定变...

2395
来自专栏向治洪

React 之props属性

React 里有一个非常常用的模式就是对组件做一层抽象。组件对外公开一个简单的属性(Props)来实现功能,但内部细节可能有非常复杂的实现。 可以使用 JSX ...

2185
来自专栏禅林阆苑

Vue2.5源码阅读笔记02—虚拟DOM的创建与渲染

Vue是数据驱动的MVVM框架,视图是由数据驱动生成的,因此对视图的修改不是通过操作 DOM,而是通过修改数据,相比传统使用jQuery的前端开发,能够大大简化...

1.2K77

扫码关注云+社区

领取腾讯云代金券