Question

Using a DelegateCommand in WPF I'm facing the following issue:

Executing the Command1 in a background worker prevents the window from refreshing correctly the CanExecute of the buttons, if the command is executed on the UI thread everything works as expected. Clicking outside the button the CanExecute is correcyly refreshed. Any hint?

(Down it's the code of the DelegateCommand, sorry if it's too large. As far as I know this is a common implementation of a DelegateCommand)

XAML:

 <Window x:Class="BackgroundWorkTest.MainWindow"
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     Title="MainWindow" Height="350" Width="525">
    <StackPanel>
          <Button Command="{Binding Command1}" Content="1" />
          <Button Command="{Binding Command2}" Content="2" />
    </StackPanel>
 </Window>

C#:

using System.ComponentModel;
using System.Windows;
using System.Windows.Input;
using Utils.Wpf;

namespace BackgroundWorkTest
{
public partial class MainWindow : Window
{
    private bool _canCommand1 = true;
    private bool _canCommand2 = true;

    private readonly BackgroundWorker _backgroundWorker;

    public ICommand Command1 { get; private set; }
    public ICommand Command2 { get; private set; }

    public MainWindow()
    {
        this.Command1 = new DelegateCommand(this.ExecuteCommand1, this.CanCommand1);
        this.Command2 = new DelegateCommand(this.ExecuteCommand2, this.CanCommand2);

        _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.DoWork += _backgroundWorker_DoWork;
        _backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;

        InitializeComponent();

        this.DataContext = this;
    }

    private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        System.Threading.Thread.Sleep(2000);
    }

    private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        System.Threading.Thread.Sleep(2000);
        _canCommand2 = true;
        _canCommand1 = false;
    }

    private bool CanCommand2()
    {
        return _canCommand2;
    }

    private void ExecuteCommand2()
    {
        _canCommand2 = false;
        _canCommand1 = true;
    }

    private bool CanCommand1()
    {
        return _canCommand1;
    }

    private void ExecuteCommand1()
    {
        _backgroundWorker.RunWorkerAsync();
    }
}

}

DelegateCommand C#:

    public class DelegateCommand : ICommand
{
    #region Constructors

    /// <summary>
    ///     Constructor
    /// </summary>
    public DelegateCommand(Action executeMethod)
        : this(executeMethod, null, false)
    {
    }

    /// <summary>
    ///     Constructor
    /// </summary>
    public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod)
        : this(executeMethod, canExecuteMethod, false)
    {
    }

    /// <summary>
    ///     Constructor
    /// </summary>
    public DelegateCommand(Action executeMethod, Func<bool> canExecuteMethod, bool isAutomaticRequeryDisabled)
    {
        if (executeMethod == null)
        {
            throw new ArgumentNullException("executeMethod");
        }

        this._executeMethod = executeMethod;
        this._canExecuteMethod = canExecuteMethod;
        this._isAutomaticRequeryDisabled = isAutomaticRequeryDisabled;
    }

    #endregion

    #region Public Methods

    /// <summary>
    ///     Method to determine if the command can be executed
    /// </summary>
    public bool CanExecute()
    {
        if (this._canExecuteMethod != null)
        {
            return this._canExecuteMethod();
        }
        return true;
    }

    /// <summary>
    ///     Execution of the command
    /// </summary>
    public void Execute()
    {
        if (this._executeMethod != null)
        {
            this._executeMethod();
        }
    }

    /// <summary>
    ///     Property to enable or disable CommandManager's automatic requery on this command
    /// </summary>
    public bool IsAutomaticRequeryDisabled
    {
        get
        {
            return this._isAutomaticRequeryDisabled;
        }
        set
        {
            if (this._isAutomaticRequeryDisabled != value)
            {
                if (value)
                {
                    CommandManagerHelper.RemoveHandlersFromRequerySuggested(this._canExecuteChangedHandlers);
                }
                else
                {
                    CommandManagerHelper.AddHandlersToRequerySuggested(this._canExecuteChangedHandlers);
                }
                this._isAutomaticRequeryDisabled = value;
            }
        }
    }

    /// <summary>
    ///     Raises the CanExecuteChaged event
    /// </summary>
    public void RaiseCanExecuteChanged()
    {
        this.OnCanExecuteChanged();
    }

    /// <summary>
    ///     Protected virtual method to raise CanExecuteChanged event
    /// </summary>
    protected virtual void OnCanExecuteChanged()
    {
        CommandManagerHelper.CallWeakReferenceHandlers(this._canExecuteChangedHandlers);
    }

    #endregion

    #region ICommand Members

    /// <summary>
    ///     ICommand.CanExecuteChanged implementation
    /// </summary>
    public event EventHandler CanExecuteChanged
    {
        add
        {
            if (!this._isAutomaticRequeryDisabled)
            {
                CommandManager.RequerySuggested += value;
            }
            CommandManagerHelper.AddWeakReferenceHandler(ref this._canExecuteChangedHandlers, value, 2);
        }
        remove
        {
            if (!this._isAutomaticRequeryDisabled)
            {
                CommandManager.RequerySuggested -= value;
            }
            CommandManagerHelper.RemoveWeakReferenceHandler(this._canExecuteChangedHandlers, value);
        }
    }

    bool ICommand.CanExecute(object parameter)
    {
        return this.CanExecute();
    }

    void ICommand.Execute(object parameter)
    {
        this.Execute();
    }

    #endregion

    #region Data

    private readonly Action _executeMethod;
    private readonly Func<bool> _canExecuteMethod;
    private bool _isAutomaticRequeryDisabled;
    private List<WeakReference> _canExecuteChangedHandlers;

    #endregion
}

