在this question的基础上,我试图用两个按钮制作一个UI。按下一个按钮将禁用自身并启用另一个按钮。我正在使用MVVM模式和DelegateCommand:
MainWindow 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>
ViewModel
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));
}
}
DelegateCommand
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吗?
答案 0 :(得分:3)
代码的问题是您的命令属性使用表达式主体,它们在有人获取属性时执行,这意味着每次您请求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; }
用户界面将仅侦听绑定到命令实例的CanExecuteChanged
事件(通过Command={Binding ...}
。
您的代码也可以简化,因为您可以直接调用RaiseCanExecuteChanged
而不是引发PropertyChangedEvent
并听自己说:
public bool ServerIsRunning
{
get => m_serverIsRunning;
set
{
m_serverIsRunning = value;
StartServer.RaiseCanExecuteChanged();
StopServer.RaiseCanExecuteChanged();
}
}
答案 1 :(得分:2)
像这样吗?
public class ViewModel : INotifyPropertyChanged
{
bool _isServerStarted;
public ViewModel()
{
StartServer = new DelegateCommand(OnStartServerExecute, OnStartServerCanExecute);
StopServer = new DelegateCommand(OnStopServerExecute, OnStopServerCanExecute);
}
void OnStartServerExecute()
{
IsServerStarted = true;
}
bool OnStartServerCanExecute()
{
return !IsServerStarted;
}
void OnStopServerExecute()
{
IsServerStarted = false;
}
bool OnStopServerCanExecute()
{
return IsServerStarted;
}
void RaiseCanExecuteChanged()
{
StartServer.RaiseCanExecuteChanged();
StopServer.RaiseCanExecuteChanged();
}
public bool IsServerStarted
{
get => _isServerStarted;
set
{
_isServerStarted = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsServerStarted)));
RaiseCanExecuteChanged();
}
}
public DelegateCommand StartServer { get; }
public DelegateCommand StopServer { get; }
public event PropertyChangedEventHandler PropertyChanged;
}
您只应使用CommandManager.InvalidateRequerySuggested()来更新按钮状态。