刷新WPF命令


72

有谁知道我可以如何强制CanExecute使用自定义命令(乔什·史密斯的RelayCommand)?

通常,CanExecute只要在UI上发生交互,就会调用。如果单击某些内容,则命令将更新。

我遇到的情况CanExecute是幕后的计时器会打开/关闭条件。因为这不是由用户交互驱动的,CanExecute所以直到用户与UI交互才被调用。最终结果是我Button保持启用/禁用状态,直到用户单击它为止。单击后,它会正确更新。有时Button显示为已启用,但是当用户单击时,它变为禁用而不是触发。

当计时器更改影响的属性时,如何强制代码更新CanExecute?我尝试在影响的属性上触发PropertyChangedINotifyPropertyChangedCanExecute,但这并没有帮助。

XAML示例:

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

后面的示例代码:

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; }

您是否尝试为命令提高INotifyPropertyChanged?您不需要Command的字段,只需每次返回一个新字段。这种组合应该有效。或者仅在需要强制的情况下创建新的Command。
egaga

Answers:


100

调用会System.Windows.Input.CommandManager.InvalidateRequerySuggested()强制CommandManager引发RequerySuggested事件。

备注: CommandManager在确定命令目标何时更改时仅注意某些条件,例如键盘焦点的更改。在CommandManager不能充分确定导致命令无法执行的条件变化的情况下,可以调用InvalidateRequerySuggested来强制CommandManager引发RequerySuggested事件。


1
您是否建议从ViewModel类调用此方法?
乔什·G

3
不一定,因为那样会使您的课程难以测试。尝试一下,如有必要,将其移入服务。另一个选择是在RelayCommand中添加一个方法,该方法允许您仅针对该命令引发CanExecuteChanged(CommandManager.InvalidRequerySuggested会使所有命令无效,这有点过头了)。
肯特·布加阿特

25
有趣的是...它可以工作,但是必须在UI线程上调用。我不惊讶。
乔什·G

4
我使用了MVVM-Light Messenger,并创建了一个简单的RefreshCommandStatus消息,我的ViewModel现在可以将其发送出去。主窗口侦听此消息并调用CommandManager.InvalidateRequerySuggest()
cordialgerm 2011年

5
必须在UI线程上运行,所以也可能会这样:UI.Dispatcher.Invoke(() => { CommandManager.InvalidateRequerySuggested(); });
Fandi Susanto

29

我很早以前就知道CommandManager.InvalidateRequerySuggested()并使用了它,但是有时它对我不起作用。我终于弄清楚了为什么会这样!即使它不像其他动作那样抛出,您也必须在主线程上调用它。

在后台线程上调用它似乎可以工作,但有时会禁用UI。我真的希望这对某人有帮助,并为他们节省了我刚刚浪费的时间。


17

一种解决方法是绑定IsEnabled到属性:

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

然后在您的ViewModel中实现此属性。这也使UnitTesting可以更轻松地使用属性而不是命令来查看命令是否可以在特定时间执行。

我个人觉得比较方便。


如何刷新IsEnabled?
visc

我会避免使所有事情失效,这是一个更好,更轻松的解决方案
Asheh 2016年

#PersistentWPFIssues12YearAnniversary #NoWonderEverybodyFledToJS
Christoph Wolf

6

此变体可能适合您:

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

实现方式:

 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();
    }
}

使用简单:

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}"/>

1
这不是理想的,因为它会创建对处理程序的强大引用,从而导致内存泄漏。
BartoszWójtowicz15年

4

谢谢大家的提示。这是一些有关如何封送从BG线程到UI线程的调用的代码:

private SynchronizationContext syncCtx; // member variable

在构造函数中:

syncCtx = SynchronizationContext.Current;

在后台线程上,触发重新查询:

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

希望能有所帮助。

-迈克尔


3
似乎最好叫Dispatcher.BeginInvoke()
Josh G

嗨,乔希。也许会更好。在内部,Dispatcher.BeginInvoke()使用SynchronizationContextSwitcher类,该类仍然委托给SynchronizationContext ...
Michael Kennedy

0

要仅更新一个GalaSoft.MvvmLight.CommandWpf.RelayCommand,可以使用

mycommand.RaiseCanExecuteChanged();

对我来说,我创建了一个Extension方法:

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(); }));
        }
    }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.