奇怪的行为与按钮,命令和canExecute
本文关键字:命令 canExecute 按钮 | 更新日期: 2023-09-27 18:02:04
我有一个简单的按钮:
<Button Content="Print" Command="{Binding PrintCommand}"/>
…使用命令:
private RelayCommand _printCommand;
public ICommand PrintCommand
{
get
{
if (_printCommand == null)
{
_printCommand = new RelayCommand(param => Print(),
() => (Files != null && Files.Count > 0));
}
return _printCommand;
}
}
仅当Files
集合不为空或包含某些项时才启用。以下是集合:
private ObservableCollection<RecordModel> _files;
public ObservableCollection<RecordModel> Files
{
get { return _files; }
set
{
if (_files == value)
{
return;
}
_files = value;
OnPropertyChanged("Files");
}
}
集合被绑定到窗口中的ListView
。到目前为止,没什么特别的……这就是奇怪行为的由来……
如果集合中有足够的物品可以显示ListView
的ScrollBar
,那么我的按钮显示为Enabled
,这很好。如果我没有物品,那么它是Disabled
,这也很好。但是,如果我有足够的项目来填充可见ListView
的一部分,而不触发ScrollBar
的出现,那么我的按钮显示为Disabled
。如果我专注于任何控件,包括按钮本身,那么它会弹出Enabled
。我不知道发生了什么。一开始,我以为这可能是我正在使用的按钮模板,所以我去掉了它,并保留了默认设置的按钮,但奇怪的行为仍然存在。
知道是怎么回事吗?
这是RelayCommand
类。我不确定是否有问题,但这就是我已经使用了一段时间:
public class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Func<bool> _canExecute;
public RelayCommand(Action<object> execute) : this(execute, null) { }
public RelayCommand(Action<object> execute, Func<bool> canExecute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
编辑:这是我如何填充我的集合。
public FileManagerViewModel()
{
LoadCollection();
}
private void LoadCollection()
{
Task task = new Task(() =>
{
Files = DbWorker.GetFiles();
});
task.Start();
}
下面是我如何将集合绑定到ListView
:
<Window.DataContext>
<vm:FileManagerViewModel/>
</Window.DataContext>
<Window.Resources>
<CollectionViewSource Source="{Binding Files}" x:Key="GroupedFiles">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="RepNum"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
</Window.Resources>
<ListView ItemsSource="{Binding Source={StaticResource GroupedFiles}}">
...
</ListView
编辑:嗯,我不知道这是不是它或者它是如何导致的(特别是整个ScrollBar
的情况),但是当我不使用Task
来更新我的集合时,我没有经历过这种行为。当然,我还得处理由于长时间手术而导致的绞刑。不知道如何补救这一点,考虑到我不想阻塞UI线程。我甚至试过这个,它没有改变任何东西:
var temp = new ObservableCollection<RecordModel>();
Task task = new Task(() =>
{
temp = DbWorker.GetFiles();
});
task.ContinueWith((result) =>
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
Files = temp;
}));
});
task.Start();
但是我没有看到属性没有更新的问题。我已经检查并根据需要更新了所有属性。这只是CanExecute
状态更新中的一些小挂起,仅通过焦点更改更新(在此场景中)。
根据我所知道的,似乎线程和命令之间存在问题…嗯。每次我手动给UI元素一个焦点,点击,命令更新(或出现)。如果滚动条出现或消失,也会发生这种情况。但是其他UI元素不做任何事情,比如text.
嗯,正如我所看到的,你的命令只是不知道它与Files
收集有关。所以Files
被改变了,它的setter被调用,你的ListView
被更新了,因为它被绑定到那个集合并对PropertyChanged做出反应。但命令并不直接与它绑定,所以它只是坐在那里不受干扰,喝咖啡或其他什么。只有当UI开始改变时,系统才会调用CanExecute并开始工作。所以我认为这里的简单解决方案是将Files
绑定到command。
你可以直接在setter中设置(因为你知道命令依赖于集合):
public ObservableCollection<RecordModel> Files
{
get { return _files; }
set
{
if (_files == value) return;
_files = value;
OnPropertyChanged("Files");
CommandManager.InvalidateRequerySuggested();
}
}
或者你可以在加载集合时这样做(例如,如果你知道这是唯一影响集合大小的操作):
task.ContinueWith((result) =>
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
new Action(() =>
{
Files = temp;
CommandManager.InvalidateRequerySuggested();
}));
});
注意:我使用InvalidateRequerySuggested
作为使命令无效的最简单方法,但它有点过度。其他ICommand
实现使用不同的技术来做到这一点(例如Telerik的DelegateCommand有自定义的InvalidateCanExecute
方法)。
我将无法评论为什么这个设计不工作,因为从提供的代码中不清楚您的命令如何处理启用按钮。但我的做法如下:在我的视图模型类中,我将有一个公共属性isprintalallowed。
private bool _isPrintAllowed;
public bool IsPrintAllowed{
get{ return _isPrintAllowed;}
set{_isPrintAllowed = value;
RaisePropertyChanged(() => IsPrintAllowed)}
Files集合的CollectionChanged事件将计算isprintalallowed Property。
Files.CollectionChanged += EvaluateIsPrintAllowed;
private void EvaluateIsPrintAllowed()
{
IsPrintAllowed = Files != null && Files.Count > 0;
}
在xaml中,我将把按钮的IsEnabled属性绑定到isprintalallowed。
<Button Content="Print" Command="{Binding PrintCommand}" IsEnabled = {Binding IsPrintAllowed}/>