在WPF中使用Backgroundworker为主应用程序弹出MessageBox

14
在WPF应用中,我使用BackgroundWorker定期检查服务器上的某个条件。虽然这很好运作,但如果在检查过程中发生错误,我希望弹出一个MessageBox通知用户。以下是我的代码:
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不会阻塞主窗口。我仍然可以在MessageBox存在的情况下点击我的WPF应用程序并更改任何我想要的东西。

我该怎么解决?谢谢。


编辑:

供参考,这是最终所做的:

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");
}
6个回答

13
替换。
MessageBox.Show("I should block the main window"); 

使用

this.Invoke((Func<DialogResult>)(() => MessageBox.Show("I should block the main window")));

这将导致消息框在主线程上出现,并阻止所有对UI的访问,直到它得到响应。作为额外的奖励,this.Invoke将返回一个可以转换为DialogResult的对象。


我也想尝试这个解决方案,但是我无法编译它。我从普通类(而不是控件)中调用它,但它没有Invoke。我还尝试过“Dispatcher.CurrentDispatcher.Invoke()”,但它不能使用建议的Func。 - Chi Chan
我认为这是从表单中调用的。为了能够使用这个方法,你需要向类传递一个对父表单的引用,并使用 _ParentForm.Invoke(...),可能还有其他方法可以实现,但我并不知道。 - Scott Chamberlain
1
这甚至在表单上都无法编译。 - Stealth Rabbi
抱歉。各种错误指示无效的表达式项,),=>以及语句应该结束的位置的期望。我推断是括号或lambda太多/太少了。 - Stealth Rabbi
1
@ScottChamberlain 我认为你忘记了一组括号:this.Invoke((Func<DialogResult>)(() => MessageBox.Show("我应该阻止主窗口"))); - sventevit

11
它不仅不会阻塞主窗口,还有很大可能会消失在它后面。这是它在不同线程上运行的直接结果。当你没有为消息框指定所有者时,它会使用 GetActiveWindow() API 函数寻找所有者。它只考虑使用相同消息队列的窗口,这是一个特定于线程的属性。当然,失去一个消息框是相当困难的。
同样地,MessageBox 只会禁用属于相同消息队列的窗口,因此不会阻塞主线程创建的窗口。
通过让 UI 线程显示消息框来解决您的问题。使用 Dispatcher.Invoke 或利用 ReportProgress 或 RunWorkerCompleted 事件。听起来这里并不适合使用事件。

谢谢,我有一种感觉这是与线程相关的,但我不知道如何解决它。使用“ReportProgess”或“RunWorkerCompleted”并不直观,但是阅读解释确实更容易理解。 - Chi Chan

8

调用ReportProgress并将this传递给MessageBox.Show


2
我将其修改为这样,对我很好用。
 return Application.Current.Dispatcher.Invoke(() => MessageBox.Show(messageBoxText, caption, button, icon));

1
应该在答案下面添加注释。 - user3595247

2
正如Stephen和Hans所说,使用ReportProgress事件,并将数据传递到UI线程。如果您要执行除MessageBox之外的任何操作(例如更新控件),则这尤为重要,因为后台线程无法直接执行此操作。否则,您将收到跨线程异常。
因此,无论您需要做什么(更新进度条,将消息记录到UI等),都要将数据传递到UI线程,告诉UI线程需要做什么,但让UI线程来执行。

-1
为了增加一些关于使用ReportProgress的建议的趣味性:
这里有一个巧妙的方法,让你既能拥有蛋糕,又能吃掉它!换句话说,这是一种让你在BackgroundWorker中编写UI交互代码而不会遇到跨线程操作的方法。我在某天为一个项目把这个东西拼凑在一起,买家自行决定是否使用!
' 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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接