And the CommandManager:

    internal class CommandManagerHelper
{
    internal static void CallWeakReferenceHandlers(List<WeakReference> handlers)
    {
        if (handlers != null)
        {
            // Take a snapshot of the handlers before we call out to them since the handlers
            // could cause the array to me modified while we are reading it.

            EventHandler[] callees = new EventHandler[handlers.Count];
            int count = 0;

            for (int i = handlers.Count - 1; i >= 0; i--)
            {
                WeakReference reference = handlers[i];
                EventHandler handler = reference.Target as EventHandler;
                if (handler == null)
                {
                    // Clean up old handlers that have been collected
                    handlers.RemoveAt(i);
                }
                else
                {
                    callees[count] = handler;
                    count++;
                }
            }

            // Call the handlers that we snapshotted
            for (int i = 0; i < count; i++)
            {
                EventHandler handler = callees[i];
                handler(null, EventArgs.Empty);
            }
        }
    }

    internal static void AddHandlersToRequerySuggested(List<WeakReference> handlers)
    {
        if (handlers != null)
        {
            foreach (WeakReference handlerRef in handlers)
            {
                EventHandler handler = handlerRef.Target as EventHandler;
                if (handler != null)
                {
                    CommandManager.RequerySuggested += handler;
                }
            }
        }
    }

    internal static void RemoveHandlersFromRequerySuggested(List<WeakReference> handlers)
    {
        if (handlers != null)
        {
            foreach (WeakReference handlerRef in handlers)
            {
                EventHandler handler = handlerRef.Target as EventHandler;
                if (handler != null)
                {
                    CommandManager.RequerySuggested -= handler;
                }
            }
        }
    }

    internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler)
    {
        AddWeakReferenceHandler(ref handlers, handler, -1);
    }

    internal static void AddWeakReferenceHandler(ref List<WeakReference> handlers, EventHandler handler, int defaultListSize)
    {
        if (handlers == null)
        {
            handlers = (defaultListSize > 0 ? new List<WeakReference>(defaultListSize) : new List<WeakReference>());
        }

        handlers.Add(new WeakReference(handler));
    }

    internal static void RemoveWeakReferenceHandler(List<WeakReference> handlers, EventHandler handler)
    {
        if (handlers != null)
        {
            for (int i = handlers.Count - 1; i >= 0; i--)
            {
                WeakReference reference = handlers[i];
                EventHandler existingHandler = reference.Target as EventHandler;
                if ((existingHandler == null) || (existingHandler == handler))
                {
                    // Clean up old handlers that have been collected
                    // in addition to the handler that is to be removed.
                    handlers.RemoveAt(i);
                }
            }
        }
    }
}
Was it helpful?

Solution

I have fixed this by calling CommandManager.InvalidateRequerySuggested() in the _backgroundWorker_RunWorkerCompleted method. I think this is somehow related to the fact that the CanExecuteChanged event is being fired outside the UI Thread. If you are working with a single Dispatcher, you could try changing your ICommand implementation to marshal the calls to CanExecuteChanged to the dispatcher

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top