3

In my application, I have a Command that I only want the user to be able to trigger if it is not already running. The command in question is bound to a WPF button which means that it automagically disables the button if CanExecute is false. So far so good.

Unfortunately, the operation performed by the command is a long running one, so it needs to occur on a different thread. I didn't think that would be a problem... but it seems it is.

I have extracted out a minimal sample that will show the problem. If bound to a button (via the LocalCommands.Problem static reference) the button will be disabled as desired. When the worker thread tries to update CanExecute, an InvalidOperationException will be thrown from inside System.Windows.Controls.Primitives.ButtonBase.

What is the most appropriate way to resolve this?

Sample command code below:

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

namespace InvalidOperationDemo
{
    static class LocalCommands
    {
        public static ProblemCommand Problem = new ProblemCommand();
    }

    class ProblemCommand : ICommand
    {
        private bool currentlyRunning = false;
        private AutoResetEvent synchronize = new AutoResetEvent(false);

        public bool CanExecute(object parameter)
        {
            return !CurrentlyRunning;
        }

        public void Execute(object parameter)
        {
            CurrentlyRunning = true;

            ThreadPool.QueueUserWorkItem(ShowProblem);
        }

        private void ShowProblem(object state)
        {
            // Do some work here. When we're done, set CurrentlyRunning back to false.
            // To simulate the problem, wait on the never-set synchronization object.
            synchronize.WaitOne(500);

            CurrentlyRunning = false;
        }

        public bool CurrentlyRunning
        {
            get { return currentlyRunning; }
            private set
            {
                if (currentlyRunning == value) return;

                currentlyRunning = value;

                var onCanExecuteChanged = CanExecuteChanged;
                if (onCanExecuteChanged != null)
                {
                    try
                    {
                        onCanExecuteChanged(this, EventArgs.Empty);
                    }
                    catch (Exception e)
                    {
                        System.Windows.MessageBox.Show(e.Message, "Exception in event handling.");
                    }
                }
            }
        }

        public event EventHandler CanExecuteChanged;
    }
}
Richard J Foster
  • 4,278
  • 2
  • 32
  • 41

1 Answers1

8

Change:

onCanExecuteChanged(this, EventArgs.Empty);

to:

Application.Current.Dispatcher.BeginInvoke((Action)(onCanExecuteChanged(this, EventArgs.Empty)));

Edit:

The reason for this is that WPF is listening to these events and attempting to perform actions in UI elements (I.E toggling IsEnabled in a Button), therefore these events must be raised in the UI Thread.

Federico Berasategui
  • 43,562
  • 11
  • 100
  • 154
  • While that unquestionably works, could you clarify why it is necessary? My (clearly incorrect) assumption was that any handlers for any event associated with UI bindings should/would already be checking that they were attempting to perform UI updates on the correct thread. – Richard J Foster Feb 25 '13 at 20:33
  • Yes, it checked that it was on the correct thread and threw an exception letting you know that. – user7116 Feb 25 '13 at 20:58
  • I guess I misphrased that @sixlettervariables (and thanks for the clarification @HighCore) - I understand that the exception is letting me know about the problem. What I don't understand is why this is different from what I perceive as an almost identical scenario of binding "Enabled" to a property of a view model. I haven't experienced any similar problems where it is not a CanExecuteChanged, but an OnPropertyChanged event causing the change. My concern is that I've just been lucky so far and that OnPropertyChange events also need to occur only on the UI thread if used in a binding context. – Richard J Foster Feb 26 '13 at 15:39
  • 2
    The binding occurs on the dispatcher's thread as does the property changed notifications. – user7116 Feb 26 '13 at 19:51