11.自定义设计器Designer

这将是这系列的最后一篇 以后如果还有将会是直接的控件的开发的文章

估计这一篇文章质量会不咋滴 因为平时我压根就很少用到Designer 而且有用到的情况也都是在控件设计时绘制装饰用 所以这一篇直接将[GDI+程序设计]上的内容直接搬过来、、- -!、、而且我还发现代码运行效果似乎不理想不知道是我没有搞对还是咋滴

进入正题 平时用控件的时候 应该见过这样的东西:


控件右键菜单里面可以有自定义的选项 与此同时控件被选中的时候右上角会出现一个箭头 里面就是自定义的选项 而和前面的两篇文章一样 需要自己写一个类就是Designer(Designer需要手动添加System.Designer.dll这个引用) 总之先搞一个控件出来:

[Designer(typeof(CtrlTestDesigner))]
public class CtrlTest : Control
{
    public CtrlTest() {
        this.Size = new Size(150, 100);
    }

    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        g.FillRectangle(Brushes.DarkCyan, this.ClientRectangle);
        base.OnPaint(e);
    }
}
控件的代码就不多说了 同理上面的特性也没啥多余说明 当然现在需要做的就是完成CtrlTestDesigner这个类:
public class CtrlTestDesigner : ControlDesigner
{
    public override DesignerVerbCollection Verbs {
        get {
            DesignerVerb[] verbs = new DesignerVerb[]{
                new DesignerVerb("&ResetSize",new EventHandler(OnResetSize))
            };//要添加的菜单 而里面每个选项里面的参数分别是 显示名字和事件
            return new DesignerVerbCollection(verbs);
        }
    }

    private void OnResetSize(object sender, EventArgs e) {
        TypeDescriptor
            .GetProperties(this.Control.GetType())["Size"]
            .SetValue(this.Control, new Size(150, 100));
        //this.Control.Size = new Size(150, 100); 不要这样用
    }
}
这样目的就达到了 重写了一个Verbs里面返回需要添加的菜单 一看代码就清楚是怎么一回事了 一个数组里面是你需要添加的菜单 而里面的每一个项里面的参数分别是 菜单的名称 和对应的事件   而下面的OnResetSize就是当点击了ResetSize这个选项的时候 所执行的事件 - -!、

不过这里要强调的是可以看到我注释的那个代码 那一句虽然可以起到同样的效果但是 会出现一点小问题 比如:

我把控件拉大之后 然后点击ResetSize 控件的大小虽然是重置了  不过外面的选择框却没有 所以 这样去设置属性不靠谱 靠谱的方式 就是按照注释上面的代码来修改 当然如果你看着别扭可以这样写:

PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(this.Control.GetType());
PropertyDescriptor pdSize = pdc["Size"];
pdSize.SetValue(this.Control, new Size(150, 100));
而我上面的那样 只是为了简洁所以 直接一句完成的

Designer不仅可以添加菜单 还可以在窗体设计器上直接编辑控件 什么意思呢、、比如现在我需要在控件上画一条线 就像这样:

而这条线的位置通过一个属性给出:

[Designer(typeof(CtrlTestDesigner))]
public class CtrlTest : Control
{
    public CtrlTest() {
        this.Size = new Size(150, 100);
    }

    private int _LinePos = 20;

    public int LinePos {
        get { return _LinePos; }
        set {
            if (value == this._LinePos) return;
            _LinePos = value;
            this.Invalidate();
        }
    }

    protected override void OnPaint(PaintEventArgs e) {
        Graphics g = e.Graphics;
        g.FillRectangle(Brushes.DarkCyan, this.ClientRectangle);
        g.DrawLine(Pens.Black, this._LinePos, 0, this._LinePos, this.Height);
        base.OnPaint(e);
    }
}

当然 现在可以通过属性窗口去设置一个值 来控制线条的位置、、不过此时 我不希望通过属性窗口去控制线条的位置 因为比较麻烦 因为我不知道这个值设置多少才能设置成我满意的位置 所以我希望直接能能够在控件上 直接拖动线条的位置 这样就很方便了 但是如果就直接这样去拖动的话 明显是不起作用的 只能把控件拖来拖去的 而不是把线条的位置拖动 当然要完成那样的效果也是可以的 依然在Designer里面写代码 只是这次的代码有点不同以往的了 先按照步骤来

假设现在在控件的线条上面画一个黄色的手柄 然后在窗体设计时 通过拖动手柄来控制线条的位置 当然这个手柄在程序运行时候是不显示的:

管他那么多 先把黄色的手柄话上去再说、、不过不是在你控件的paint代码里面去画 要不然运行程序的时候 那个黄色的手柄也在(其实可以通过DesignModel这个属性来判断是否是在窗体设计器的模式下 如果是 就绘制一个黄色手柄) 在Designer里面也有一个Paint Designer里面的Paint只是在控件设计期间才会执行的 程序运行出来后那些代码就没有用了:

protected override void OnPaintAdornments(System.Windows.Forms.PaintEventArgs pe) {
    Graphics g = pe.Graphics;
    g.FillRectangle(Brushes.Yellow, /*rect*/);
    base.OnPaintAdornments(pe);
}
上面的这个是在Designer里面重写的 里面的代码只是在窗体设计期间有效的窗体程序运行出来后这些代码就没有用了所以程序一运行上面的手柄就不再了 所以可以理解为Designer这个类和前面的TypeConverter和Editor一样 只是给窗体设计期间用的 程序运行出来后这些代码就是没有用的

