首页 > 解决方案 > 在切换视图时禁用 CanExecute

问题描述

我有一个静态属性,可以跟踪我的视图模型中的属性更改:

public static class Global
{
   public static int Warning
   {
       get { return _warning; }
       set { _warning = value; OnStaticPropertyChanged(); }
   }
   private static int _warning;
}

然后,在视图模型中我有 CanExecute 命令(用于取消更改的按钮):

private bool Cancel_CanExecute(object parameter)
{
     bool changes=false;

     if (!string.IsNullOrEmpty(Name) && !string.IsNullOrEmpty(SurName))
     { 
        changes= true;
     }

     if (changes)
     {
       Global.Warning = 1; //Changes are true, store this state
     }

     return changes;
}

当我的视图发生变化并且用户想要切换视图时,我想向他们显示一个警告消息框。因此,在我的主窗口 viewModel 中,我有一个用于更改视图的命令:

private void Open_view(object parameter)
{
   if (Global.Warning != 0)
   {
       var msg = _windowservice.ShowMessage("Want to leave? Changes won't be saved.");

       if (msg == true) //User clicked OK in MessageBox
       {
          Global.Warning = 0; // reset static property to default, and proceed with code
       }
       else
       {
           return; //Prevent opening new view
       }
               
    }
    //Switch to new view - Calling this line before 'Global.Warning = 0;' doesn't help either
    Open_view = new SomeViewModel();

    //...
}

但是,当我确认 MessageBox 离开视图而不保存更改并将静态属性Warning重置为 0 时,CommandManager 仍然调用旧 viewmodel 的 CanExecute 命令,因此我的Warning属性再次获得值 1。

我的视图都是在资源字典中定义了 DataTemplates 的 UserControls,我能设法解决此行为的唯一方法是通过 UserControl 代码后面,如下所示:

private void UserControl_Unloaded(object sender, System.Windows.RoutedEventArgs e)
{
     this.DataContext = null; //
}

问题:如何在 MVVM 中正确处理这种情况?底线是我想在我的视图中跟踪属性更改,同时仍然能够通知用户未保存的更改,如果他想离开同一个视图。

编辑:不确定是否有帮助,但这也是我的命令实现:

    public class MyCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Predicate<object> _canExecute;

        public MyCommand(Action<object> execute, Predicate<object> canExecute)
        {
            _execute = execute;
            _canExecute = canExecute;
        }

        public bool CanExecute(object parameter)
        {
            if (_canExecute == null)
            {
                return true;
            }
         
            return _canExecute(parameter);
        }

        public void Execute(object parameter)
        {
            _execute?.Invoke(parameter);
        }

        public event EventHandler CanExecuteChanged
        {
            add { CommandManager.RequerySuggested += value; }
            remove { CommandManager.RequerySuggested -= value; }
        }
            

        public void RaiseCanExecuteChanged()
        {
          CommandManager.InvalidateRequerySuggested();
        }
    }

定义的 DataTemplates(资源字典,在 app.xaml 中注册):

    <!--Each view has It's own reference of ViewModel-->
    <DataTemplate DataType="{x:Type ViewModels:HomeViewModel}">
        <Views:HomeView />
    </DataTemplate>
    //...etc..

改变视图的属性:

        ///<summary>Inherited Base property</summary>
        public object Open_view
        {
            get { return _open_view; }
            set { _open_view = value; OnPropertyChanged(); }
        }
        private object _open_view;

所有视图都通过 ContentControl 在 MainWindow 中打开(在这里也尝试了 UpdateSourceTrigger ..):

 <ContentControl Grid.Column="1" Grid.Row="2"  Content="{Binding Open_view,UpdateSourceTrigger=PropertyChanged}">

标签: c#wpficommand

解决方案


在我看来,这听起来像是一个约束性问题。具体来说:View(用户控件)不知道 ViewModel 发生变化的地方。

如果父 ViewModel 中的 ViewModel 可以更改,请确保添加 PropertyChanged 事件以通知 View 它绑定的 ViewModel 已更改。

过去在代码隐藏中设置数据上下文让我很头疼,所以我建议不要这样做:(this.DataContext = null)也不要这样做(this.DataContext = ViewModel)。

因此,请确保 Open_view 看起来像这样:

public SomeViewModel Open_view 
{ 
  get { return _open_view; }
  set { _open_view = value; OnPropertyChanged(nameof(Open_view)); }
}

在父视图(包含用户控件的视图)中绑定数据上下文,如下所示:

<SomeUserControl ... DataContext={Binding Path=Open_view, Mode=OneWay, UpdateSourceTrigger=PropertyChanged} ... />

当然,您需要将路径更改为实际路径。


推荐阅读