为什么Refresh()不像DoEvents()那样做
本文关键字:不像 Refresh 为什么 DoEvents | 更新日期: 2023-09-27 18:19:31
我正在努力理解Windows Forms re:UI编程中一个长期存在的概念;以下代码来自Chris Sells的《Windows窗体编程》一书(2006年第2版):
void ShowProgress(string pi, int totalDigits, int digitsSoFar) {
// Display progress in UI
this.resultsTextBox.Text = pi;
this.calcToolStripProgressBar.Maximum = totalDigits;
this.calcToolStripProgressBar.Value = digitsSoFar;
if( digitsSoFar == totalDigits ) {
// Reset UI
this.calcToolStripStatusLabel.Text = "Ready";
this.calcToolStripProgressBar.Visible = false;
}
// Force UI update to reflect calculation progress
this.Refresh();
}
此方法是小样本应用程序的一部分,该应用程序具有另一个计算Pi的长期运行方法。每次计算一组数字时,都会调用ShowProgress()来更新UI。正如书中所解释的,这段代码是"错误的"做事方式,当应用程序最小化,然后再次进入前台时,会导致UI冻结,导致系统要求应用程序重新绘制自己。
我不明白的是:既然this.Refresh()被反复调用,为什么它不处理任何等待关注的系统重新绘制事件
还有一个后续问题:当我在这个.Refresh()之后立即添加Application.DoEvents()时,冻结问题就消失了这不必求助于Invoke/BeginInvoke等。有什么意见吗?
基本上,这是因为Windows处理消息的方式——它在内部消息循环中以同步的方式进行处理。
关键是有一条消息触发了您的代码。例如单击按钮。您的应用程序正在处理消息。从该处理程序中,您可以强制刷新,将另一个WM_PAINT放入消息队列中。当处理程序完成时,消息循环肯定会接收并调度它,从而重新绘制控件。但是您的代码还没有完成,事实上它循环调用ShowProgress
,导致WM_PAINT永远排队。
另一方面,DoEvents()会引发消息循环的一个独立实例。它是从代码中的触发的,这意味着调用堆栈看起来像这样:
外部消息循环->您的代码->内部消息循环。
内部消息循环处理所有挂起的消息,包括WM_PAINT(因此重新绘制控件),但这是危险的,因为它将调度所有其他挂起的信息,包括按钮单击、菜单单击或右上角X关闭应用程序的事件。不幸的是,没有简单的方法使循环仅处理WM_PAINT,这意味着调用DoEvents()会使您的应用程序暴露在微妙的潜在问题中,这些问题涉及触发DoEvents的代码执行过程中的意外用户活动。