2

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.

14
  • but im telling u bro same code in windows forms it works!!! @Sheridan Commented May 7, 2015 at 12:49
  • What are you trying to achieve? It's very uncommon to open a window on another thread. Commented May 7, 2015 at 12:51
  • @CharlesMager im working on a c# network program bro Commented May 7, 2015 at 12:52
  • @Sheridan You can if the background thread was the thread which created the Window. It happens to be the case here. Commented May 7, 2015 at 12:52
  • 4
    Don't create multiple UI threads. Use a single UI thread, and do all of your UI work in that one thread. Do any long running non-UI work you have in a non-UI thread, rather than doing non-UI work in your UI thread and creating a second thread to do UI work. You're making things way harder on yourself than you need to. Commented May 7, 2015 at 13:30

2 Answers 2

1

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();
    }
Sign up to request clarification or add additional context in comments.

5 Comments

this will work even without the owner property dude.. all the point of my question was how to do this with thread cuz im using it in a socket program ...@Eledra Nguyen
You don't need to use test; you can just call Dispatcher.BeginInvoke(Display) and it'll work.
@Dan: BeginInvoke need a delegate.
Ah yes; Invoke will accept an Action but BeginInvoke won't. You can do this though: Dispatcher.BeginInvoke((Action)Display);
Awesome ! Changed the answer as your suggestion.
0

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.)

7 Comments

Would the downvoter explain themselves? I believe I've posted a working answer to the problem at hand (while also explaining that it's typically a bad idea).
ty bro i really appreiciate ur help i tried ur suggestion and when i trigger the button click the ui just getting freeze... it's hangs bro...
@user3548681: are you debugging it? What line does it freeze on? Freezing likely means you've hit another issue related to your use of threading...
@Dan: your answer is precise in general. However, in his specific case, he did not init the Dispatcher loop, resulting in Invoke hang the application.
@EledraNguyen: what do you mean by "init the Dispatcher loop?"
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.