首先要画出那个矩形区域的位置 而他的位置则根据控件的属性来决定的 所以在Designer里面:

private Rectangle GetRect() {
    return new Rectangle(((CtrlTest)this.Control).LinePos - 2, 10, 5, 10);
}
这样就可以得到矩形区域的位置了然后进行绘制 而现在要做的就是怎么去拖动它 而接下来的代码就有点抽象了 需要override消息处理函数WndProc如果对Windows消息处理机制懂的话估计知道是什么 如果不知道的话在我的博客的C#Windows编程分组里面有一篇Windows消息处理机制的文章来着

如果现在鼠标直接去拖动黄色的手柄的话 拖动的是控件而不是线条的位置 所以在窗体设计期间 一些鼠标的操作需要自己来处理 全部的代码就是:

private const int WM_MOUSEMOVE = 0x200;
private const int WM_LBUTTONDOWN = 0x201;
private const int WM_LBUTTONUP = 0x202;

private bool m_bMouseDown;

private Rectangle GetRect() {
    return new Rectangle(((CtrlTest)this.Control).LinePos - 2, 10, 5, 10);
}

protected override void OnPaintAdornments(System.Windows.Forms.PaintEventArgs pe) {
    Graphics g = pe.Graphics;
    g.FillRectangle(Brushes.Yellow, this.GetRect());
    base.OnPaintAdornments(pe);
}

protected override void WndProc(ref System.Windows.Forms.Message m) {
    Point pt = Point.Empty;
    switch (m.Msg) {
        case WM_LBUTTONDOWN:    //鼠标左键点下
            pt = new Point(m.LParam.ToInt32());//lParam参数里面是坐标信息
            m_bMouseDown = this.GetRect().Contains(pt);
            break;
        case WM_MOUSEMOVE:      //鼠标移动
            pt = new Point(m.LParam.ToInt32());
            if (m_bMouseDown) {
                TypeDescriptor
                    .GetProperties(this.Control.GetType())["LinePos"]
                    .SetValue(this.Control, pt.X);
                this.Control.Invalidate();
                this.Control.Update();
                return;//自己处理了 就直接返回不用base了
            }
            break;
        case WM_LBUTTONUP:      //鼠标左键抬起
            m_bMouseDown = false;
            break;
    }
    base.WndProc(ref m);
}

上面代码的逻辑也不是很复杂 但是有个问题 鼠标指针会很闪烁 不知道为什么本来在[GDI+程序设计]上这个例子是处理了鼠标的指针样式的 比如鼠标移动到手柄上的时候换一个样式 但是我记得最开始看那本书的时候照着做了一下 发现鼠标指针太闪烁了顿时就不知道怎么回事了 所以 在这里我就没有处理鼠标指针样式了  结果发现还是很闪烁 就算自己处理了WM_SETCURSOR消息也木有效果 顿时感觉好悲剧、、由于平时也怎么用到Designer用到也是用它来画装饰 所以就没有深入研究一些问题、、

总之Designer还是挺有意思的 它可以控制你的控件在设计期间的很多行为 比如上面列举的 说白了这个类可以单独为你的控件做一个设计器 上面只是列举了其中的三个 除了OnPaintAdornments之外还有一个我用过的就是SelectionRules:

public override SelectionRules SelectionRules {
    get {
        return base.SelectionRules & SelectionRules.Moveable;
    }
}

上面的代码可以控制控件在设计器上的时候是否能够被用户用鼠标去拖动大小 而我上面的代码则表示该控件只能被移动 效果差不多就是这样

可以看到 那八个可以控制大小的顶点已经不在了 因为正如你看到的 我这个控件不需要用户去修改大小 大小是我自己动态计算出来的 我上面的代码中base.SelectionRules默认是全部都会有的我和Moveable做了一个与运算那么就之剩下一个Moveable其实也可以直接返回Moveable SelectionRules有很多枚举值

上面可以看到我选中的那个是表示有向下多动大小的那个顶点 反正自己试试就知道了

当然这样做只是让用户无法从设计器上拖动来设置大小而已 你通过Size之类的属性还是可以去设置的 要做彻底一点呢就是还需要重写一下控件的SetBoundsCore函数:

//在控件的代码里面 而不是Designer的代码里面
protected override void SetBoundsCore(
        int x, int y, int width, int height, BoundsSpecified specified
    ) {
    //set x y width height
    base.SetBoundsCore(x, y, sz.Width, sz.Height, specified);
}
当控件的大小发生变化的时候就会去调用这个函数 你想要设置成什么大小这里控制就是了 sz是我计算之后的大小 这样的话用户只能设置到x和y而width和height传进去也没有用

这一篇暂时到这里吧、、、这一系列的也就全部结束了、、之后还有文章的话的都是实际的控件开发的了、、


添加时间:2014-06-18 04:24:40 编辑时间:2016-11-09 22:28:54 阅读:1562 
C#自定义控件开发 C#控件开发
冰封一夏 - 2019-08-17 12:00:33
写的不错,学习了
  • 编写评论

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