3.为自定义控件添加属性

3.为自定义控件添加属性

原文请看我博客: http://clzf.co/blog.php?id=3

前面"废话"了两篇 现在开始慢慢的踏入自定义控件开发的主题 首先来说说属性

对于一个控件来说属性和事件什么的是必须的 要不然也没啥意义了 对于"属性"两个字而言 我想大家已近再熟悉不过了 如果你说你不知道 那我只能说 难道你没有用C#写过类 没有在里面定义过属性么 是的 没错 我说的就是它

其实在控件里面属性也是那样定义的 比起普通的类不同的是 当你给控件定义了一个属性的时候 控件添加到窗体上的时候 可以在右下角的属性窗口看到它 并且修改它

当我们的控件继承Control的时候已经有一些属性了 是从Control继承下来的一些基本的属性 比如Text BackColor BackgroundImage...

如果这些属性够你用 你大可不必自己去定义一个属性  可是如果从Control中得到的属性满足不了你的时候 这个时候你就需要自定义属性了 比如:

继续用那个万年不变的MyControl说事 我们在里面加入这样的代码:

public class MyControl : Control
{
    private Color _BoardColor;

    public Color BoardColor {
        get { return _BoardColor; }
        set { _BoardColor = value; }
    }

    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        using (Pen p = new Pen(this._BoardColor)) {
            g.DrawRectangle(p, 0, 0, this.Width - 1, this.Height - 1);
        }
        base.OnPaint(e);
    }
}

从上面的代码可以看出 我想为MyControl绘制一个边框 而边框的颜色可以根据用户的喜欢去设置 所以我为控件提供了一个BoardColor属性

如果你就这样把控件添加到窗体上 你可能暂时看不到任何效果 因为BoardColor我们压根就没有给他赋值 对于Color而已 如果没有赋值默认是透明色 String类型而言默认是"" int类型而言默认是0 Object而言默认是null...自己YY吧、、

但是 你可以在右下角的属性窗口发现BoardColor属性的存在 而vs非常智能的知道他表示一个颜色 所以当我们点击的时候vs弹出了颜色选择的窗口

估计当你 在里面随便选着了一个颜色的时候 可能你会问 为什么明明就已经为控件的BoardColor赋值了 为什么还是在窗体上没有看到效果呢?但是 这个时候 你去拖动一下控件位置 或者改变一下大小 你就会发现控件出现边框了 为什么呢?

其实和简单的道理啊 我们绘制控件的代码是在Paint事件里面绘制的 而Paint事件又不是一直在哪里执行绘制自己 而是系统通知它去绘制的时候才会执行 什么情况系统才会去通知它? 比如你改变了控件大小的时候 或者控件被拖出屏幕区域的时候 然后在拖回来的时候 ....

为什么在这样的情况下才会重绘?说点题外话吧:

当你盯着你的显示器的时候 运行着各种各样的程序的时候 你凭什么就能看到那些程序的窗体?你又凭什么能看到 窗体上的那些按钮啥的?那些全是都是绘图函数画上去的 包括你看到的整个桌面都是绘图函数来呈现的 当你运行一个程序出来的时候出来一个窗体的时候 然后系统就会在桌面上绘制一个窗体出来 至于窗体长什么样子的 那个是由你的程序来决定怎么绘制自己的长相 或许你会说 卧槽 你这在说啥啊 我写代码的时候 都是拖一个控件出来 然后双击写代码 根本就没有见到过啥 绘制窗体自己长相的代码 程序运行依然出来一个窗体啊? 是的 你没有做过 那是因为你没有做 就一个默认处理了 所以你系统主题长啥样子 你窗体就啥风格 继续刚才的说 当你运行出来一个程序的时候 会在桌面绘制一个窗体 而当你移动这个窗体到另一个位置的时候 系统会刷新刚才的区域 然后在新的位置绘制一个窗体出来 或许到这里你有些矛盾了 我一会儿又说 系统在绘制窗体 一会儿又说是窗体自己在绘制自己 到底啥跟啥啊、、好吧 其实是窗体自己在绘制自己 而系统所做的就是 通知窗体你应该绘制自己了 也就是所谓的Paint事件 如果是Windows操作系统上 系统会向窗口发出WM_PAINT消息去通知窗体 你现在绘制一下自己 然后控件去执行Paint里面的代码 所以当我们去修改BoardColor属性的时候 系统不会知道我们是想要重新绘制一下控件的边框 系统只会关心桌面上的一些变化情况 比如当你的窗体被拖出屏幕区域外了 再拖回来的时候系统已近不知道 刚才被拖出屏幕区域的地方是啥样子了 所以会对着窗体说 "喂 刚才你被拖出屏幕外了 我不知道你张上样子的 你快绘制一下自己" 然后控件就去执行Paint事件 然后Paint里面调用一些绘图函数绘制自己 其实那些绘图函数的说白了 就是去更新显示缓存区的数据 然后由系统去调用显卡的接口在显示器上呈现、、、卧槽 尼玛 扯远了 算了算了 以上言论纯属个人YY经供参考、、

