简体   繁体   中英

Closing a window owned by a different thread

I am new to threading. I am using background threads in my WPF Application to talk to the DB and message communication.

One of the view models should open a separate window. Since this should Run as a UI thread, I am doing:

    private void OnSelection(SelectionType obj)
    {
        Thread thread = new Thread(ShowRegionWindow);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
    }
    private void ShowRegionWindow()
    {
        var rWindow = new RegionWindow();
        rWindow .Show();
        rWindow .Closed += (s, e) => System.Windows.Threading.Dispatcher.ExitAllFrames();
        System.Windows.Threading.Dispatcher.Run();
    }

Now I need to close this window on another message. How do I do that?

Before I go any further, you said you are new to threading and I want to stress that there is probably no good reason for your application to open windows on different threads. It is good that you are using MVVM, but you may not be doing it right. Ideally, all your views and view models would be on the main UI thread. Any worker threads in your model layer need to invoke the UI dispatcher before interacting with a view model. For instance, you might have an update event on a worker thread call a handler on the view model to update the UI. The UI dispatcher should either be invoked immediately before or after that event is invoked. (To be clear though, the model should not know about the view model.)

In fact, you seem to be creating a new Window in a UI event handler which means you should probably just do this:

private void OnSelection(SelectionType obj)
{
    var rWindow = new RegionWindow();
    rWindow.Show();
}

However, maybe you have a perfectly legitimate reason for doing it the way you are. If so, one way you could close that new window from the calling thread would be to pass in an event. You could do something like this:

private event Action CloseRegionWindows = delegate { }; // won't have to check for null

private void OnSelection(SelectionType obj)
{
    Thread thread = new Thread(() => ShowRegionWindow(ref CloseRegionWindows));
    ...
}

private void ShowRegionWindow(ref Action CloseRegionWindows)
{
    var rWindow = new RegionWindow();
    rWindow.Show();
    CloseRegionWindows += () => rWindow.Dispatcher.BeginInvoke(new ThreadStart(() => rWindow.Close()));
    ...
}

And then raise that event somewhere:

private void OnClick(object sender, RoutedEventArgs args)
{
    CloseRegionWindows();
}

After reading some of your comments again, I think I have a better understanding of the scenario. Here's what you need to do.

First, be sure that one of your ViewModels has a reference to the Model that needs to open and close a window. One way to accomplish that is constructor dependency injection.

public ViewModel(Model model) // or IModel
{
    ...

Next, you'll need to capture the UI dispatcher in that ViewModel. The best place for this is probably also the ViewModel constructor.

private Dispatcher dispatcher;

public ViewModel(Model model)
{
    dispatcher = Dispatcher.CurrentDispatcher;
    ...

Now create two events in your Model; one to open and one to close the window.

class Model
{
    internal event Action OpenWindow = delegate { };
    internal event Action CloseWindow = delegate { };
    ...

And subscribe to them in your ViewModel constructor.

public ViewModel(Model model)
{
    dispatcher = Dispatcher.CurrentDispatcher;
    model.OpenWindow += OnWindowOpen;
    model.CloseWindow += OnWindowClose;
    ...
}

Now open and close your window with the UI Dispatcher in the ViewModel class;

private Window window;

private void OnWindowOpen()
{
    // still on background thread here

    dispatcher.BeginInvoke(new ThreadStart(() =>
    {
        // now we're on the UI thread

        window = new Window();
        window.Show();
    }
}

private void OnWindowClose()
{
    dispatcher.BeginInvoke(new ThreadStart(() =>
    {
        window.Close();
    }
}

Finally, raise the OpenWindow and CloseWindow events from your background thread in your Model, just as you would raise any event. Your Model might look something like this:

class Model
{
    private Thread worker;

    internal event Action OpenWindow = delegate { };
    internal event Action CloseWindow = delegate { };

    public Model()
    {
        worker = new Thread(Work);
        worker.Start();
    }

    private void Work()
    {
        while(true)
        {
            if (/*whatever*/) OpenWindow();
            else if (/*whatever*/) CloseWindow();
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM