我试图理解Windows Forms中的某个长期概念:UI编程;以下代码来自Chris Sells的Windows窗体编程书(第2版,2006年):
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(),为什么不处理等待注意的任何系统重绘事件?
还有一个后续问题:当我在this.Refresh()之后立即添加Application.DoEvents()时,冻结问题就会消失。这不需要求助于Invoke / BeginInvoke等等任何评论?
答案 0 :(得分:5)
基本上,原因是Windows处理消息的方式 - 它在内部消息循环中以同步方式执行此操作。
重点是有一条消息触发了您的代码。例如按一下按钮。您的应用程序正在处理消息。在此处理程序中,您强制刷新将另一个WM_PAINT 放入消息队列中。当你的处理程序完成时,消息循环肯定会拾取并调度,从而重新绘制控件。但是你的代码还没有完成,实际上它循环调用你的ShowProgress
,导致WM_PAINT永远排队。
另一方面,DoEvents()会触发消息循环的独立实例。它是从你的代码 中解雇的,这意味着调用堆栈如下所示:
外部消息循环 - >你的代码 - >内部消息循环。
内部消息循环处理所有待处理的消息,包括WM_PAINT(因此重绘了控件),但它很危险 - 因为它会发送所有其他待处理消息,包括按钮点击,菜单点击或使用右上角的X关闭应用程序的事件。遗憾的是,没有简单的方法可以使循环仅处理WM_PAINT,这意味着调用DoEvents()会使您的应用程序暴露于涉及意外用户活动的微妙潜在问题,这些问题会在您的代码执行期间触发DoEvents。