在 WPF 应用程序中,我使用 BackgroundWorker 定期检查服务器上的情况。虽然这工作正常,但我想弹出一个消息框,如果检查过程中出现问题,通知用户。
这是我所拥有的:
public static void StartWorker()
{
worker = new BackgroundWorker();
worker.DoWork += DoSomeWork;
worker.RunWorkerAsync();
}
private static void DoSomeWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
Thread.Sleep(5000);
var isOkay = CheckCondition();
if(!isOkay)
MessageBox.Show("I should block the main window");
}
}
但是这个MessageBox并不会遮挡主窗口。我仍然可以单击我的 WPF 应用程序并使用 MessageBox 更改我喜欢的任何内容。
我该如何解决这个问题?谢谢,
编辑:
仅供参考,这就是我最终所做的:
public static void StartWorker()
{
worker = new BackgroundWorker();
worker.DoWork += DoSomeWork;
worker.ProgressChanged += ShowWarning;
worker.RunWorkerAsync();
}
private static void DoSomeWork(object sender, DoWorkEventArgs e)
{
while (!worker.CancellationPending)
{
Thread.Sleep(5000);
var isOkay = CheckCondition();
if(!isOkay)
worker.ReportProgress(1);
}
}
private static void ShowWarning(object sender, ProgressChangedEventArgs e)
{
MessageBox.Show("I block the main window");
}
更换
MessageBox.Show("I should block the main window");
与
this.Invoke((Func<DialogResult>)(() => MessageBox.Show("I should block the main window")));
这将导致消息框位于主线程上,并阻止对 UI 的所有访问,直到收到响应。作为额外的好处, this.Invoke 将返回一个可以转换为 DialogResult 的对象。
它不但不会挡住主窗口,还很有可能消失在主窗口后面。这是它在不同线程上运行的直接结果。当您没有指定消息框的所有者时,它会使用 GetActiveWindow() API 函数寻找所有者。它只考虑使用相同消息队列的窗口。这是线程特定的属性。当然,丢失消息框是很难处理的。
同样,MessageBox 仅禁用属于同一消息队列的窗口。因此不会阻塞主线程创建的窗口。
通过让 UI 线程显示消息框来解决您的问题。使用 Dispatcher.Invoke 或利用 ReportProgress 或 RunWorkerCompleted 事件。听起来这些活动在这里不合适。
致电
ReportProgress
并将 this
传递给 MessageBox.Show
。
正如 Stephen 和 Hans 所说,使用 ReportProgress 事件,并将数据传递到 UI 线程。如果您想要执行 MessageBox 以外的任何操作(例如,更新控件),这一点尤其重要,因为后台线程无法直接执行此操作。你会得到一个跨线程异常。
因此,无论您需要做什么(更新进度条、向 UI 记录消息等),将数据传递给 UI 线程,告诉 UI 线程需要做什么,但让 UI 线程去做。
我像这样修改了它并且对我来说效果很好
return Application.Current.Dispatcher.Invoke(() => MessageBox.Show(messageBoxText, caption, button, icon));
为有关使用 ReportProgress 的建议添加一些技巧:
这是鱼与熊掌兼得的好方法!或者换句话说,这是一种让您在 BackgroundWorker
' Init background worker
Dim _BGWorker As BackgroundWorker
' Your Windows Form's _Load event handler:
Private Sub Form_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
' Just for this example, we're newing up the _BGWorker here
_BGWorker = New BackgroundWorker()
_BGWorker.WorkerReportsProgress = True
End Sub
' Magical UI Action Handling ProgressChanged Event Handler Thing (v24.4.550-alpha9) ™ ©
Private Sub _BGWorker_ProgressChanged(ByVal sender As Object, ByVal e As ProgressChangedEventArgs) Handles _BGWorker.ProgressChanged
' Take the UserState object and cast to an Action delegate type
Dim uiAction As Action = CType(e.UserState, Action)
' Check if an action was passed
If uiAction IsNot Nothing Then
' Run it if so!
uiAction()
End If
End Sub
' Standard DoWork handler for BackroundWorker
Private Sub _BGWorker_DoWork(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles _BGWorker.DoWork
'...your background worker code...
' EXAMPLE:
'
' Me.Text = "Background worker is (trying to) change form title! :-O"
'
' This line would normally fail with an exception when written directly in the DoWork
' handler of a BackgroundWorker. Exception you would see is:
' System.InvalidOperationException: Cross-thread operation not valid: Control 'FormName'
' accessed from a thread other than the thread it was created on.'
' BUT...
' If we write something like this:
_BGWorker.ReportProgress(-1, New Action(
Sub()
'...and put that line inside this action:
Me.Text = "Background worker is changing form title! :-O"
' Then NO PROBLEM! UI-interactive code MAGIC!
' Well, it's not magic... This works because this code is not executed here immediately.
' It is an Action Delegate: https://learn.microsoft.com/en-us/dotnet/api/system.action-1?view=net-7.0
' You can write ANY UI-interactive code inside blocks like this in your BackgroundWorkers
' and they will all execute on the main thread, because the Action delegate is being
' shipped to the ProgressChanged event handler on the main Form thread.
'TODO: Add your other UI code...
End Sub))
' Now simply repeat the above section every time you wish write UI-interactive
' code in your BG workers! SHAZAM!
'...your background worker code...
End Sub