C# WPF MVVM基于INotifyPropertyChanged的撤销系统问题求助
解决方案
问题1:撤销操作重复触发入栈的解决方法
核心思路是在执行撤销操作时,临时跳过将变更记录压入栈的逻辑,但保留PropertyChanged事件触发以更新视图。可以在ViewModel中添加一个标记变量控制这个行为。
修改后的ViewModel代码:
internal class MyViewModel { public MyModel MyModel { get; set; } = new(); public Stack<KeyValuePair<string, object>> PropertyStateStack = new(); public RelayCommand Undo { get; set; } // 添加撤销状态标记 private bool isUndoing; public MyViewModel() { SetupCommands(); MyModel.PropertyChanged += MyModel_PropertyChanged; } private void MyModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { // 撤销过程中不压栈 if (isUndoing) return; var args = e as PropertyChangedEventArgsExtended; if (args?.OldValue != null) { PropertyStateStack.Push(new KeyValuePair<string, object>(args.PropertyName, args.OldValue)); } } private void SetupCommands() { Undo = new RelayCommand(o => { if (PropertyStateStack.Count == 0) return; isUndoing = true; KeyValuePair<string, object> propertyState = PropertyStateStack.Pop(); PropertyInfo? property = MyModel.GetType().GetProperty(propertyState.Key); if (property != null) { property.SetValue(MyModel, Convert.ChangeType(propertyState.Value, property.PropertyType), null); } // 撤销完成后恢复标记 isUndoing = false; }); } }
问题2:备忘录模式绑定失效的优化方案
之前备忘录模式失效是因为替换了整个MyModel实例,视图绑定的是原实例引用,所以会失效。正确的做法是备份实例的属性状态而非替换实例,恢复时直接给现有实例的属性赋值,保持引用不变。
实现备忘录类
// 备忘录类,保存模型的完整属性状态 public class ModelMemento { private Dictionary<string, object> _propertyStates = new(); // 从模型创建备忘录 public static ModelMemento CreateFromModel(MyModel model) { var memento = new ModelMemento(); var properties = typeof(MyModel).GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { if (prop.CanRead && prop.CanWrite) { memento._propertyStates[prop.Name] = prop.GetValue(model); } } return memento; } // 将备忘录状态恢复到模型 public void RestoreToModel(MyModel model) { foreach (var kvp in _propertyStates) { var prop = model.GetType().GetProperty(kvp.Key); if (prop != null && prop.CanWrite) { prop.SetValue(model, Convert.ChangeType(kvp.Value, prop.PropertyType)); } } } }
修改ViewModel使用备忘录
internal class MyViewModel { public MyModel MyModel { get; set; } = new(); // 改用备忘录栈 public Stack<ModelMemento> MementoStack = new(); public RelayCommand Undo { get; set; } private bool isUndoing; public MyViewModel() { SetupCommands(); // 初始状态入栈 MementoStack.Push(ModelMemento.CreateFromModel(MyModel)); MyModel.PropertyChanged += MyModel_PropertyChanged; } private void MyModel_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) { if (isUndoing) return; // 每次属性变更后保存当前状态到栈 MementoStack.Push(ModelMemento.CreateFromModel(MyModel)); } private void SetupCommands() { Undo = new RelayCommand(o => { if (MementoStack.Count <= 1) return; // 保留初始状态 isUndoing = true; MementoStack.Pop(); // 弹出当前状态 var previousState = MementoStack.Peek(); previousState.RestoreToModel(MyModel); isUndoing = false; }); } }
额外优化:修正INotifyPropertyChangedExtended的定义
当前把INotifyPropertyChangedExtended定义为类不符合接口设计意图,改为接口可避免继承限制:
public interface INotifyPropertyChangedExtended : INotifyPropertyChanged { void OnPropertyChanged(string propertyName, object oldValue); } public class PropertyChangedEventArgsExtended : PropertyChangedEventArgs { public object OldValue { get; } public PropertyChangedEventArgsExtended(string propertyName, object oldValue) : base(propertyName) { OldValue = oldValue; } }
修改MyModel实现该接口:
internal class MyModel : INotifyPropertyChangedExtended { private string testProperty1 = ""; public string TestProperty1 { get { return testProperty1; } set { var oldValue = testProperty1; testProperty1 = value; OnPropertyChanged(nameof(TestProperty1), oldValue); } } private string testProperty2 = ""; public string TestProperty2 { get { return testProperty2; } set { var oldValue = testProperty2; testProperty2 = value; OnPropertyChanged(nameof(TestProperty2), oldValue); } } public event PropertyChangedEventHandler? PropertyChanged; public void OnPropertyChanged(string propertyName, object oldValue) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgsExtended(propertyName, oldValue)); } }
内容的提问来源于stack exchange,提问作者92carmnad
相关产品推荐
相关产品推荐

