在WinForms中使用重新绘制优化的控件

本文关键字:绘制 优化 控件 新绘制 WinForms | 更新日期: 2023-09-27 17:51:11

已删除帖子,并有代码:

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毫秒后更新UI。很好,但请注意,在许多情况下,最后一个值5000000将不会显示。如何解决这个问题?我应该使用一个类(线程),它将调用事件每50毫秒,它将检查值写的变化?

在WinForms中使用重新绘制优化的控件

可以使用计时器

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- thread(如果你喜欢主线程的话)上运行的每个代码都将冻结UI,直到它终止。在单独的线程中在后台做繁重工作的一个简单方法是使用BackgroundWorker。后台worker会自动在ui线程和worker线程之间切换,并允许你向ui线程报告进程。

你也可以手动启动一个线程来更新计数器。如果这是唯一一个改变这个数字的线程,则不需要同步机制。但是永远不要从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();
}

你没有张贴任何代码,但我可以猜到它看起来像什么。你在属性设置器中做了太多的工作。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