-1

I'm trying to close a WPF window I created in a separate thread from the main hosting thread (The main thread is of a PDM application that I have no control over). The host application references my assembly (it's a plugin). I don't know but why the Dispatcher is always null. Creating the WaitView on the host application is not an option for me.

Thanks guys!

    var WaitViewModel = new MVVM.ViewModels.WaitViewModel();
                        MVVM.Views.WaitView WaitView = default(MVVM.Views.WaitView);
                        Dispatcher dispatcher = default(Dispatcher); 
                        var thread = new Thread(new ThreadStart(() =>
                        {
                            dispatcher = Dispatcher.CurrentDispatcher; 
                            WaitView = new MVVM.Views.WaitView();
                            WaitView.Topmost = true;
                            WaitView.WindowStartupLocation = WindowStartupLocation.CenterScreen;
                            WaitView.DataContext = WaitViewModel;
                            WaitView.Show();
                            System.Windows.Threading.Dispatcher.Run();
                        }));
                        thread.SetApartmentState(ApartmentState.STA);
                        thread.IsBackground = true;
                        thread.Start();


'unrelated code here
if (dispatcher != null)
dispatcher.Invoke(()=>
{
WaitView.Close();
});
Amen Jlili
  • 1,884
  • 4
  • 28
  • 51
  • 1
    You can't create UI elements in a non-UI thread. – Enigmativity May 17 '18 at 04:03
  • @Enigmativity do you mind explaining why. – Amen Jlili May 17 '18 at 04:07
  • 1
    UI elements are not thread-safe so any cross calls can corrupt the UI state. Things like dispatchers and message pumps aren't properly set up. – Enigmativity May 17 '18 at 04:11
  • Why you ask? _[Multithreaded toolkits: A failed dream?, Oracle](https://community.oracle.com/blogs/kgh/2004/10/19/multithreaded-toolkits-failed-dream)_ –  May 17 '18 at 07:16
  • Possible duplicate of [Ensuring that things run on the UI thread in WPF](https://stackoverflow.com/questions/2382663/ensuring-that-things-run-on-the-ui-thread-in-wpf) – Amen Jlili Jun 04 '18 at 15:57

3 Answers3

1

Two ways to do this:

  1. Pass the view's dispatcher into the view model via the constructor.

    public MyClass
    {
        public MyClass(Dispatcher dispatcher)
        {
             // use your view's dispatcher.
        }
    {
    
  2. Use the Application default dispatcher.

    Dispatcher dispatcher = App.Current.Dispatcher;
    

For clarity, a true view model will not use a dispatcher since it is on the UI thread. Nevertheless, you could use regular methods and have the view's dispatcher execute them on the View.

Ryan Thiele
  • 364
  • 2
  • 7
  • App.Current returns a null for me. I have already tried this. My dll is loaded in a host application that not a .NET assembly. – Amen Jlili May 17 '18 at 03:31
  • @JLILIAmen just because `App.Current.Dispatcher` returns `null` for you in your _incorrect_ code to begin with, is not a good enough reason to downvote –  May 17 '18 at 04:17
0

You should grab the dispatcher prior to creating the thread, then pass it into the thread.

In the spirit of dont do that, you shouldn't be creating any form of UI elements in other threads, even if they are marked as STA. Spawning child threads that just run to eternity is not so nice so is potentially multiple message pumps. So, your base design is kinda flawed.

Fix that and your other problems go away.

I hope you are not doing all this from say a console app that is attempting to make it look as though your windows are part of a different process?

  • My dll is loaded via a third party application. – Amen Jlili May 17 '18 at 03:16
  • I don't understand why I should grab the dispatcher prior to creating the thread. The view will get created in that thread. Only that thread should be to update view hence the reason why I'm trying to get the dispatcher of that thread. I hope I'm not missing something. – Amen Jlili May 17 '18 at 03:20
  • I don't have a choice here. If I run the view on the main thread than it freezes. – Amen Jlili May 17 '18 at 03:21
  • 1
    @JLILIAmen Experience has shown when you call `Dispatcher.CurrentDispatcher` from inside a worker thread, it won't be the same as the UI thread and will fail when you later try to use it to marshal to the UI. But don't take my word for it, take Enigmativity's. Considering you already state you are getting `null`, you are being quite arrogant about the whole thing not to mention downvoting everyone's answers for insufficient reasons. –  May 17 '18 at 04:16
0

Solution is to handle closing the view from the view code behind.

I have added a property called CloseRequest to the ViewModel.

View's code behind:

  WaitViewModel WaitViewModel;
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            // get data context 
             WaitViewModel = this.DataContext as WaitViewModel;
            WaitViewModel.PropertyChanged += WaitViewModel_PropertyChanged;
        }

        private void WaitViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            if(e.PropertyName == "CloseRequest")
            {
                Dispatcher dispatcher = this.Dispatcher;
                if (WaitViewModel.CloseRequest)
                    dispatcher.Invoke(() => { 
                    this.Close();
                    });
            }
        }
Amen Jlili
  • 1,884
  • 4
  • 28
  • 51
  • 2
    So you ripoff what I and others said and then downvote everyone else? Here's a downvote for your plagiarism –  May 17 '18 at 04:38
  • @MickyD I didn't rip off what you said. I didn't even understand it to begin with. – Amen Jlili May 17 '18 at 18:04