views:

2719

answers:

2

I have a WPF dll that contains a number of Window classes and exposes methods that display those windows.

I also have a separate WinForms project that is calling one of those methods in the WPF project inside the DoWork method of a BackgroundWorker component.

On the line of code that instantiates a WPF Window, I get the following runtime error:

The calling thread must be STA, because many UI components require this.

A google search let me to this discussion. (Turns out Jon Skeet answers questions on other sites in addition to Stack Overflow!) He linked to this article, which states

The BackgroundWorker component works well with WPF ...

That article also mentions using the DispatcherObject class, but I don't understand how to make that work and I would rather just continue using my BackgroundWorker component.

As a test case, I came up with the following code to reproduce the error. In the WPF class library, here is the code in Window1.xaml.vb

Partial Public Class Window1

    Public Shared Function ShowMe() As Boolean?
        Dim w = New Window1 'Error appears on this line.
        Return w.ShowDialog()
    End Function

End Class

In the WinForms application, here is the code in Form1.vb

Imports System.ComponentModel

Public Class Form1

    Private WithEvents worker As BackgroundWorker
    Private Sub doWord(ByVal sender As Object, ByVal e As DoWorkEventArgs) Handles worker.DoWork
        WpfLibrary.Window1.ShowMe()
    End Sub
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        worker = New BackgroundWorker
        worker.RunWorkerAsync()
    End Sub
End Class

Even when the BackgroundWorker component is placed in Window1.xaml.vb itself, the same error occurs. So, is that article wrong and I can't really use a BackgroundWorker with WPF? Or is there something else I need to do to get it to work?

If the BackgroundWorker won't work, then how would I replace the code in Form1.vb above to use a Dispatcher instead?

+2  A: 

You can use a background worker with WPF, that's not your problem.

Your problem is that you can't perform any task that updates the UI from outside of the main UI thread in either a winform or WPF application and the BackgroundWorker's DoWork method is running in a different thread.

Therefore you must open the new window outside of the BackgroundWorker's background thread, either before you start the BackgroundWorker, or in its RunWorkerCompleted event.

Without knowing the code that surrounds the call to open the window it is difficult for me to advise further, but I hope that points you in the right direction.

Martin Harris
Thanks! I just copied the code out of the DoWork event into the RunWorkerCompleted event and it works.
Dennis Palmer
FYI, I think that RunWorkerCompleted runs on the same thread that kicked off the worker (rather than the worker's thread). If so, you might as well not use a BackgroundWorker at all.
Eddie Deyo
Not true. You can have multiple UI threads in WPF (I've done it). However, you wouldn't want to show UI from a BackgroundWorker thread, because it's not fully under your control -- the ThreadPool decides what that thread will do and when. You'd need a regular Thread, set it to STA, and call Application.Run from your ThreadStart method.
Joe White
@OAB, I don't care which thread is running the code. I just need the method to return right away instead of waiting for the Window to display, do it's thing and then close.
Dennis Palmer
+2  A: 

Your worker could also create its own thread, mark that thread as STA, and then call Thread.Join() to wait for it to terminate. You would then be able to create and show the window on your new thread (although note that no other thread will be able to interact with it without using the Dispatcher).

Example in C#:

Thread workerThread = new Thread(ShowMyWindow);
workerThread.SetApartmentState(ApartmentState.STA);
workerThread.Start();
workerThread.Join();

And then:

private void ShowMyWindow()
{
    WpfLibrary.Window1.ShowMe()
}
Eddie Deyo
What does that look like in code? How is a thread marked STA?
Dennis Palmer
I've added an example in C#. Sorry, I don't know VB well enough to do a VB example, but the SetApartmentState call is what you need.
Eddie Deyo
Cool, thanks! I have worked with C# also, but I think most VB devs know enough C# to read code examples. We have to bcs that's what most are written in!
Dennis Palmer