有谁知道我如何强制
CanExecute
被调用自定义命令(Josh Smith 的 RelayCommand
)?
通常,只要在 UI 上发生交互,就会调用
CanExecute
。如果我单击某些东西,我的命令就会更新。
我有一种情况,即
CanExecute
的条件被幕后的计时器打开/关闭。因为这不是由用户交互驱动的,所以 CanExecute
在用户与 UI 交互之前不会被调用。最终结果是我的 Button
保持启用/禁用状态,直到用户单击它为止。单击后,它会正确更新。有时 Button
显示为已启用,但当用户单击时它会变为禁用而不是触发。
当计时器更改影响
CanExecute
的属性时,如何强制更新代码?我尝试在影响PropertyChanged
的属性上发射INotifyPropertyChanged
(CanExecute
),但这没有帮助。
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; }
System.Windows.Input.CommandManager.InvalidateRequerySuggested()
强制 CommandManager 引发 RequerySuggested 事件。
备注: CommandManager 在确定命令目标何时发生变化时只关注某些条件,例如键盘焦点的变化。在 CommandManager 没有充分确定导致命令无法执行的条件变化的情况下,可以调用 InvalidateRequerySuggested 以强制 CommandManager 引发 RequerySuggested 事件。
很久以前我就知道 CommandManager.InvalidateRequerySuggested() 并使用过它,但有时它对我不起作用。我终于明白为什么会这样了!即使它不像其他一些动作那样抛出,您也必须在主线程上调用它。
在后台线程上调用它似乎可以工作,但有时会使 UI 处于禁用状态。我真的希望这对某人有所帮助,并为他们节省我刚刚浪费的时间。
解决方法是将
IsEnabled
绑定到属性:
<Button Content="Button" Command="{Binding Cmd}" IsEnabled="{Binding Path=IsCommandEnabled}"/>
然后在您的 ViewModel 中实现此属性。这也使得单元测试更容易使用属性而不是命令来查看命令是否可以在特定时间点执行。
我个人觉得比较方便。
这个变体可能适合你:
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}"/>
谢谢大家的提示。下面是一些关于如何将该调用从 BG 线程编组到 UI 线程的代码:
private SynchronizationContext syncCtx; // member variable
在构造函数中:
syncCtx = SynchronizationContext.Current;
在后台线程上,触发重新查询:
syncCtx.Post( delegate { CommandManager.InvalidateRequerySuggested(); }, null );
希望有帮助。
--迈克尔
要仅更新单个 GalaSoft.MvvmLight.CommandWpf.RelayCommand,您可以使用
mycommand.RaiseCanExecuteChanged();
对我来说,我创建了一个扩展方法:
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(); }));
}
}
没有看到这里提到的所以在 2023 年添加。
我通过 NuGet 安装 Prism 库解决了这个问题(VS 建议我使用“DelegateCommand”)并使用
DelegateCommand
而不是自己的 RelayCommand
实现。然后我就打电话给command.RaiseCanExecuteChanged()
,就像我打电话给OnPropertyChanged(propName)
一样。对我很有用。