user3548681
user3548681

Reputation:

Odd behavior when creating a new window in WPF using another thread

I am trying to make a simple application in WPF which will open a new window in a thread it's behaving oddly.

ArrayList formArray = new ArrayList();
Thread th;
Window1 vd;

public void Start()
{
    vd = new Window1();

    formArray.Add(vd);
    vd.ShowDialog();
}

public void StartCall()
{
    th = new Thread(new ThreadStart(Start));
    th.SetApartmentState(ApartmentState.STA);
    th.Start();
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    StartCall();
}

private void Button_Click(object sender, RoutedEventArgs e)
{
    ((Window1)(formArray[0])).Show();
}

Window1 code is

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    e.Cancel = true;
    this.Hide();
}

When trying to open it again, it just throws an error The calling thread cannot access this object because a different thread owns it.

When trying to use dispatcher.. invoke... all these things didn't help. To make it even weirder, this same code worked in a Windows Forms application.

Maybe it's related to this line? th.SetApartmentState(ApartmentState.STA);?

It might be this guys, but if I won't add it, it will also fail with an error that

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

Upvotes: 2

Views: 566

Answers (2)

Eledra Nguyen
Eledra Nguyen

Reputation: 400

Edit: Added the force run on dispatcher on your thread. I also added a Display method to show the dialog depending on the dispatcher who is calling. Hope that help !

Also, as explained here: Dispatcher.Run

You should shutdown the dispatcher of the corresponding thread when you are done.

MainWindow:

    void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        StartCall();
    }

    ArrayList formArray = new ArrayList();
    Window1 vd;
    Thread th;

    public void Start()
    {
        vd = new Window1();
        formArray.Add(vd);
        vd.ShowDialog();

        System.Windows.Threading.Dispatcher.Run(); //ok this is magic
    }

    public void StartCall()
    {
        th = new Thread(new ThreadStart(Start));
        th.SetApartmentState(ApartmentState.STA);
        th.Start();
    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        StartCall();
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        ((Window1)(formArray[0])).Display();            
    }

Window1:

    void Window1_Closing(object sender, System.ComponentModel.CancelEventArgs e)
    {
        e.Cancel = true;
        this.Hide();
    }

    public void Display()
    {
        if (!Dispatcher.CheckAccess())
        {
            Dispatcher.BeginInvoke((Action)Display);
            return;
        }

        this.Show();
    }

Upvotes: 1

Dan Puzey
Dan Puzey

Reputation: 34198

You can't call .Show on your window from a thread other than the one it was created on (that's basically what the error message is telling you!). Fortunately, as you suggested, this is what the dispatcher is for: to marshal calls onto the correct thread. But you have to use the correct dispatcher for the job!

Each control in WPF (including a Window) has a .Dispatcher property that gets the Dispatcher for that control's thread. My guess is that you were using the one from your main window when trying to re-open the dialog - which is the wrong one. Instead, if you use this in your Button_Click you will have more luck:

var window = (Window1)formArray[0];
window.Dispatcher.Invoke(window.Show);  // note: use the dispatcher that belongs to the window you're calling

(NOTE: this isn't to say that this is a typically useful/recommended design pattern. In fact, it's often going to cause more problems than it solves. But, it's certainly something you can choose to do.)

Upvotes: 0

Related Questions