Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF NotifyIcon From Background Thread

Tags:

wpf

I know normally one is not supposed to touch UI elements from threads other than the UI thread, but I am new to WPF and I am wondering if my current working implementation can be improved.

I have an application that is comprised solely of a notification tray icon, and I want to update that icon from a background thread.

Here is my Program.cs entry point:

static class Program
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);

        using (IconHandler notify = new IconHandler())
        {
            notify.Display();
            Application.Run();
        }

    }
}

This is my IconHandler.cs notification icon handler class:

class IconHandler : IDisposable
{

    NotifyIcon ni;

    public IconHandler()
    {
        ni = new NotifyIcon();
    }

    public void Display()
    {
        ni.MouseClick += new MouseEventHandler(ni_MouseClick);
        ni.Icon = Resources.icon1;
        ni.Visible = true;

        new Thread(new ThreadStart(UpdateIcon)).Start();
    }

    public void UpdateIcon()
    {
        while (true)
        {
            // reference ni directly, it updates fine
        }
    }

    public void Dispose()
    {
        ni.Dispose();
    }

    void ni_MouseClick(object sender, MouseEventArgs e)
    {
        // something useful
    }
}

Is there anything blatantly incorrect about this? It seems a bit fishy to me - it was just my first attempt. It seems to work for what I want to do, does anyone have any suggestions for a better implementation? Will I run into lifecycle issues with this setup?

like image 487
jpalm Avatar asked Jul 22 '15 16:07

jpalm


2 Answers

Is there anything blatantly incorrect about this? It seems a bit fishy to me - it was just my first attempt. It seems to work for what I want to do, does anyone have any suggestions for a better implementation? Will I run into lifecycle issues with this setup?

To begin with NotifyIcon is not a WPF control, but comes from the Windows Forms namespace. As such it has normal C# properties (e.g. Icon, Visible) meaning you have been able to alter the icon property in the non-UI thread without an exception being raised. If you had used a WPF controls then they have Dependency Properties and direct manipulation of Dependency Properties outside of the UI thread will cause an exception to be raised.

Will I run into lifecycle issues with this setup?

You've currently NOT created a WPF window or WPF controls. If your application develops such that you start using WPF and the UpdateIcon method is expanded to do more than you currently do and access these WPF objects then yes you will need a strategy to deal with the updates from non-UI threads.

You can hide some of this cross-threaded access using some helper methods.


Example 1 If your strategy becomes referencing WPF controls programmatically from the background thread then you can use a helper method such as this.

It first checks if the call is on the UI thread, if so then it updates the control directly, otherwise it will schedule that the method (itself) be called from the UI thread at a later point in time.

I've used BeginInvoke here so that the background thread can continue before the UI thread has actually called the method. If you want to block the background thread then use Invoke instead.

public void UpdateLabel(Label control, string text)
{ 
  if (Application.Current.Dispatcher.CheckAccess()) 
    control.Content = text;
  else
    Application.Current.Dispatcher.BeginInvoke(new System.Action(() => UpdateLabel(control, text)), DispatcherPriority.Normal);
}


Example 2 If your strategy uses Events raised on the background thread to update the WPF controls programmatically then you can hide some of the cross-threading calls as part of raising the event, leaving the WPF update routine quite clean and simple to read.

Any event handlers of this event can be coded knowing that the call will be made from the UI thread, so no threading issues.

public void OnRaiseEvent(EventHandler handler, EventArgs args)
{
  if (handler != null)
    {
    if (Application.Current.Dispatcher.CheckAccess())
      handler(sender, new PropertyChangedEventArgs(propName));
    else
      Application.Current.Dispatcher.BeginInvoke(new System.Action(() => handler(sender, args)), DispatcherPriority.Normal);
     }
}


Example 3 If your future strategy fully utilizes the benefits of WPF with Binding (as opposed to programmatically updating your WPF controls), then you can embed the cross-threading code into the data-bound objects.

If for example your XAML databinds to the MyProperty property of an instance of the MyDataClass class and that class implements the INotifyPropertyChanged interface you can put the cross-threading code in the data class making it possible to update the data from any thread. Here is the example of the class:-

public class MyDataClass : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private string _myProperty;

    public string MyProperty { get { return _myProperty;} set { PropertyChanged.SetValueAndNotify(this, ref _myProperty, value); } }
}

This class utilizes the SetValueAndNotify extension method on the PropertyChanged event. It is in here we hide the cross-threading code to simplify other parts of the code. Here's the definition of this extension method.

public static class PropertyChangedExtension
{
    public static void SetValueAndNotify<T>(this PropertyChangedEventHandler handler, object sender, ref T destination, T source, [CallerMemberName] string propName = "notset")
    {
        // Is the new value different from the previous value? If there is no difference then there is nothing more to do
        if (Equals(destination, source))
            return;

        // If we got to this point then the new value is different from the old value, so lets make the assignemnt and raise the property changed event
        destination = source;

        if (handler != null)
        {
            if (Application.Current.Dispatcher.CheckAccess())
                handler(sender, new PropertyChangedEventArgs(propName));
            else
                Application.Current.Dispatcher.BeginInvoke(new System.Action(() => handler(sender, new PropertyChangedEventArgs(propName))), DispatcherPriority.Normal);
        }
    }
}

The above example uses the [CallerMemberName] attribute from C#5 to remove any typing errors in supplying the property name for the INotifyPropertyChanged arguments. If you are not using the latest then you will need to modify the getter and setter as follows:-

public string MyProperty { get { return _myProperty;} set { PropertyChanged.SetValueAndNotify(this, ref _myProperty, value, "MyProperty"); } }
like image 171
Rhys Avatar answered Nov 01 '22 17:11

Rhys


You must always update UI from UI thread only, however, you can schedule some work on UI thread from background thread using dispatcher

public void Display()
{
    ni.MouseClick += new MouseEventHandler(ni_MouseClick);
    ni.Icon = Resources.icon1;
    ni.Visible = true;

    new Thread(new ThreadStart(UpdateIcon)).Start();
}

public void UpdateIcon()
{
    while (true)
    {
        //do some long running work
        Application.Current.Dispatcher.Invoke(()=>{
           //update ui
        });
    }
}

But if you don't have long running work and you just want to do something periodically, you should use DispatcherTimer instead of loop in background thread.

like image 30
Liero Avatar answered Nov 01 '22 17:11

Liero