Application.DoEvents()可以在c#中使用吗?
Application.DoEvents()
这个函数是一种让GUI跟上应用程序其余部分的方法吗,就像VB6的DoEvents一样?
DoEvents
关于Application.DoEvents方法,请查阅MSDN文档。
Application.DoEvents
可以,但这只是个黑客。
看到 DoEvents邪恶吗?< / >。
直接来自thedev引用的MSDN页面:
调用此方法将导致当前 线程被挂起 等待窗口消息被处理。 如果消息导致事件 触发的,则是你的其他领域 应用程序代码可以执行。这可以 使您的应用程序显示 意想不到的行为 很难调试。如果你表现出色 需要a的操作或计算 时间长了,往往更可取 请在new 线程。欲了解更多有关 异步编程,请参见 .异步编程概述
调用此方法将导致当前 线程被挂起 等待窗口消息被处理。 如果消息导致事件 触发的,则是你的其他领域 应用程序代码可以执行。这可以 使您的应用程序显示 意想不到的行为 很难调试。如果你表现出色 需要a的操作或计算 时间长了,往往更可取 请在new 线程。欲了解更多有关 异步编程,请参见
因此微软对它的使用提出了警告。
此外,我认为这是一个黑客,因为它的行为是不可预测的,容易产生副作用(这来自于尝试使用DoEvents而不是旋转一个新线程或使用后台工作)。
这里没有大男子主义——如果它是一个强有力的解决方案,我会全力支持它。然而,尝试在。net中使用DoEvents只会给我带来痛苦。
是的。
然而,如果你需要使用Application.DoEvents,这主要是一个糟糕的应用程序设计的迹象。也许你想在一个单独的线程中做一些工作?
在这里是一个如何使用DoEvents的例子;请注意,微软也提供了使用它的警告。
Hmya, DoEvents()的持久神秘感。有很多人反对它,但没有人真正解释为什么它是“坏的”。这和“不要改变结构体”是一样的道理。嗯,为什么运行时和语言支持突变一个结构,如果这是如此糟糕?同样的原因:如果你做得不对,你就搬起石头砸自己的脚。很容易。正确地做它需要知道完全它做什么,这在DoEvents()的情况下绝对不容易理解。
马上:几乎所有Windows窗体程序实际上都包含对DoEvents()的调用。它被巧妙地伪装了,但是使用了不同的名称:ShowDialog()。DoEvents()允许对话框是模态的,而不会冻结应用程序中的其他窗口。
大多数程序员在编写自己的模态循环时都希望使用DoEvents来防止用户界面冻结。它确实做到了;它分派Windows消息,并获得任何油漆请求交付。但问题是,这并不是有选择性的。它不仅发送油漆消息,还传递其他所有信息。
还有一组会引起麻烦的通知。它们来自显示器前大约3英尺的地方。例如,用户可以在调用DoEvents()的循环运行时关闭主窗口。这是可行的,用户界面消失了。但是你的代码并没有停止,它仍然在执行循环。这是不好的。非常非常糟糕。
还有更多:用户可以单击相同的菜单项或按钮,从而启动相同的循环。现在有两个嵌套循环执行DoEvents(),前一个循环暂停,新循环从头开始。这可能会起作用,但可能性很小。特别是当嵌套循环结束,挂起的循环继续时,试图完成已经完成的工作。如果没有异常爆炸,那么数据肯定被打乱了。
回到ShowDialog()。它执行DoEvents(),但请注意它还执行其他一些事情。它禁用应用程序中的所有Windows,除了对话框。现在3英尺的问题已经解决了,用户不能做任何扰乱逻辑的事情。解决了关闭窗口和重新启动作业两种失败模式。或者换句话说,用户没有办法让你的程序以不同的顺序运行代码。它将可预测地执行,就像测试代码时一样。这使得对话框非常烦人;谁不讨厌有一个活跃的对话框,不能从另一个窗口复制和粘贴一些东西?但这就是代价。
这就是在代码中安全地使用DoEvents所需要的。将所有表单的Enabled属性设置为false是避免问题的一种快速有效的方法。当然,没有程序员喜欢这样做。和不喜欢。这就是为什么你不应该使用DoEvents()。你应该使用线程。即使他们给你一个完整的武器库的方式射击你的脚在丰富多彩和不可思议的方式。不过好处是你只射中自己的脚;它(通常)不会让用户开枪。
c#和VB的下一个版本。NET将使用新的await和async关键字提供一个不同的枪。灵感在一小部分由DoEvents和线程引起的麻烦,但在很大程度上是由WinRT的API设计需要你保持你的UI更新,而异步操作正在发生。比如从文件中读取。
根据我的经验,我建议在。net中使用DoEvents时要非常谨慎。当在包含DataGridViews的TabControl中使用DoEvents时,我经历了一些非常奇怪的结果。另一方面,如果你所处理的只是一个带有进度条的小表单,那么它可能是OK的。
底线是:如果要使用DoEvents,那么在部署应用程序之前需要彻底测试它。
我见过许多使用“DoEvents-Hack”的商业应用程序。特别是当渲染开始发挥作用时,我经常看到这样的情况:
while(running) { Render(); Application.DoEvents(); }
他们都知道这种方法的害处。然而,他们使用黑客,因为他们不知道任何其他解决方案。下面是汤姆米勒从博客中获取的一些方法:
将表单设置为在wpaint中进行所有绘制,并在那里进行渲染。在OnPaint方法结束之前,确保执行this.Invalidate();这将导致OnPaint方法立即再次被触发。 P/Invoke进入Win32 API,调用PeekMessage/TranslateMessage/DispatchMessage。(Doevents实际上做了类似的事情,但您可以在没有额外分配的情况下完成此操作)。 编写自己的表单类,它是CreateWindowEx的一个小包装器,并让自己完全控制消息循环。 -确定DoEvents方法适合你,并坚持使用它
我看到了上面jheriko的评论,最初我同意,如果你最终旋转你的主UI线程,等待另一个线程上长时间运行的异步代码来完成,我无法找到一种避免使用DoEvents的方法。但是根据Matthias的回答,我的UI上一个小面板的简单刷新可以取代DoEvents(并避免一个讨厌的副作用)。
更多关于我案子的细节…
我正在做以下(建议在这里),以确保在长时间运行的SQL命令期间更新进度条类型的启动画面(如何显示一个“加载”覆盖…):
IAsyncResult asyncResult = sqlCmd.BeginExecuteNonQuery(); while (!asyncResult.IsCompleted) //UI thread needs to Wait for Async SQL command to return { System.Threading.Thread.Sleep(10); Application.DoEvents(); //to make the UI responsive }
对我来说,调用DoEvents意味着鼠标点击有时会在我的启动画面后面的窗体上发射,即使我把它做成了TopMost。
用一个简单的刷新调用替换DoEvents行到我的启动画面中心的一个小面板,FormSplash.Panel1.Refresh()。UI更新得很好,其他人警告过的DoEvents怪异现象也消失了。
FormSplash.Panel1.Refresh()
应用程序。如果在消息队列中放入图形处理以外的内容,DoEvents可能会产生问题。
它可以用于更新进度条,并在MainForm构造和加载等过程中通知用户进度(如果这需要一段时间)。
在我最近制作的一个应用程序中,每次在MainForm的构造函数中执行代码块时,我都使用DoEvents更新加载屏幕上的一些标签。在本例中,UI线程用于在不支持SendAsync()调用的SMTP服务器上发送电子邮件。我可能已经用Begin()和End()方法创建了一个不同的线程,并从他们的方法中调用Send(),但该方法容易出错,我更喜欢我的应用程序的Main Form在构造过程中不抛出异常。
DoEvents确实允许用户单击或键入并触发其他事件,后台线程是一种更好的方法。
但是,在某些情况下,您可能会遇到需要刷新事件消息的问题。我遇到了一个问题,在RichTextBox控件忽略ScrollToCaret()方法时,该控件在队列中有消息要处理。
下面的代码在执行DoEvents时阻塞所有用户输入:
using System; using System.Runtime.InteropServices; using System.Windows.Forms; namespace Integrative.Desktop.Common { static class NativeMethods { #region Block input [DllImport("user32.dll", EntryPoint = "BlockInput")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool BlockInput([MarshalAs(UnmanagedType.Bool)] bool fBlockIt); public static void HoldUser() { BlockInput(true); } public static void ReleaseUser() { BlockInput(false); } public static void DoEventsBlockingInput() { HoldUser(); Application.DoEvents(); ReleaseUser(); } #endregion } }