- -!、、上面扯了那么多 其实我想说的是 要解决刚才的问题其实 加上一句this.Invalidate();就可以了:

public class MyControl : Control
{
    private Color _BoardColor = Color.Red;

    public Color BoardColor {
        get { return _BoardColor; }
        set {
            if (value == _BoardColor) return;
            _BoardColor = value;
            this.Invalidate();
        }
    }

    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        using (Pen p = new Pen(this._BoardColor)) {
            g.DrawRectangle(p, 0, 0, this.Width - 1, this.Height - 1);
        }
        base.OnPaint(e);
    }
}

从上面的代码来看 我默认让边框颜色为红色 而且在属性的set里面 我加上了两句代码 首先是this.Invalidate()他的作用就是 告诉控件 整个绘制一次 其实他的目的是使得整个窗体无效 然后会通知Paint事件重绘无效区域

哦对了 这里说一句 对于Windows而言 无论你是一个Button还是一个Form还是一个Panel 对于Windows来说都是一个窗体 他们都是一个四四方方的矩形区域 只是一些行为和长相有点不一样罢了

哦还有 可能你看到我上面为什么会对 Width和Height分别 减去了1 要注意  对于矩形框和一个填充的矩形区域 是有区别的 这个属于GDI的范畴了 这里不在啰嗦了 上面已经扯远了一次了 你姑且这样理解吧看看有没有道理

如果 一个像素点表示一个单位宽度 那么一个像素点 如果你当成1*1的填充区域那说的过去 如果你当作一个矩形框 注意是矩形框 那你是当他是一个1*1的矩形框呢 还是0*0呢? 其实说白了 重点就是 你到底把不把边框也算进去一个宽度? 如果正要扯 还是比较绕 你干脆就暂且理解为 如果一个矩形区域 作为填充的话 那大小该多少就多少 如果作为一个矩形边框来绘制的话 出来的效果会比你指定的高度和宽度多出一个像素距离来、、所以 如果那里我不减去1的话 就绘制到外面去了(注意虽然控件在窗体上 不要YY的以为绘制到外面 就是到窗体上去了 每个窗体(控件)都有自己的矩形区域 自己的DC 不要以为超出了范围 就会绘制到外面的东西去了)

尼玛 我真扯、、这篇文章到底是要讲啥来着、、、算了 继续扯属性

解释一下我set里面的代码:

  1. 如果你新设置的颜色本来就和现在的颜色一样 就没有必要继续了
  2. 重新设置属性的为指定的值
  3. 使整个窗口无效 然后绘制 注意Invalidate有一个重载 接受一个矩形区域 一个无效去 使得重绘的时候 只绘制无效区域 如果没有传递参数就表示整个窗体(所指的控件)重绘一次 由于我们绘制的是边框 是整个控件的区域 所以只能全部重绘 当然你不想全部重绘 执行四次也可以this.Invalidate(new Rectangle(0,0,1,this.Height))....你懂的、、
注意:以后在重绘区域的时候 如果能直接确定只需要重绘某个区域 尽量只重绘那个区域就行了 我想不用说为什么你也应该明白
属性的一些特性:

上面扯了那么多不怎么和这篇文章搭调的东西 下面来说说和本文搭调的东西

先来看看属性的默认值 额 这里说的默认值可刚才上面说的那个赋值一个默认值有点区别 平时不知道大家有没有发现这样一个现象截图一个吧:


这个是窗体的BackColor属性 我把它设置成了黑色、、、然后我右键属性的时候发现有Reset故名思议就是重置属性的值 但是重置成什么值?并不是我们在代码里面 直接赋值的那个值 这个值我们得告诉vs是啥 我在刚才的控件中随便再加上一个属性 然后在看看:

private int _Test = 50;

[DefaultValue(100)]//引用 System.ComponentModel;
public int Test {
    get { return _Test; }
    set { _Test = value; }
}

如上代码 我在属性上面的方括号里写上DefaultValue然后在里面写上了一百 而我直接给Test赋值的是50 接下来添加到窗体上看看有啥效果:

可以看到默认赋值的50右键也能看到Reset处于可用状态 点一下啥效果?

值变成了100 当再次右键的时候发现Reset已经禁用了 我想不用我解释为什么吧

或许你这里有点疑问为什么我刚才不是有个BoardColor吗?为什么不直接用BoardColor来演示 非得搞一个Test?请自己看DefaultValue的构造器你就明白了 并不是什么值都是可以在DefaultValue里面指定的因为DefaultValue里面没有DefaultValue(Color)这个构造器也没有DefaultValue(Object)这个 至于为什么没有我也不知道 你姑且理解为不能这样写DefaultValue(new ObjectName(...)) 因为这样的代码只会出现在给 类里面的全局变量赋值 或者代码块里面 如果能想这样写 违背了原则 但是里面有个DefaultValue(Type type,String value);如果是Color的话 到还可以用他就像:

