[英]Use of Application.DoEvents()
可以在C#中使用Application.DoEvents()
嗎?
此功能是否可以讓GUI跟VB6的DoEvents
幾乎一樣地趕上應用程序的其余部分?
Hmya,DoEvents()持久的奧秘。 人們對此有強烈的反對意見,但是沒有人真正解釋它為什么是“不好的”。 與“不要變異結構”一樣的智慧。 嗯,為什么運行時和語言如此糟糕,為什么支持對結構進行突變? 同樣的原因:如果做得不好,就會用腳開槍射擊自己。 容易。 而正確地做到這一點需要確切地知道它的作用,在DoEvents()的情況下,這絕對不容易理解。
馬上:幾乎所有Windows Forms程序實際上都包含對DoEvents()的調用。 巧妙地偽裝了它,但是使用了另一個名稱:ShowDialog()。 正是DoEvents()允許對話框成為模態對話框,而不會凍結應用程序中的其余窗口。
大多數程序員都希望使用DoEvents在編寫自己的模態循環時停止其用戶界面凍結。 它確實做到了; 它分派Windows消息並獲取所有繪畫請求。 但是,問題在於它不是選擇性的。 它不僅可以發送繪畫消息,還可以傳遞其他所有消息。
並且有一組導致麻煩的通知。 它們來自顯示器前方約3英尺。 例如,用戶可以在調用DoEvents()的循環運行時關閉主窗口。 那行得通,用戶界面不見了。 但是您的代碼沒有停止,它仍在執行循環。 那很糟。 非常非常糟糕。
還有更多:用戶可以單擊相同的菜單項或按鈕,以使相同的循環開始。 現在,您有兩個執行DoEvents()的嵌套循環,上一個循環已暫停,新循環從頭開始。 那可能行得通,但是男孩的可能性很小。 尤其是當嵌套循環結束且掛起的循環恢復時,嘗試完成已經完成的作業。 如果那沒有例外,那么可以肯定的是數據全部被打亂了。
回到ShowDialog()。 它執行DoEvents(),但請注意它還會執行其他操作。 除了對話框之外,它禁用了應用程序中的所有窗口 。 現在解決了三英尺的問題,用戶無法做任何事情來弄亂邏輯。 解決了關閉窗口和重新開始工作兩種故障模式。 換句話說,用戶沒有辦法使程序以不同的順序運行代碼。 就像您測試代碼時一樣,它將可預測地執行。 它使對話非常煩人。 誰不討厭激活對話框並且不能夠復制和粘貼其他窗口中的內容? 但這就是價格。
這是在代碼中安全使用DoEvents所需要的。 將所有表單的Enabled屬性設置為false是避免問題的一種快速有效的方法。 當然,沒有程序員真正喜歡這樣做。 而且沒有。 這就是為什么您不應該使用DoEvents()的原因。 您應該使用線程。 即使它們為您提供了豐富的方法,也可以用豐富多彩且難以理解的方式射腳。 但是,這樣做的好處是您只用自己的腳射擊; 它不會(通常)讓用戶拍攝她的照片。
C#和VB.NET的下一版本將使用新的await和async關鍵字提供另一種方式。 一部分是由DoEvents和線程引起的麻煩啟發的,而另一部分則是由WinRT的API設計啟發的,該API設計要求您在進行異步操作時保持UI更新。 就像從文件中讀取一樣。
可以,但是這是hack。
請參閱DoEvents是否有害? 。
調用此方法將導致在處理所有等待窗口消息時掛起當前線程。 如果消息導致事件被觸發,則您的應用程序代碼的其他區域可能會執行。 這可能會導致您的應用程序表現出難以調試的意外行為。 如果執行耗時較長的操作或計算,通常最好在新線程上執行這些操作。 有關異步編程的更多信息,請參見異步編程概述。
因此,Microsoft告誡不要使用它。
另外,我認為它是一種hack,因為它的行為是不可預測的,並且容易產生副作用(這是由於嘗試使用DoEvents而不是旋轉新線程或使用后台工作程序而產生的)。
這里沒有大男子主義-如果它作為一個強大的解決方案,我將無所不在。 但是,嘗試在.NET中使用DoEvents只是讓我感到痛苦。
是的,System.Windows.Forms命名空間的Application類中有一個靜態DoEvents方法。 在UI線程中執行長時間運行的任務時,可以使用System.Windows.Forms.Application.DoEvents()處理UI線程在隊列中等待的消息。 這樣做的好處是,在執行長任務時,UI看起來更具響應性,並且不會“鎖定”。 但是,這幾乎總是不是做事的最佳方法。 據微軟稱,DoEvents“……導致當前線程在處理所有等待的窗口消息時被掛起。” 如果觸發了事件,則可能會出現難以跟蹤的意外和間歇性錯誤。 如果您有一項繁重的任務,最好在單獨的線程中執行。 在單獨的線程中運行長任務,可以對其進行處理,而不會干擾UI繼續平穩運行。 在這里查看更多詳細信息。
這是一個如何使用DoEvents的示例; 請注意,Microsoft還警告您不要使用它。
根據我的經驗,我建議在.NET中使用DoEvents時要格外謹慎。 在包含DataGridViews的TabControl中使用DoEvents時,我遇到了一些非常奇怪的結果。 另一方面,如果您要處理的只是帶有進度條的小表格,那么可能就可以了。
最重要的是:如果要使用DoEvents,則需要在部署應用程序之前對其進行徹底的測試。
是。
但是,如果您需要使用Application.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的情況下,也會在初始屏幕后面的窗體上觸發鼠標單擊。
好的/答案:用對Respesh的簡單刷新調用替換DoEvents行,該調用位於我的初始屏幕中央FormSplash.Panel1.Refresh()
。 UI很好地更新了,其他人已經警告過的DoEvents怪異已經消失了。
我已經看到許多使用“ DoEvents-Hack”的商業應用程序。 尤其是在渲染開始發揮作用時,我經常看到以下內容:
while(running)
{
Render();
Application.DoEvents();
}
他們都知道這種方法的弊端。 但是,他們使用了hack,因為他們不知道任何其他解決方案。 以下是Tom Miller在博客文章中采取的一些方法:
- 設置您的窗體以使所有繪圖都在WmPaint中進行,然后在此處進行渲染。 在OnPaint方法結束之前,請確保執行this.Invalidate();。 這將導致立即再次觸發OnPaint方法。
- P /調用Win32 API,然后調用PeekMessage / TranslateMessage / DispatchMessage。 (Doevents實際上執行類似的操作,但是您可以在沒有額外分配的情況下執行此操作)。
- 編寫自己的窗體類,該窗體類是CreateWindowEx的小包裝,並讓您完全控制消息循環。 -確定DoEvents方法適合您,並堅持使用。
請查閱MSDN文檔中的Application.DoEvents
方法。
如果在消息隊列中放置圖形處理以外的其他內容,Application.DoEvents可能會產生問題。
如果需要一些時間,它對於更新進度條並通知用戶MainForm構造和加載之類的進度很有用。
在我最近制作的一個應用程序中,每次在MainForm的構造函數中執行代碼塊時,我都使用DoEvents更新加載屏幕上的一些標簽。 在這種情況下,UI線程忙於在不支持SendAsync()調用的SMTP服務器上發送電子郵件。 我可能用Begin()和End()方法創建了一個不同的線程,並從它們中調用了Send(),但是該方法容易出錯,我希望我的應用程序的Main Form在構造期間不拋出異常。
DoEvents確實允許用戶單擊或鍵入並觸發其他事件,而后台線程是一種更好的方法。
但是,在某些情況下,您可能會遇到需要刷新事件消息的問題。 我遇到一個問題,當控件在隊列中要處理消息時,RichTextBox控件會忽略ScrollToCaret()方法。
以下代碼在執行DoEvent時阻止所有用戶輸入:
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
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.