问题描述
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 仍然调用旧视图模型的 CanExecute 命令,所以我的 警告 属性再次获得 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();
}
}
Defined 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}">
解决方法
这听起来像是一个绑定问题。具体来说: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} ... />
当然需要把路径改成实际路径。
,就我这几天的研究而言,不幸的是,没有任何简单的选项可以用来解决我的问题。发现的问题 here 和 here 更详细地解释了我的问题是什么。链接中的第一个问题实际上提供了一个可以完成且有用的答案,但在我的情况下需要大量编码。
然后我重新考虑了这个问题,并决定使用 Icommand CanExecute
放弃所有 UI 逻辑。即使在 UserControl Unloaded 事件上将 DataContext 设置为 null 也会导致我编写大量代码。
所以我决定尝试不同的方法,在我看来最简单的方法。我所做的是我创建了一个自定义按钮控件,它侦听IsEnabledChangedEvent
,并将它的启用属性状态绑定到 ViewModel 属性。所以完整的代码是这样的:
静态属性:
public static class Global
{
public static int Warning
{
get { return _warning; }
set { _warning = value; OnStaticPropertyChanged(); }
}
private static int _warning;
}
自定义按钮:
public class Button_cancel : Button
{
public Button_cancel()
{
//Custom style
Style stil = FindResource("Btn_style") as Style;
Style = stil;
Width = 80;
IsEnabledChanged += Button_IsEnabledChanged;
}
public static DependencyProperty ChangesProperty =
DependencyProperty.Register("Changes",typeof(bool),typeof(Button_cancel),new FrameworkPropertyMetadata(false,FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
public bool Changes
{
get { return (bool)GetValue(ChangesProperty); }
set { SetValue(ChangesProperty,value); }
}
private void Button_IsEnabledChanged(object sender,System.Windows.DependencyPropertyChangedEventArgs e)
{
Changes = IsEnabled ? true : false;
}
}
ViewModel 属性:
public bool Changes_made
{
get { return _changes_made; }
set {
_changes_made = value;
OnPropertyChanged();
if (Changes_made)
{
Global.Warning = 1;
}
else
{
Global.Warning = 0;
}
}
}
private bool _changes_made;
XAML 绑定:
<ctrls:Button_cancel Content="Cancel" Command="{Binding CANCEL}" Changes="{Binding Changes_made}" />
最后,主窗口 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
Open_view = new SomeViewModel();
//...
}
就像一个魅力,不会干扰 ICommand's CanExecuteChanged
事件。
因此,每当 ICommand 的 CanExecute 触发并启用 Button 时,我都会将此状态存储在另一个属性中。这样我就知道用户所做的每项更改。
我心中还有一个解决方案 - 跟踪每个必需属性设置器的更改,但这也涉及大量编码。
希望它对未来有所帮助,非常感谢您的帮助!