WinForms中的自己控件具有重新绘制优化

时间:2013-01-08 21:05:07

标签: c# winforms multithreading

已删除帖子并拥有代码:

namespace StackOverflowQuestion
{
  class Program
  {
        static void Main (string[] args)
        {
              var cw = new ConsoleWriter ();

              for (int i = 0; i <= 5000000; i++)
              {
                    cw.Write (i);
              }

              Console.ReadKey ();
        }
  }

  class ConsoleWriter
  {
        private Stopwatch sw = new Stopwatch ();

        public ConsoleWriter ()
        {
              sw.Start ();
        }

        public void Write (int pNumber)
        {
              if (sw.ElapsedMilliseconds >= 50)
              {
                    Console.WriteLine (pNumber);
                    sw.Restart ();
              }
        }
  }
}

输出: 305940个
651171个
1002965个
1358665个
1715740个
2069602个
2419054个
2772833个
3127880个
3485054个
3844335个
4204016个
4557912个
4913494

所以一切正常。在此示例中ConsoleWriter在控制台上显示编号,但它可以显示在控制界面中。就像你看到的那样,即使我调用5000000次Write方法,它也只会在最短50 ms后更新UI。太棒了,但是在很多情况下请注意不会显示最后一个值5000000。怎么解决?我应该使用一个类(线程),它将每50毫秒调用一次事件,它会检查要写入的值是否已更改?

2 个答案:

答案 0 :(得分:4)

您可以使用计时器

Timer _timer;

public void StartTimer()
{
    _timer = new Timer();
    _timer.Interval = 100; // 100 ms = 0.1 s
    _timer.Tick += new EventHandler(timer_Tick);
    _timer.Start();
}

void timer_Tick(object sender, EventArgs e)
{
    myControl.Number = i;
}

在控件中应该有类似

的东西
private int _number;    
public int Number
{
    get { return _number; }
    set
    {
        if (value != _number) {
            _number = value;
            Invalidate();
        }
    }
}

Invalidate()的来电将触发Paint事件。您的绘画逻辑应该在OnPaint方法中:

protected override void OnPaint(PaintEventArgs e)
{
    ... paint here
}

但是因为for循环本身会冻结应用程序。您可以使用第二个计时器,以比显示计数器更快的间隔更新计数器。在UI-tread上运行的每个代码(如果您愿意,主线程)将冻结UI直到它终止。在单独的线程中在后台执行繁重工作的简单方法是使用BackgroundWorker。后台工作程序自动在UI线程和工作线程之间切换,并允许您向UI线程报告progess。

您也可以手动启动线程以更新计数器。如果这是唯一更改数字的线程,则不需要同步机制。但是永远不要从UI线程以外的其他线程访问UI(表单或控件)。

这是一个完整的非阻塞解决方案,使用另一个线程进行计数

Timer _timer;
int _counter;
System.Threading.Thread _worker;

public frmTimerCounter()
{
    InitializeComponent();
    _worker = new System.Threading.Thread(() =>
    {
        while (_counter < 10000000) {
            _counter++;
            System.Threading.Thread.Sleep(20);
        }
    });
    _worker.Start();
    StartTimer();
}

public void StartTimer()
{
    _timer = new System.Windows.Forms.Timer();
    _timer.Interval = 100; // 100 ms = 0.1 s
    _timer.Tick += new EventHandler(timer_Tick);
    _timer.Start();
}

void timer_Tick(object sender, EventArgs e)
{
    // I used a Label for the test. Replace it by your control.
    label1.Text = _counter.ToString();
}

答案 1 :(得分:2)

您没有发布任何代码,但我可以猜测它的样子。您在属性设置器中执行了 way 过多的工作。这个for()循环永远不会超过一毫秒,太短暂,不会发现GUI冻结。

您可以按照标准方式控件重绘自己来实现此目的。这是懒惰。你可以通过调用Invalidate()方法得到它。像这样:

class MyControl : Control {
    private int number;
    public int Number {
        get { return this.number; }
        set {
            if (value != this.number) this.Invalidate();
            this.number = value;
        }
    }
    protected override void OnPaint(PaintEventArgs e) {
        // TODO: paint number
        //...
        base.OnPaint(e);
    }
}

你现在也会发现其他的东西,没有必要再使用for()循环了。从来没有一个人,人类不可能看到现代处理器可以增加数字的令人难以置信的速度。因此,您现在将其替换为:

myControl.Number = 50000;

如果你真的想让人眼看到数字增加,那么你将不得不做得慢得多。每50毫秒不超过一次,大约是变化变成模糊的点。这需要一个Timer。