如何使用 Prism 6 正确触发 CanExecute
How to make the CanExecute trigger properly using Prism 6
我有模特
public class Irritant : BindableBase
{
private short _id;
private string _name;
private string _description;
public short Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public string Description
{
get { return _description; }
set { SetProperty(ref _description, value); }
}
public Irritant()
{
Id = 0;
Name = "";
Description = "";
}
}
然后我的 ViewModel 有两个版本
public class IrritantViewModel : BindableBase
{
private IrritantDb db = new IrritantDb();
//Version 1 - The Model's property is coded in IrritantViewModel
//private short _id;
//private string _name = "Alen";
//private string _description;
//public short Id
//{
// get { return _id; }
// set { SetProperty(ref _id, value); }
//}
//public string Name
//{
// get { return _name; }
// set { SetProperty(ref _name, value); }
//}
//public string Description
//{
// get { return _description; }
// set { SetProperty(ref _description, value); }
//}
//Version2 - I use the Irritant Model as property of IrritantViewModel
private DateTime? _lastUpdated;
private Irritant _entity;
public Irritant Entity
{
get { return _entity; }
set { SetProperty(ref _entity, value); }
}
public DateTime? LastUpdated
{
get { return _lastUpdated; }
set { SetProperty(ref _lastUpdated, value); }
}
public DelegateCommand UpdateCommand { get; set; }
public IrritantViewModel()
{
Entity = new Irritant();
//Version1
//UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Name);
//Version2
UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Entity.Name);
}
private bool CanExecute()
{
//Version1
//switch (Name)
//{
// case null:
// return false;
// case "":
// return false;
//}
//Version2
switch (Entity.Name)
{
case null:
return false;
case "":
return false;
}
return true;
}
private void EditCommand()
{
LastUpdated = DateTime.UtcNow;
}
}
这是我的观点
public partial class IrritantView : UserControl
{
public IrritantView()
{
InitializeComponent();
DataContext = new IrritantViewModel();
}
}
<Grid >
<ScrollViewer>
<StackPanel MinWidth="200">
<TextBlock Text="Irritant" />
<!--Version 1-->
<!--<TextBlock Text="Name" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Description" />
<TextBox Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" />
-->
<!--Version 2-->
<TextBlock Text="Name" />
<TextBox Text="{Binding Entity.Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Description" />
<TextBox Text="{Binding Entity.Description, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Last Updated" />
<Label Content="{Binding LastUpdated, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Save"
Command="{Binding UpdateCommand}"
/>
</StackPanel>
</ScrollViewer>
</Grid>
Version1 工作正常,当绑定到 Name (TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}") 的 TextBox 为 null 或空时,保存按钮将禁用。
但是对于版本 2,保存按钮不会禁用。它仅在初始化期间调用 CanExecute 方法,删除 TextBox 中的文本不会禁用 Button。我做错了什么?
DelegateCommand
不会自动引发 CanExecuteChanged
事件,您必须在适当的时候通过调用 RaiseCanExecuteChanged
手动引发该事件。除了使用 DelegateCommand
,您还可以使用 RelayCommand
,它依赖 CommandManager.RequerySuggested
事件为您做类似的事情。
更改命令定义返回 ICommand
:
public ICommand UpdateCommand { get; set; }
使用以下命令初始化命令:
UpdateCommand = new AutoCanExecuteCommand(new DelegateCommand(EditCommand, CanExecute));
使用以下 class 作为包装器:
public class AutoCanExecuteCommand : ICommand
{
public ICommand WrappedCommand { get; private set; }
public AutoCanExecuteCommand(ICommand wrappedCommand)
{
if (wrappedCommand == null)
{
throw new ArgumentNullException("wrappedCommand");
}
WrappedCommand = wrappedCommand;
}
public void Execute(object parameter)
{
WrappedCommand.Execute(parameter);
}
public bool CanExecute(object parameter)
{
return WrappedCommand.CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
出于多种原因,我不建议连接到 CommandManager。除了内存泄漏之外,它还会在您的应用程序中引入性能问题,因为您无法控制 CommandManager 调用 CanExecute 的时间或次数(发生在 UI 线程上)。相反,我会建议使用您的模型对象的 INPC,而不是如这个答案所示:
我有模特
public class Irritant : BindableBase
{
private short _id;
private string _name;
private string _description;
public short Id
{
get { return _id; }
set { SetProperty(ref _id, value); }
}
public string Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
public string Description
{
get { return _description; }
set { SetProperty(ref _description, value); }
}
public Irritant()
{
Id = 0;
Name = "";
Description = "";
}
}
然后我的 ViewModel 有两个版本
public class IrritantViewModel : BindableBase
{
private IrritantDb db = new IrritantDb();
//Version 1 - The Model's property is coded in IrritantViewModel
//private short _id;
//private string _name = "Alen";
//private string _description;
//public short Id
//{
// get { return _id; }
// set { SetProperty(ref _id, value); }
//}
//public string Name
//{
// get { return _name; }
// set { SetProperty(ref _name, value); }
//}
//public string Description
//{
// get { return _description; }
// set { SetProperty(ref _description, value); }
//}
//Version2 - I use the Irritant Model as property of IrritantViewModel
private DateTime? _lastUpdated;
private Irritant _entity;
public Irritant Entity
{
get { return _entity; }
set { SetProperty(ref _entity, value); }
}
public DateTime? LastUpdated
{
get { return _lastUpdated; }
set { SetProperty(ref _lastUpdated, value); }
}
public DelegateCommand UpdateCommand { get; set; }
public IrritantViewModel()
{
Entity = new Irritant();
//Version1
//UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Name);
//Version2
UpdateCommand = new DelegateCommand(EditCommand, CanExecute).ObservesProperty(() => Entity.Name);
}
private bool CanExecute()
{
//Version1
//switch (Name)
//{
// case null:
// return false;
// case "":
// return false;
//}
//Version2
switch (Entity.Name)
{
case null:
return false;
case "":
return false;
}
return true;
}
private void EditCommand()
{
LastUpdated = DateTime.UtcNow;
}
}
这是我的观点
public partial class IrritantView : UserControl
{
public IrritantView()
{
InitializeComponent();
DataContext = new IrritantViewModel();
}
}
<Grid >
<ScrollViewer>
<StackPanel MinWidth="200">
<TextBlock Text="Irritant" />
<!--Version 1-->
<!--<TextBlock Text="Name" />
<TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Description" />
<TextBox Text="{Binding Description, UpdateSourceTrigger=PropertyChanged}" />
-->
<!--Version 2-->
<TextBlock Text="Name" />
<TextBox Text="{Binding Entity.Name, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Description" />
<TextBox Text="{Binding Entity.Description, UpdateSourceTrigger=PropertyChanged}" />
<TextBlock Text="Last Updated" />
<Label Content="{Binding LastUpdated, UpdateSourceTrigger=PropertyChanged}" />
<Button Content="Save"
Command="{Binding UpdateCommand}"
/>
</StackPanel>
</ScrollViewer>
</Grid>
Version1 工作正常,当绑定到 Name (TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}") 的 TextBox 为 null 或空时,保存按钮将禁用。
但是对于版本 2,保存按钮不会禁用。它仅在初始化期间调用 CanExecute 方法,删除 TextBox 中的文本不会禁用 Button。我做错了什么?
DelegateCommand
不会自动引发 CanExecuteChanged
事件,您必须在适当的时候通过调用 RaiseCanExecuteChanged
手动引发该事件。除了使用 DelegateCommand
,您还可以使用 RelayCommand
,它依赖 CommandManager.RequerySuggested
事件为您做类似的事情。
更改命令定义返回
ICommand
:public ICommand UpdateCommand { get; set; }
使用以下命令初始化命令:
UpdateCommand = new AutoCanExecuteCommand(new DelegateCommand(EditCommand, CanExecute));
使用以下 class 作为包装器:
public class AutoCanExecuteCommand : ICommand { public ICommand WrappedCommand { get; private set; } public AutoCanExecuteCommand(ICommand wrappedCommand) { if (wrappedCommand == null) { throw new ArgumentNullException("wrappedCommand"); } WrappedCommand = wrappedCommand; } public void Execute(object parameter) { WrappedCommand.Execute(parameter); } public bool CanExecute(object parameter) { return WrappedCommand.CanExecute(parameter); } public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } } }
出于多种原因,我不建议连接到 CommandManager。除了内存泄漏之外,它还会在您的应用程序中引入性能问题,因为您无法控制 CommandManager 调用 CanExecute 的时间或次数(发生在 UI 线程上)。相反,我会建议使用您的模型对象的 INPC,而不是如这个答案所示: