views:

60

answers:

4

Hi there,

In a WPF app, I am using a BackgroundWorker to periodically check for a condition on the server. While that works fine, I want to pop a MessageBox notifing the users if something fails during the check.

Here's what I have:

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");                
    }   
}

But this MessageBox does not block the main window. I can still click on my WPF app and change anything I like with the MessageBox around.

How do I solve this? Thanks,


EDIT:

For reference, this is what I ended up doing:

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");
}
+2  A: 

Call ReportProgress and pass this to MessageBox.Show.

Stephen Cleary
+2  A: 

It doesn't only not block the main window, it is also very likely to disappear behind it. That's a direct consequence of it running on a different thread. When you don't specify an owner for the message box then it goes looking for one with the GetActiveWindow() API function. It only considers windows that use the same message queue. Which is a thread-specific property. Losing a message box is quite hard to deal with of course.

Similarly, MessageBox only disables windows that belong to the same message queue. And thus won't block windows created by your main thread.

Fix your problem by letting the UI thread display the message box. Use Dispatcher.Invoke or leverage either the ReportProgress or RunWorkerCompleted events. Doesn't sound like the events are appropriate here.

Hans Passant
Thank you, I had a feeling that this is thread related, but I have no idea how to solve it. Using "ReportProgess" or "RunWorkerCompleted" wasn't intuitive, but reading the explanations does makes a lot more senses.
Chi Chan
+1  A: 

Replace

MessageBox.Show("I should block the main window"); 

with

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

that will cause the message box to be on the main thread and will block all access to the UI until it gets a response. As a added bonus this.Invoke will return a object that can be cast in to the DialogResult.

Scott Chamberlain
I want to try this solution too, but I can't get it to compile. I am calling this from a normal class (not a control) and it does not have Invoke on it.I also tried "Dispatcher.CurrentDispatcher.Invoke()" but it doesn't take the Func that was suggested
Chi Chan
I assumed this was being called from the form. to be able to do this method you will need to pass the class a reference to the parent form and use _ParentForm.Invoke(...), there may be another way to do it but I do not know any off of the top of my head.
Scott Chamberlain
+1  A: 

As Stephen and Hans have said, use the ReportProgress event, and pass data to the UI thread. This is especially important if you want to do anything other tha a MessageBox (for isntance, update a control) because the background thread can't do this directly. You'll get a cross-thread exception.

So whatever you need to do (update progress bar, log messages to the UI, etc.), pass data to the UI thread, tell the UI thread what needs to be done, but let the UI thread do it.

Cylon Cat