2

So I have this object:

public class SomeObject: INotifyPropertyChanged
{
         public decimal AlertLevel {
            get {
                return alertLevel;
            }
            set {
                if(alertLevel == value) return;
                alertLevel = value;
                OnPropertyChanged("AlertLevel");
            }

         private void OnPropertyChanged(string propertyName) {
            if(PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
}

Suppose I am changing this object on a thread that is NOT the GUI thread. How can I have this object raise the PropertyChanged event on the same thread as the GUI when I don't have a reference to any GUI component in this class?

Denis
  • 11,796
  • 16
  • 88
  • 150
  • You have to use Dispatcher. Check this http://stackoverflow.com/questions/1644079/change-wpf-controls-from-a-non-main-thread-using-dispatcher-invoke – 3615 Mar 09 '16 at 20:05
  • Yeah, I saw similar ones but where is the Dispatcher class? Which reference do I need? I can't find it anywhere... – Denis Mar 09 '16 at 20:06

5 Answers5

6

Normally the event subscriber should be responsible for marshalling the calls to the UI thread if necessary.

But if the class in question is UI specific (a.k.a view model), as soon it is created on the UI thread, you can capture the SynchronizationContext and use it for raising the event like this:

public class SomeObject : INotifyPropertyChanged
{
    private SynchronizationContext syncContext;

    public SomeObject()
    {
        syncContext = SynchronizationContext.Current;
    }

    private decimal alertLevel;

    public decimal AlertLevel
    {
        get { return alertLevel; }
        set
        {
            if (alertLevel == value) return;
            alertLevel = value;
            OnPropertyChanged("AlertLevel");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            if (syncContext != null)
                syncContext.Post(_ => handler(this, new PropertyChangedEventArgs(propertyName)), null);
            else
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Alternatively you can pass SynchronizationContext via constructor.

Yet another way is to keep the object intact, but data bind to it via intermediate synchronized binding source as described here Update elements in BindingSource via separate task.

Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • The problem with my class is that it is an element in a bindinglist. So the article you provide is very interesting. – Denis Mar 09 '16 at 22:27
  • This didn't sync in the first time I read it but I see it now. Posted an answer I ended up going with and now realize it is very similar to your answer. – Denis Mar 11 '16 at 15:03
3

for WPF - Add the following references:

PresentationFramework.dll
WindowsBase.dll

In your background thread - wrap the code that needs access to UI into a dispatcher.Invoke()

using System.Windows;
using System.Windows.Threading;
...

//this is needed because Application.Current will be NULL for a WinForms application, since this is a WPF construct so you need this ugly hack
if (System.Windows.Application.Current  == null)
   new System.Windows.Application();

Application.Current.Dispatcher.BeginInvoke(new Action(() =>
{
  //Do Your magic here 
}), DispatcherPriority.Render);

for WinForms use

  Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Render, new Action(() => {
       //Do Your magic here
   }));
Denis
  • 11,796
  • 16
  • 88
  • 150
Marty
  • 3,485
  • 8
  • 38
  • 69
1

An even better idea, without using any WPF references:

public class GUIThreadDispatcher {
        private static volatile GUIThreadDispatcher itsSingleton;
        private WeakReference itsDispatcher;

        private GUIThreadDispatcher() { }

        public static GUIThreadDispatcher Instance
        {
            get
            {
                if (itsSingleton == null)
                    itsSingleton = new GUIThreadDispatcher();

                return itsSingleton;
            }
        }

        public void Init(Control ctrl) {
            itsDispatcher = new WeakReference(ctrl);
        }

        public void Invoke(Action method) {
            ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: false));
        }

        public void BeginInvoke(Action method) {
            ExecuteAction((Control ctrl) => DoInGuiThread(ctrl, method, forceBeginInvoke: true));
        }

        private void ExecuteAction(Action<Control> action) {
            if (itsDispatcher.IsAlive) {
                var ctrl = itsDispatcher.Target as Control;
                if (ctrl != null) {
                    action(ctrl);
                }
            }
        }

        public static void DoInGuiThread(Control ctrl, Action action, bool forceBeginInvoke = false) {
            if (ctrl.InvokeRequired) {
                if (forceBeginInvoke)
                    ctrl.BeginInvoke(action);
                else
                    ctrl.Invoke(action);
            }
            else {
                action();
            }
        }
    }
}

And initialize like this:

  private void MainForm_Load(object sender, EventArgs e) {

     //setup the ability to use the GUI Thread when needed via a static reference
     GUIThreadDispatcher.Instance.Init(this);  
     ...
  }

And use like this:

public class SomeObject: INotifyPropertyChanged
{
         public decimal AlertLevel {
            get {
                return alertLevel;
            }
            set {
                if(alertLevel == value) return;
                alertLevel = value;
                OnPropertyChanged("AlertLevel");
            }

          private void OnPropertyChanged(string propertyName) {
              GUIThreadDispatcher.Instance.BeginInvoke(() => {
              if (PropertyChanged != null)
                    PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                });
        }}
Denis
  • 11,796
  • 16
  • 88
  • 150
0

This turned out to be a clean implementation (relatively). Just had to include a reference to WindowsBase.dll which turns out to be a WPF library so eh..., not extremely pleased with it but it's a solution...:

 public class GUIThreadDispatcher {
        private static volatile GUIThreadDispatcher itsSingleton;
        private Dispatcher itsDispatcher;

        private GUIThreadDispatcher() { }
        public static GUIThreadDispatcher Instance
        {
            get
            {
                if (itsSingleton == null)
                    itsSingleton = new GUIThreadDispatcher();

                return itsSingleton;
            }
        }

        public void Init() {
            itsDispatcher = Dispatcher.CurrentDispatcher;
        }

        public object Invoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
           return itsDispatcher.Invoke(method, priority, args);
        }

        public DispatcherOperation BeginInvoke(Action method, DispatcherPriority priority = DispatcherPriority.Render, params object[] args) {
            return itsDispatcher.BeginInvoke(method, priority, args);
        }

Then initialize it like this:

static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() {
           GUIThreadDispatcher.Instance.Init();  //setup the ability to use the GUI Thread when needed via a static reference
           Application.Run(new MainForm());
        }
    }

And then use it like this:

public class SomeObject: INotifyPropertyChanged
{
         public decimal AlertLevel {
            get {
                return alertLevel;
            }
            set {
                if(alertLevel == value) return;
                alertLevel = value;
                OnPropertyChanged("AlertLevel");
            }

          private void OnPropertyChanged(string propertyName) {
                GUIThreadDispatcher.Instance.BeginInvoke(() => {
                    if (PropertyChanged != null)
                         PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                });
        }}
Denis
  • 11,796
  • 16
  • 88
  • 150
0

Found an even better answer without having to use a WeakReference to the form control and NO WPF References based on https://lostechies.com/gabrielschenker/2009/01/23/synchronizing-calls-to-the-ui-in-a-multi-threaded-application/ and Ivan's answer above:

   public class GUIThreadDispatcher {
    private static volatile GUIThreadDispatcher itsSingleton;
    private SynchronizationContext itsSyncContext;

    private GUIThreadDispatcher() {}

    /// <summary>
    /// This needs to be called on the GUI Thread somewhere
    /// </summary>
    public void Init() {
        itsSyncContext = AsyncOperationManager.SynchronizationContext;
    }

    public static GUIThreadDispatcher Instance
    {
        get
        {
            if (itsSingleton == null)
                itsSingleton = new GUIThreadDispatcher();

            return itsSingleton;
        }
    }

    public void Invoke(Action method) {
        itsSyncContext.Send((state) => { method(); }, null);
    }

    public void BeginInvoke(Action method) {
        itsSyncContext.Post((state) => { method(); }, null);
    }
}

}

And initialize like this:

  private void MainForm_Load(object sender, EventArgs e) {

     //setup the ability to use the GUI Thread when needed via a static reference
     GUIThreadDispatcher.Instance.Init();  
     ...
  }

And use like this:

public class SomeObject: INotifyPropertyChanged
{
         public decimal AlertLevel {
            get {
                return alertLevel;
            }
            set {
                if(alertLevel == value) return;
                alertLevel = value;
                OnPropertyChanged("AlertLevel");
            }

          private void OnPropertyChanged(string propertyName) {
                  GUIThreadDispatcher.Instance.BeginInvoke(() => {
                     if (PropertyChanged != null)
                          PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                });
        }}
Denis
  • 11,796
  • 16
  • 88
  • 150