c# - 如何让 DelegateCommand CanExecute 使用 MVVM 更新 WPF UI?
问题描述
解决这个问题,我试图用两个按钮制作一个 UI。按下一个按钮会禁用自身并启用另一个按钮。我正在使用 MVVM 模式和 DelegateCommand:
主窗口 XAML
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel>
<Rectangle Height="100"></Rectangle>
<Button Command="{Binding StartServer}" >Start Server</Button>
<Button Command="{Binding StopServer}" >Stop Server</Button>
</StackPanel>
</Window>
视图模型
public class ViewModel : INotifyPropertyChanged
{
public ViewModel()
{
PropertyChanged += PropertyChangedHandler;
}
private void PropertyChangedHandler(object sender, PropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(ServerIsRunning):
StartServer.RaiseCanExecuteChanged();
StopServer.RaiseCanExecuteChanged();
break;
}
}
private bool m_serverIsRunning;
public bool ServerIsRunning
{
get => m_serverIsRunning;
set
{
m_serverIsRunning = value;
RaisePropertyChanged(nameof(ServerIsRunning));
}
}
public DelegateCommand<object> StartServer => new DelegateCommand<object>(
context =>
{
ServerIsRunning = true;
}, e => !ServerIsRunning);
public DelegateCommand<object> StopServer => new DelegateCommand<object>(
context =>
{
ServerIsRunning = false;
}, e => ServerIsRunning);
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
}
委托命令
public class DelegateCommand<T> : ICommand
{
private readonly Action<object> ExecuteAction;
private readonly Func<object, bool> CanExecuteFunc;
public DelegateCommand(Action<object> executeAction, Func<object, bool> canExecuteFunc)
{
ExecuteAction = executeAction;
CanExecuteFunc = canExecuteFunc;
}
public bool CanExecute(object parameter)
{
return CanExecuteFunc == null || CanExecuteFunc(parameter);
}
public void Execute(object parameter)
{
ExecuteAction(parameter);
}
public event EventHandler CanExecuteChanged;
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, EventArgs.Empty);
}
}
问题是,当我单击Start Server
按钮时,按钮的 IsEnabled 状态在 UI 中没有改变。我是否不正确地使用了 DelegateCommand?
解决方案
您的代码的问题在于您的命令属性使用表达式主体,每次有人获取属性时都会执行它们,这意味着每次您请求StartServer
命令时,它都会为您提供一个新实例。您可以通过在 getter 上放置一个断点来自己检查这一点,您会看到它会被多次命中,并且总是会创建一个新的委托命令实例。
为了使您的代码工作,您必须使用每个命令的一个实例,您可以通过使用支持字段或只读属性并将其设置在构造函数中来实现这一点:
public MainWindowViewModel()
{
PropertyChanged += PropertyChangedHandler;
StartServer = new DelegateCommand<object>(
context =>
{
ServerIsRunning = true;
}, e => !ServerIsRunning);
StopServer = new DelegateCommand<object>(
context => {
ServerIsRunning = false;
}, e => ServerIsRunning);
}
public DelegateCommand<object> StartServer
{ get; }
public DelegateCommand<object> StopServer
{ get; }
UI 只会监听CanExecuteChanged
它所绑定的命令实例引发的事件(通过Command={Binding ...}
.
您的代码也可以简化,因为您可以RaiseCanExecuteChanged
直接调用而不是提高 aPropertyChangedEvent
并自己听:
public bool ServerIsRunning
{
get => m_serverIsRunning;
set
{
m_serverIsRunning = value;
StartServer.RaiseCanExecuteChanged();
StopServer.RaiseCanExecuteChanged();
}
}
推荐阅读
- python - 如何在类内部但在方法外部引用python类变量?
- android - 在 Android 中通过 Retrofit 库使用 API
- react-native - 如何动态分配每个按钮以在单击时更改颜色
- ios - 即使禁用 contentInset Xcode 9.4.1,UITableView 仍然在顶部留下空间
- ios - 无法将类型值转换为 UINavigationController swift3
- ios - iOS 12 Siri 快捷方式与 plist
- javascript - Google Maps Javascript 跨域脚本标签支持
- javascript - PHP:如何正确地回显类和 id 以供 jQuery 呈现
- java - 添加或删除 SynchronizedMap 和同步块
- javascript - Typescript/lodash:如何获取具有多个公共属性的结果/对象