当前位置: 爱符号 - 所有分类 - 资源共享 - 文档 - C# - C# 用GDI绘制进度条以及多线程演示
C# 用GDI绘制进度条以及多线程演示 C# YongGDIHuiZhiJinDuTiaoYiJiDuoXianChengYanShi
符号空间 发表于:2013-07-09 22:33:35 阅读(7054)
关键词:C# C# GDI GDI 进度条 进度条 多线程 多线程 Sleep Sleep
摘要:群里有人说用GDI画进度条,遇到了很多问题。 1.很卡,我就纳闷了,GDI怎么会卡呢?有多大的逻辑会让GDI半天反应不过来? 2.用了Sleep,我想原因就是它了,他可能不了解多线程

也许玩了几下GDI的你,是乎心痒痒的想绘制点别的什么东西?

比如说:进度条、按钮?


群里有人说用GDI画进度条,遇到了很多问题。

1.很卡,我就纳闷了,GDI怎么会卡呢?有多大的逻辑会让GDI半天反应不过来?

2.用了Sleep,我想原因就是它了,他可能不了解多线程


现在给大家科普一下:

1.多线程,你可以这样理解,以前你要一件一件的事挨个做,现在有人帮你,你们同时进行,虽然各自有快慢,有时差,但时总体来说效率提高了,但不是线程多就好,因为跟人一样,多了就乱套了,不好管理,资源也有限的。

2.主线程,只要是应用程序,它都有一个主线程,就像是一个家里面,总有一个当家的,先这样去理解,就是它是主要的。

3.子线程,子线程不是凭空而来,它是由主线程某些逻辑代码创建出来的,就跟家中小孩是父母生的,不是突然出来的

4.同步,这个其实一般情况下所有的事情都是同步的,比如说打饭的时候,前面的人打了,你才可以过去,因为师傅只有一双手,忙不过来,所以用一个窗口来把你们锁着,让你们排除,所有用这个窗口服务的人,都要排队,需求量越大,就越慢。

5.异步,跟同步恰好相反,就如同上面的打饭一样,每种菜都有不同的窗口在服务,大家都从食堂进来后,就各自走向自己需要的窗口,然后花的时间不一样,最后出门的顺序也不一样。


所以说,你要是在主线程里面休眠,就是用Sleep的话,就会让家中的主人(比如老爸)睡着了,这个时候天下大乱了吧,你点界面没有任何反应的。

另外就是,子线程不要什么事情都去找主线程,只有特别需要的时候才去调用,否则就像家里有一个小屁孩一样,什么事都要来烦家长。


先不去管这些了,那么贴一部分核心代码吧:


