【问题标题】:RelayCommand on view still being evaluated after ShowDialog is closed关闭 ShowDialog 后,视图上的 RelayCommand 仍在评估中
【发布时间】:2013-10-30 15:24:28
【问题描述】:

(Closest related question)

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

为什么当窗口关闭/为空时,Command/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());
}

LogInViewModel.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;
  }
}

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 感谢您的精彩解释。我刚刚找到了在完成here as well 时将命令设置为空的类似解释
    • 我有同样的问题,但是它发生在我的项目中,即使我将命令设置为空,有什么想法吗?
    • @Chen 当您取消您的命令时,您是否在提高PropertyChanged?如果不是,则绑定控件不知道该命令已被替换,会继续查询旧的。
    • 我有一个正在尝试清理的视图模型,我要做的一件事就是将所有命令设为空。清理后我被调用可以执行。避免它的最佳方法是什么?谢谢。
    【解决方案2】:

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

     CommandManager.RequerySuggested 
    

    这在性能方面是非常广泛的,并且由于它引发了很多错误

      CanExecuteChangedEvent 
    

    每次 VisualTree 聚焦时。

    您应该这样实现自己的:

    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(); 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-12-29
      • 1970-01-01
      • 2016-05-26
      • 2021-08-22
      • 2014-06-11
      相关资源
      最近更新 更多