关闭 ShowDialog 后,视图上的中继命令仍在评估中

本文关键字:命令 评估 ShowDialog 视图 关闭 | 更新日期: 2023-09-27 18:37:25

(最接近的相关问题)

我执行调用LogInView.ShowDialog() LogInRequest()。该视图有一个名为 VerifyLogInCommand 的命令。执行命令时,它完成并显示关闭对话框的this.CloseAction()。但是,该视图命令的 CanExecute 方法VerifyLogInCanExecute中的断点在对话框关闭(不间断)后仍然被命中。我尝试在调用 ShowDialog 后将视图设置为 null,但没有更改。

为什么当窗口关闭/null时,仍在评估命令/CanExecute?

LogInView.xaml.cs

public LogInOutView()
{
    InitializeComponent();
    // Data context
    IModule existingVM = SessionViewModel.Instance.ModulesOpen.Single(mod => mod.ModuleName == "LogIn");
    LogInViewModel livm = (LogInViewModel)existingVM;
    this.DataContext = livm;
    // Cancel Handler
    livm.CloseAction = new Action(() => this.Close());
}

登录视图模型.cs

public Action CloseAction { get; set; }
private RelayCommand verifyLogInCommand;
public RelayCommand VerifyLogInCommand
{
  get
  {
    if (verifyLogInCommand == null)
    {
      verifyLogInCommand = new RelayCommand(
        param => VerifyLogInExecute(),
        param => VerifyLogInCanExecute);
    }
    return verifyLogInCommand;
  }
}
public void VerifyLogInExecute()
{
  // Validate Login
  Employee employee = ValidateLogin(Password);
  // Clear password field
  ResetExecute();
  // Return false if invalid login
  if (employee == null)
  {
    Result = LogInOutDialogResults.Cancel;
    ConfirmationView c = new ConfirmationView("Invalid Login!");
    c.ShowDialog();
    return;
  }
  // Set Result to LogIn status
  Result = LogInOutDialogResults.LogIn;
  // Set LastAuthorizedEmployee
  SessionViewModel.Instance.LastAuthorizedEmployee = employee;
  // Close View to go back where it was called
  this.CloseAction();
}
public bool VerifyLogInCanExecute
{
  get
  {
    // Password length restrictions
    if (!CheckRequiredPasswordLength(Password)) { return false; }
    return true;
  }
}
public static LogInOutDialogResults LogInRequest()
{
  // Show Login View
  LogInOutDialogResults LogInOutResult = LogInOutDialogResults.Cancel;
  LogInOutView LogInOutView = new LogInOutView()
  {
    Title = "Log In",
    ShowInTaskbar = false,
    Topmost = true,
    ResizeMode = ResizeMode.NoResize,
    Owner = SessionViewModel.Instance.ProfitPOSView
  };
  LogInOutView.ShowDialog();
  LogInOutResult = ((LogInViewModel)LogInOutView.DataContext).Result;
  // LogIn
  if (LogInOutResult == LogInOutDialogResults.LogIn)
  {
    LogInOutView = null;
    return LogInOutDialogResults.LogIn;
  }
}

关闭 ShowDialog 后,视图上的中继命令仍在评估中

如果您使用的是 MvvmLight 中的RelayCommand,它会通过将订阅转发到 CommandManager.RequerySuggested 来实现其CanExecuteChanged事件。 这有效地允许RelayCommandRoutedCommand在 WPF 中那样更新自己的状态;RequerySuggested事件在特定条件下触发,包括每次焦点更改或窗口被(取消)激活时。 RequerySuggested事件使用弱事件处理程序来缓解泄漏的订阅,但 WPF 使用的弱事件实现在清理自身之后并不是非常勤奋,因此订阅可能仍保持活动状态一段时间(甚至可能无限期)。

CanExecute回调似乎正在不间断地重新评估,因为每次遇到断点时,Visual Studio 都会从应用程序中窃取焦点,当您点击"恢复"时,应用程序将重新激活,从而触发RequerySuggested事件并导致重新评估CanExecute。 反过来,这会再次触发断点,并且您将陷入一个循环,直到禁用断点。

如果您的视图模型知道其关闭状态,我会将您的VerifyLogInCanExecute属性更改为以下内容:

public bool VerifyLogInCanExecute
{
    get { return !IsClosed && CheckRequiredPasswordLength(Password); }
}

至少那时你不会做超过必要的工作。 另一种选择是在视图模型关闭时将登录命令设置为 null(或空/无操作命令)(并引发相应的PropertyChanged事件)。 这将导致绑定到命令的任何按钮取消订阅其CanExecuteChanged事件。

关于 Mike Storbl 指出的关于 Mvvm light 的 RelayCommand 使用

 CommandManager.RequerySuggested 

这是非常扩展的性能明智,并且容易分配错误,因为它会引发

  CanExecuteChangedEvent 

每次可视化树聚焦时。

你应该这样实现你自己的:

public class RelayCommand : ICommand
{
    private Func<bool> _canExecute;
    private Action _execute;
    public RelayCommand(Action execute , Func<bool> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute();
    }
    public event EventHandler CanExecuteChanged;
    public void RaiseCanExecuteChanged()
    {
        var temp = Volatile.Read(ref CanExecuteChanged);
        if (temp != null)
            temp(this, new EventArgs());
    }
    public void Execute(object parameter)
    {
        _execute();
    }
}

并在需要时从您的代码中提出它,例如当密码被更改时。

  verifyLogInCommand.RaiseCanExecuteChanged();