private Color _BoardColor = Color.Red;

[DefaultValue(typeof(Color), "Red")]//引用 System.ComponentModel;
public Color BoardColor {
    get { return _BoardColor; }
    set { _BoardColor = value; }
}

当然 这个也不是万能的 如果是一些其他类型的值的时候 得通过一种叫做"魔术命名"的方式 所谓魔术命名 我理解的就是 只要你按照一定规则去命名 就会有意想不到的结果的一种神奇的东西 比如现在不用DefaulValue来设置BoardColor的默认值了换成这种方式:

private Color _BoardColor = Color.Red;

public Color BoardColor {
    get { return _BoardColor; }
    set {_BoardColor = value; }
}

public bool ShouldSerializeBoardColor() {
    return Color.Red != this._BoardColor;
}

public void ResetBoardColor() {
    this._BoardColor = Color.Red;
}

上面的代码同样可以首先我们提供两个方法 一个是bool ShouldSerialize[Name]()和void Reset[Name]()这两个方法 那个Name是指代你属性的名字 ShouldSerialize[Name] 需要返回一个布尔值来告诉IDE是否需要重置当前的属性值 如果不是红色则返回true否则false 如果是true则会显示Reset菜单 Reset[Name] 是通过点击下Reset菜单的时候需要执行的代码

如果你的属性是一个其他类型的值的话 就可以用这种方式


不知道 有时候你是不是注意到 我们在属性窗口选种某个属性的时候下面会有这个属性的描述 比如:


我选择了窗体的ShowInTaskbar下面有提示说明 而我选着了刚才我控件中的BoardColor下面啥也没有 但是如果在属性上面加上这个试试:

private Color _BoardColor = Color.Red;

[Description("控件边框的颜色")]//引用 System.ComponentModel;
public Color BoardColor {
    get { return _BoardColor; }
    set { _BoardColor = value; }
}
现在你再去看看你的属性 这个时候 就能看到 有属性的描述信息了

不知道你能不能想到这样一种需求 有时候我觉得我的属性没有必要显示在属性窗口 比如我的属性本来就是一个只读属性的时候 显示出来也没有意义 因为你无法对他进行设置 比如这种情况很多 如窗体的ClientSize 你可以在属性找到Size但你找不到ClientSize ClientSize是啥?也就是窗体的客户区的大小:

你看到的黑色部分就是窗体的客户区 平时我们用的Size是整个窗体的大小包括非客户区 也就是窗体的边框 标题栏啥的、、ClientSize是个只读属性 他是根据窗体Size变换而变化的不需要你去设置 假设你可以设置ClientSize的值 那么岂不是窗体的大小也要跟着变动?貌似道理也说得通 不过你还是去问微软为什么要搞成只读吧

遇到这种情况我们不想要在属性窗口显示的时候就加上这个

private Color _BoardColor = Color.Red;

[Browsable(false)]
public Color BoardColor {
    get { return _BoardColor; }
    set {_BoardColor = value; }
}
这样你就在属性窗口看不到了
再来说一个吧 不知道平时大家属性窗口都是怎么排序的 默认情况下 属性窗口的属性都是按字母排序的 但是还可以按照分组排序:

我们自定义的属性会在那个分组下?如果你没有指定的话 默认是在最后面 那个是啥组我也不知道- -!、当然 你也可以指定在那个分组下 也可以自己定一个一个分组

private Color _BoardColor = Color.Red;

[Category("MyGroup")]
public Color BoardColor {
    get { return _BoardColor; }
    set {_BoardColor = value; }
}
如果你输入的是一个已经存在的分组 则会分组到那个分组里面去
对了 有时候我们可能需要对属性的值进行一个验证 只能的如果不合法可以引发一个异常之类的 比如刚才的Test属性 我让他的值必须大于0:
public int Test {
    get { return _Test; }
    set {
        if (value <= 0) throw new ArgumentException("the Test value must be more than zero!"); _Test = value; } }
这样写是可以的 如果在程序运行的时候 我们代码赋值一个无效值肯定会弹异常的框出来 如果在属性窗口赋值无效值则会:

和属性相关的东西就暂时介绍到这里吧 其实还有很多比如DefaultProperty什么的、、列举这些差不多了 想看更多的东西还是百度或者 msdn 吧 哪里不这里全多了、、、在之后的文章里面还会在扯到属性的很多东西的 比如如果你的某个属性是你自定义的一个类 那么在属性窗口会怎么显示?比如怎么让你自定义的属性增加一些可视化的设计 比如自定义一个窗体弹出来修改属性的值什么的、、这些后面来说、、

哦对了 你不要看到我上面那些特性都是单独写的 就以为一个属性只能给他指定一个特性 你可以这样:

[xxxxxx()]
[xxxxxx()]
[xxxxxx()]
.....
public Type Name{}
或者这样:
[XXXXX(),XXXXX(),XXXXX(),...]
public TypeName{}


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值