#region OnPaint
        protected override void OnPaint(PaintEventArgs e) {
            base.OnPaint(e);
            DrawBackground(e);//画背景
            DrawFront(e);//画前景
        }
        void DrawBackground(PaintEventArgs e) {
            //采用背景颜色,填充就行了
            Pen background = new Pen(BackColor, 1F);
            e.Graphics.DrawRectangle(background, e.ClipRectangle.X, e.ClipRectangle.Y, e.ClipRectangle.Width - 1, e.ClipRectangle.Height - 1);
        }
        void DrawFront(PaintEventArgs e) {
            Brush front = new SolidBrush(ForeColor);
            float scale = (e.ClipRectangle.Width - 2) / 100F;//计算进度与宽度的比例
            scale *= _value;//得到实际宽度

            //填充进度块
            e.Graphics.FillRectangle(front, e.ClipRectangle.X + 1, e.ClipRectangle.Y + 1, scale, e.ClipRectangle.Height - 2);
            DrawText(e, scale, front);//绘制文本
            //System.Drawing.Drawing2D.LinearGradientBrush  这个可以渐变的
        }
        void DrawText(PaintEventArgs e,float valueWidth,Brush front) {
            //无文本
            if (_textMode == GDIProgressBarTextModes.None)
                return;

            string text = string.Empty;
            if (_textMode == GDIProgressBarTextModes.PercentageIntegter) {
                text = _value.ToString("0'%'");//整数
                if (text.Length < 4) {//补位,以免文字晃动
                    text = string.Empty.PadRight(4 - text.Length, ' ') + text;
                }
            } else if (_textMode == GDIProgressBarTextModes.PercentageDecimal) {
                text = _value.ToString("0.00'%'");//带两位小数
                if (text.Length < 7) {//补位,以免文字晃动
                    text = string.Empty.PadRight(7 - text.Length, ' ') + text;
                }
            } else {//自定义
                text = _text;
            }
            SizeF textSize = e.Graphics.MeasureString(text, Font);//计算文本的大小
            float w = e.ClipRectangle.Width - 2;
            float x = (w - textSize.Width) / 2;//算出文本的水平起点,水平居中
            //float x2 = x + textSize.Width;
            float y = (e.ClipRectangle.Height - 2 - textSize.Height) / 2;//文本不一定有进度条那么大,所以是垂直居中的,计算它的垂直起点

            //if (valueWidth > x) {//进度块已经超过文本的X边界
            //    if (valueWidth > x2) {//进度快已经完全覆盖了
            //        //那就直接画背景色
            //        e.Graphics.DrawString(text, Font, new SolidBrush(BackColor), new PointF(x, y));
            //    } else {//只有一部分怎么办?其实我想画面一半是空心的,一半是有色的,但是不好画
            //        e.Graphics.DrawString(text, Font, new SolidBrush(Color.White), new PointF(x, y));
            //    }
            //} else {//进度块还早着呢
            //    //画前景色
            //    e.Graphics.DrawString(text, Font, front, new PointF(x, y));
            //}

            //发现一个更好的办法,直接用第三种颜色,这样永远都是在突出的。
            e.Graphics.DrawString(text, Font, new SolidBrush(_textColor), new PointF(x, y));
        }
        #endregion




下面是运行效果图(有点多,嘿嘿):


最后两张效果的说明:

第一个按钮是让timer运行,让进度条不断的跑满再从头来

代码:

        private void timer1_Tick(object sender, EventArgs e) {
            if (gdiProgressBar1.Value + 2F <= 100F) {//其实也不怕你超出100F,它自己内部处理了的
                gdiProgressBar1.Value += 2F;
            } else {
                gdiProgressBar1.Value = 0F;//还原进度,这样可以不停的跑下去。
            }
        }

        private void button1_Click(object sender, EventArgs e) {
            timer1.Enabled = !timer1.Enabled;//timer的开始与停止就是这么简单,
            button1.Text = timer1.Enabled ? "stop timer" : "start timer";
        }


第二个按钮是创建一个线程,在线程内部Sleep,然后适当的去调用主线程的东西,这样避免卡住的问题

代码:

        private void button2_Click(object sender, EventArgs e) {
            gdiProgressBar2.Value = 0F;
            button2.Enabled = false;
            new Thread(() => {
                float f = 0F;
                while (f < 100F) {
                    f += 1.35F;//故意造成小数

                    //线程调用,必须这样,如果不这样,会更麻烦的
                    //记住只有需要调用界面的时候才这样调用一下,这样做到需要就用,不需要就不要占着不拉屎
                    ThreadInvoke(this, () => gdiProgressBar2.Value = f);
                    Thread.Sleep(100);//通过线程内部休眠,不会影响主线程
                }
                //还原按钮状态
                ThreadInvoke(this, () => button2.Enabled=true);
            }).Start();//Start里面可以传参数的,这里不需要了,注意不要忘记调用Start,否则不会执行的。
        }


        public static void ThreadInvoke(Control control, Action action) {
            if (control.InvokeRequired) {
                control.Invoke(action);
            } else {
                action();
            }
        }


下载源代码:GDIProgressBarDemo.zip





声明:以上内容仅代表作者观点,不代表爱符号赞成此内容或立场