【WinForm】自定义控件(进度控制条)

上篇说了如何创建自定义控件,接下来说说如何自定义属性,如何绘制控件,以进度控制条为例,先上效果图

这里只实现了简单的进度控制功能,该控件由三部分组成,总长度(底部白色矩形),已加载长度(灰色矩形),控制块(黑色矩形),百分比

1、首先创建一个类库,命名为MySlider, 继承自 Control 类

    public class MySlider : Control
    {
        public MySlider()
        {
            this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer | ControlStyles.ResizeRedraw | ControlStyles.UserPaint, true);
        }
    }

  在构造函数中设置控件Style,ControlStyles枚举可以参考

http://technet.microsoft.com/zh-cn/subscriptions/system.windows.forms.controlstyles.aspx

2、接下来,我们需要定义以下变量,并对一些变量进行一些默认设置

        Rectangle foreRect;
        Rectangle backRect;
        Rectangle setRect;

        Color backgroundColor = Color.White;
        Color foregroundColor = Color.Gray;
        Color setRectColor = Color.Black;
        Color fontColor = Color.Black;

        int maximum = 100;
        int minimum = 0;
        double myValue = 0;

        bool showPercent;
        float fontSize = 9;
        FontFamily myFontFamily = new FontFamily("宋体");

3、再来,设置属性值

View Code
        [Category("General"), Description("Show Percent Tag"), Browsable(true)]
        public bool ShowPercentTag
        {
            get { return showPercent; }
            set
            {
                showPercent = value;
                Invalidate();
            }
        }
        [Category("General"), Description("Control's Maximum"), Browsable(true)]
        public int Maximum
        {
            get { return maximum; }
            set
            {
                maximum = value;
                Invalidate();
            }
        }
        [Category("General"), Description("Control's Minimum"), Browsable(true)]
        public int Minimum
        {
            get { return minimum; }
            set
            {
                minimum = value;
                Invalidate();
            }
        }
                
        [Category("General"), Description("Control's FontSize"), Browsable(true)]
        public float FontSize
        {
            get { return fontSize; }
            set
            {
                this.fontSize = value;
                Invalidate();
            }
        }
        [Category("General"), Description("Control's FontFamily"), Browsable(true)]
        public FontFamily MyFontFamily
        {
            get { return myFontFamily; }
            set
            {
                this.myFontFamily = value;
                Invalidate();
            }
        }
        
        [Category("Color"), Browsable(true)]
        public Color BackgroundColor
        {
            get { return backgroundColor; }
            set
            {
                this.backgroundColor = value;
                Invalidate();
            }
        }
        [Category("Color"), Browsable(true)]
        public Color ForegroundColor
        {
            get { return foregroundColor; }
            set
            {
                this.foregroundColor = value;
                Invalidate();
            }
        }
        [Category("Color"), Browsable(true)]
        public Color SetRectColor
        {
            get { return setRectColor; }
            set
            {
                this.setRectColor = value;
                Invalidate();
            }
        }
        [Category("Color"), Browsable(true)]
        public Color FontColor
        {
            get { return fontColor; }
            set
            {
                this.fontColor = value;
                Invalidate();
            }
        }

还有一个myValue没有设置,后面会讲到

4、接下来要确定控件的位置,我们根据 Width 和 Height 属性来确定矩形的位置,由于Control 类也有这两个属性,我们在前面加上new覆盖掉原有的属性

        [Category("General"), Description("Control's Width"), Browsable(true)]
        public new int Width
        {
            get { return base.Width; }
            set
            {
                base.Width = value;
                foreRect.X = backRect.X = base.Width / 20;
                backRect.Width = base.Width * 9 / 10;
                foreRect.Width = (int)(myValue / maximum * backRect.Width);
                setRect.X = (int)(myValue / maximum * (backRect.Width - backRect.Height) + foreRect.X);

                Invalidate();
            }
        }
        [Category("General"), Description("Control's Height"), Browsable(true)]
        public new int Height
        {
            get { return base.Height; }
            set
            {
                base.Height = value;
                foreRect.Height = backRect.Height = setRect.Height = setRect.Width = base.Height / 3;
                foreRect.Y = backRect.Y = setRect.Y = base.Height / 3;
                Invalidate();
            }
        }

通过Width 和 Height 属性,计算出三个矩形的位置和大小,接下来是Value属性

5、在进度控制条中,这个Value属性比较重要,我们经常需要相应这个Value值变化的事件,例如,播放音乐的时候,用户拖动进度条改变Value时,需要使音乐播放到相应的位置

  所以在这里我们先定义一个事件,当外部为该事件添加了响应函数时,事件就会生效,否则为OnValueChanged的值为null

        protected EventHandler OnValueChanged;
        public event EventHandler ValueChanged
        {
            add
            {
                if (OnValueChanged != null)
                {
                    foreach (Delegate d in OnValueChanged.GetInvocationList())
                    {
                        if (object.ReferenceEquals(d, value)) { return; }
                    }
                }
                OnValueChanged = (EventHandler)Delegate.Combine(OnValueChanged, value);
            }
            remove
            {
                OnValueChanged = (EventHandler)Delegate.Remove(OnValueChanged, value);
            }
        }

