Refresh WPF Command

心不动则不痛 提交于 2019-12-17 15:14:49

问题


Does anyone know how I can force CanExecute to get called on a custom command (Josh Smith's RelayCommand)?

Typically, CanExecute is called whenever interaction occurs on the UI. If I click something, my commands are updated.

I have a situation where the condition for CanExecute is getting turned on/off by a timer behind the scenes. Because this is not driven by user interaction, CanExecute is not called until the user interacts with the UI. The end result is that my Button remains enabled/disabled until the user clicks on it. After the click, it is updated correctly. Sometimes the Button appears enabled, but when the user clicks it changes to disabled instead of firing.

How can I force an update in code when the timer changes the property that affects CanExecute? I tried firing PropertyChanged (INotifyPropertyChanged) on the property that affects CanExecute, but that did not help.

Example XAML:

<Button Content="Button" Command="{Binding Cmd}"/>

Example code behind:

private ICommand m_cmd;
public ICommand Cmd
{
    if (m_cmd == null)
        m_cmd = new RelayCommand(
            (param) => Process(),
            (param) => EnableButton);

    return m_cmd;
}

// Gets updated from a timer (not direct user interaction)
public bool EnableButton { get; set; }

回答1:


CommandManager.InvalidateRequerySuggested()




回答2:


I was aware of CommandManager.InvalidateRequerySuggested() a long time ago, and used it, but it wasn't working for me sometimes. I finally figured out why this was the case! Even though it doesn't throw like some other actions, you HAVE to call it on the main thread.

Calling it on a background thread will appear to work, but sometimes leave the UI disabled. I really hope this helps somebody, and saves them the hours I just wasted.




回答3:


A workaround for that is binding IsEnabled to a property:

<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>

and then implement this property in your ViewModel. This also makes it a bit easier for the UnitTesting to work with the properties rather than commands to see if the command can be executed at a certain point of time.

I, personally, find it more convenient.




回答4:


Probably this variant will suit you:

 public interface IRelayCommand : ICommand
{
    void UpdateCanExecuteState();
}

Implementation:

 public class RelayCommand : IRelayCommand
{
    public event EventHandler CanExecuteChanged;


    readonly Predicate<Object> _canExecute = null;
    readonly Action<Object> _executeAction = null;

   public RelayCommand( Action<object> executeAction,Predicate<Object> canExecute = null)
    {
        _canExecute = canExecute;
        _executeAction = executeAction;
    }


    public bool CanExecute(object parameter)
    {
       if (_canExecute != null)
            return _canExecute(parameter);
        return true;
    }

    public void UpdateCanExecuteState()
    {
        if (CanExecuteChanged != null)
            CanExecuteChanged(this, new EventArgs());
    }



    public void Execute(object parameter)
    {
        if (_executeAction != null)
            _executeAction(parameter);
        UpdateCanExecuteState();
    }
}

Using simple:

public IRelayCommand EditCommand { get; protected set; }
...
EditCommand = new RelayCommand(EditCommandExecuted, CanEditCommandExecuted);

 protected override bool CanEditCommandExecuted(object obj)
    {
        return SelectedItem != null ;
    }

    protected override void EditCommandExecuted(object obj)
    {
        // Do something
    }

   ...

    public TEntity SelectedItem
    {
        get { return _selectedItem; }
        set
        {
            _selectedItem = value;

            //Refresh can execute
            EditCommand.UpdateCanExecuteState();

            RaisePropertyChanged(() => SelectedItem);
        }
    }

XAML:

<Button Content="Edit" Command="{Binding EditCommand}"/>



回答5:


Thanks guys for the tips. Here's a bit of code on how to marshal that call from a BG thread to the UI thread:

private SynchronizationContext syncCtx; // member variable

In the constructor:

syncCtx = SynchronizationContext.Current;

On the background thread, to trigger the requery:

syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );

Hope that helps.

-- Michael




回答6:


To update only a single GalaSoft.MvvmLight.CommandWpf.RelayCommand you could use

mycommand.RaiseCanExecuteChanged();

and for me i've created an Extension method:

public static class ExtensionMethods
    {
        public static void RaiseCanExecuteChangedDispatched(this RelayCommand cmd)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
        }

        public static void RaiseCanExecuteChangedDispatched<T>(this RelayCommand<T> cmd)
        {
            System.Windows.Application.Current.Dispatcher.Invoke(DispatcherPriority.Normal, new Action(() => { cmd.RaiseCanExecuteChanged(); }));
        }
    }


来源:https://stackoverflow.com/questions/783104/refresh-wpf-command

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!