9

I have a FileSystemWatcher that react on the Changed event.

I want to open the file, read its content display it in a textbox and hide the popup that has been created after 1 sec. The code almost work but something fail when hiding the popup.

Here is a snippet of the code :

       txtLog.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate() {
            this.txtLog.Text = dataToDisplay;
            extendedNotifyIcon_OnShowWindow();
            Thread threadToClosePopup = new Thread(new ThreadStart((Action)delegate() { 
                Thread.Sleep(1000); 

                extendedNotifyIcon_OnHideWindow();
       }));
            threadToClosePopup.Start();
        });

As you can see, I use the Invoke to set the text because the event is in a different thread (FileSystemWatcher). But to hide the windows the extendedNotifyIcon_OnHideWindow() is not executed in the thread of the GUI. How can I execute it in the GUI thread?

Patrick Desjardins
  • 125,683
  • 80
  • 286
  • 335

6 Answers6

25

This works well for WPF with MVVM.

Application.Current.Dispatcher.Invoke(
    () =>
    {
         // Code to run on the GUI thread.
    });

This will not work consistently (it will fail if we are inside a handler for Reactive Extensions):

Dispatcher.CurrentDispatcher.Invoke(
    () =>
    {
         // Fails if we are inside a handler for Reactive Extensions!
    });

Extra for Experts: The Reason?

By design, any thread can have a dispatcher thread paired with it, see MSDN: Dispatcher Class.

If we reference Dispatcher.CurrentDispatcher from any thread, it will actually create a new dispatcher thread, which is separate to the official WPF UI dispatcher thread. When we try to execute code on this newly created dispatcher thread, WPF will throw as it's not the official UI dispatcher thread.

The solution is to always use Application.Current.Dispatcher.Invoke(), or Application.Current.Dispatcher.BeginInvoke(). See What's the difference between Invoke() and BeginInvoke().


Update 2020-05-02: It is possible to run a WPF application with multiple WPF UI dispatcher threads. I work with a huge WPF app that is doing this. It is tricky to get it to work, but once it works - it's amazing and the entire app runs an order of magnitude faster as there are multiple UI dispatcher threads. Happy to answer questions on this.


Tested on:

  • WPF
  • .NET 4.5
  • .NET 4.6
  • .NET 4.61
Contango
  • 65,385
  • 53
  • 229
  • 279
11

To execute the extendedNotifyIcon_OnHideWindow method on the GUI thread use the Dispatcher as you did to show it.

Thread threadToClosePopup = new Thread(new ThreadStart((Action)delegate() { 
  Thread.Sleep(1000); 
  txtLog.Dispatcher.Invoke(
    DispatcherPriority.Normal,
    (Action)() => extendedNotifyIcon_OnHideWindow());
}));
JaredPar
  • 673,544
  • 139
  • 1,186
  • 1,421
3

This will give you the Window dispatcher:

Dispatcher.CurrentDispatcher

As long as you get it on the windows thread.

ozczecho
  • 7,951
  • 6
  • 33
  • 40
2

Use Control.Invoke

   txtLog.Dispatcher.Invoke(DispatcherPriority.Normal, (Action)delegate() {
        this.txtLog.Text = dataToDisplay;
        extendedNotifyIcon_OnShowWindow();
        Thread threadToClosePopup = new Thread(new ThreadStart((Action)delegate() { 
            Thread.Sleep(1000); 

            extendedNotifyIcon.Invoke(extendedNotifyIcon_OnHideWindow);
   }));
        threadToClosePopup.Start();
    });
Alex Reitbort
  • 13,061
  • 1
  • 36
  • 60
1

Just wrap extendedNotifyIcon_OnHideWindow(); into a Dispatcher.Invoke()

But I would rather do it (all im XAML) using an Animation and with an EvenTrigger that triggers upon the TimeLine.Completed event.

bitbonk
  • 45,662
  • 32
  • 173
  • 270
  • I am really new to WPF, just needed to modify an existing code. The code in the XAML popup and in fact pop down when the mouse leave the container. But, now I need to add something to pop it from code and pop down from code without deleting the initial behavior. +1 for the idea – Patrick Desjardins Sep 15 '10 at 14:45
0

The problem is that extendedNotifyIcon_OnHideWindow is being executed on a thread other than the UI thread. You will need to use the Dispatcher for that part as well. Also, I would not create a dedicated thread just to wait one second. You could easily refactor that part into a System.Threading.Timer. Here is my refactored version.

txtLog.Dispatcher.Invoke(
    (Action)(() =>
        {
            txtLog.Text = dataToDisplay;
            extendedNotifyIcon_OnShowWindow(); 
            new Timer(
                (state) =>
                {
                    button1.Dispatcher.BeginInvoke(
                        (Action)(() =>
                            {
                              extendedNotifyIcon_OnHideWindow(); 
                            }), null);
                }, null, 1000, Timeout.Infinite);
        }));
Brian Gideon
  • 45,093
  • 12
  • 98
  • 145