6、接下来定义Value属性

  当Value值改变的时候,重新设置矩形的进度,控制块的位置,并且重绘控件

        [Category("General"), Description("Control's Value"), Browsable(true)]
        public double Value
        {
            get { return myValue; }
            set
            {
                if (myValue < Minimum)
                    throw new ArgumentException("小于最小值");
                if (myValue > Maximum)
                    throw new ArgumentException("超过最大值");
                
                myValue = value;
                foreRect.Width = (int)(myValue / maximum * backRect.Width);
                setRect.X = (int)(myValue / maximum * (backRect.Width - backRect.Height) + backRect.X);

                if ((myValue - maximum) > 0)
                {
                    foreRect.Width = backRect.Width;
                    setRect.X = backRect.Width - backRect.Height + backRect.X;
                }

                //如果添加了响应函数,则执行该函数
                if (OnValueChanged != null)
                {
                    OnValueChanged(this, EventArgs.Empty);
                }
                
                Invalidate();
            }
        }

  这样,属性就算添加完了,有一个地方需要注意的,Value属性内如果对进度条的值进行修改,使用myValue变量,而在其他地方,则用Value属性

7、接下来是绘制控件,从过重载OnPaint方法对控件进行绘制

        protected override void OnPaint(PaintEventArgs e)
        {
            base.OnPaint(e);
            DrawRect(e.Graphics);
            DrawText(e.Graphics);
        }        
        private void DrawRect(Graphics e)
        {
            Pen pen = new Pen(this.foregroundColor);

            e.FillRectangle(new SolidBrush(this.backgroundColor), backRect);
            e.DrawRectangle(new Pen(Color.Black), backRect);

            e.FillRectangle(new SolidBrush(this.foregroundColor), foreRect);
            e.DrawRectangle(new Pen(Color.Black), foreRect);

            e.FillRectangle(new SolidBrush(this.setRectColor), setRect);
            e.DrawRectangle(new Pen(Color.Black), setRect);
        }
        private void DrawText(Graphics e)
        {
            Point point = new Point();
            point.X = this.backRect.X + this.backRect.Width * 3 / 7;
            point.Y = this.backRect.Y + this.backRect.Height / 3;

            SolidBrush brush = new SolidBrush(fontColor);
            Font font = new Font(myFontFamily, this.fontSize);
            string percent = ((int)this.myValue).ToString() + "%";

            StringFormat format = new StringFormat();
            format.Alignment = StringAlignment.Center;
            format.LineAlignment = StringAlignment.Center;

            e.DrawString(percent, font, brush, backRect, format);
        }

使用Graphics进行绘制,这里有个要点,通过设置StringFormat可以让文字居中显示

 

8、最后还有一个方法OnResize,在设计时,修改控件的大小会调用这个方法,比如:拖动边缘的箭头改变控件的大小时,控件也要做相应的改变时,就可以重载该方法,如果没有重载,就只有在修改完成后才更新控件,不懂的可以自己试一下 : )

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            this.Width = Width;
            this.Height = Height;
            Invalidate();
        }

  好了,控件算是绘制完成了

 

对了,还有,控件绘制完了,却不能操作,还要对控件进行操作

9、这里通过三个鼠标事件函数,让鼠标可以拖动控制条

  在构造函数中添加下面语句

        this.MouseDown += MySlider_MouseDown;
        this.MouseMove += MySlider_MouseMove;
        this.MouseUp += MySlider_MouseUp;

  添加三个辅助变量,添加响应函数

        Point originPoint;
        Point originsetRectPoint;
        bool setRectDown = false;
        
        void MySlider_MouseUp(object sender, MouseEventArgs e)
        {
            setRectDown = false;
        }
        void MySlider_MouseMove(object sender, MouseEventArgs e)
        {
            if (setRectDown)
            {
                int dd = e.Location.X - originPoint.X;

                double percent = (double)(originsetRectPoint.X + dd - this.backRect.X) / (this.backRect.Width - this.backRect.Height);
                if (percent < 0)
                {
                    this.Value = minimum;
                    this.foreRect.Width = 0;
                    this.setRect.X = backRect.X;
                }
                else if (percent > 1)
                {
                    this.Value = maximum;
                    this.foreRect.Width = this.backRect.Width;
                    this.setRect.X = backRect.X + backRect.Width - backRect.Height;
                }
                else
                {
                    this.Value = percent * maximum;
                    this.foreRect.Width = (int)(percent * this.backRect.Width);
                    this.setRect.X = originsetRectPoint.X + dd;
                }
                Invalidate();
            }
        }
        void MySlider_MouseDown(object sender, MouseEventArgs e)
        {
            if (setRect.Contains(e.Location))
            {
                this.originPoint = e.Location;
                originsetRectPoint = this.setRect.Location;
                this.setRectDown = true;
            }
        }

  到这里,一个自定义的进度控制条算是完成了,功能比较简单,大家可以自己完善

posted @ 2012-12-11 18:08  bomo  阅读(10905)  评论(1编辑  收藏  举报