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事件又不是一直在那里执行绘制自己 而是系统通知它去绘制的时候才会执行 什么情况系统才会去通知它? 比如你改变了控件大小的时候 或者控件被拖出屏幕区域的时候 然后在拖回来的时候 或者手动去通知重绘

面扯了那么多 其实我想说的是 要解决刚才的问题其实 加上一句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))....你懂的、、
注意:以后在重绘区域的时候 如果能直接确定只需要重绘某个区域 尽量只重绘那个区域就行了 我想不用说为什么你也应该明白

--属性的默认值[DefaultValue]:

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


这个是窗体的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(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菜单的时候需要执行的代码

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

--属性描述[Description]

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


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

private Color _BoardColor = Color.Red;

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

--是否在属性窗口显示属性[Browsable]

有时候我可能并不想在属性窗口显示我的数学那个 比如我的属性本来就是一个只读属性的时候 显示出来也没有意义 因为你无法对他进行设置 或者说我确实不想让他显示徂徕 比如这种情况很多 如窗体的ClientSize 你可以在属性找到Size但你找不到ClientSize ClientSize是啥?也就是窗体的客户区的大小:

你看到的黑色部分就是窗体的客户区 平时我们用的Size是整个窗体的大小包括非客户区 也就是窗体的边框 标题栏啥的、、ClientSize是个窗体内部的可用Size 他是根据窗体Size变换而变化的 虽然你也可以直接在代码中对这个ClientSize赋值 但是.Net并没有让他显示在属性窗口上

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

private Color _BoardColor = Color.Red;

[Browsable(false)]
public Color BoardColor {
    get { return _BoardColor; }
    set {_BoardColor = value; }
}

这样你就在属性窗口看不到了 属性不仅可以不显示在属性窗体上 你甚至可以加上特性不让它出现在代码提示上面

--属性分组[Category]

再来说一个吧 不知道平时大家属性窗口都是怎么排序的 默认情况下 属性窗口的属性都是按字母排序的 但是还可以按照分组排序:

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

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 Type Name{}



添加时间:2014-03-21 23:47:10 编辑时间:2016-11-03 12:33:24 阅读:1027 
C#自定义控件开发 C#控件开发
还没有人留言 要不你来抢一个沙发?
  • 编写评论

      我觉得区分大小写是一个码农的基本素质
[访问统计] 今天:65 总数:151768 提示:未成年人 请在大人陪同下浏览本站内容 还有:世界上最帅的码农 -> 石头 RSS:http://st233.com/rss Powered by -> Crystal_lz