【问题标题】:How should one implement a change state dialog with undo/redo support in Catel?应该如何在 Catel 中实现具有撤消/重做支持的更改状态对话框?
【发布时间】:2014-11-01 20:16:35
【问题描述】:

在使用对话框时,我无法让 Undo 和 Redo 正常运行。

我有一个简单的模型,其属性指示对象的状态(runningpausedstopped)可以通过对话框进行更改。发生的情况是,我的撤消队列中似乎没有执行任何操作,或者撤消将对象恢复到中间状态。

模型对象在构造函数中注册到 memento。该对话框具有三个单选按钮,每个按钮代表三种不同状态之一。每个单选按钮都绑定到一个命令。每个命令都会更改属性。我尝试了两种不同的方法,每个命令直接在对象中设置属性,或者每个命令在调用时为视图模型设置一个实例变量,然后我使用保存事件来修改对象。

如果使用第一种方法,如果用户在单击对话框中的“确定”之前单击了多个单选按钮,则每个属性更改都将放在“撤消”队列中。试图通过将整个对话框包装成批处理来解决该问题,但这会导致状态更改撤消,对象将恢复到最后一个之前的状态,即如果在对话框打开之前属性设置为stopped,并且用户按下暂停单选按钮,然后启动一个,最后确定,撤消将属性设置为paused,而不是预期的stopped

如果使用第二种方法,用户打开对话框,将状态更改为paused,在对话框中单击“确定”,撤消/重做的行为与预期相同,但如果再次打开对话框并选择“取消”,则会添加更多操作到 Undo 队列,即用户必须单击 Undo 两次才能回到初始的stopped-state。

所以我的问题是如何正确实施以获得预期的行为;每个对话交互都可以撤消,而不是对话中的每个交互?

这是 ViewModel 的代码:

namespace UndoRedoTest.ViewModels
{
    using Catel.Data;
    using Catel.MVVM;
    public class StartStopViewModel : ViewModelBase
    {
        Machine.MachineState _state;
        public StartStopViewModel(Machine controlledMachine) 
        {
            ControlledMachine = controlledMachine;
            _state = controlledMachine.State;
            StartMachine = new Command(OnStartMachineExecute);
            PauseMachine = new Command(OnPauseMachineExecute);
            StopMachine = new Command(OnStopMachineExecute);
            Saving += StartStopViewModel_Saving;
        }

        void StartStopViewModel_Saving(object sender, SavingEventArgs e)
        {
            ControlledMachine.State = _state;
        }

        [Model]
        public Machine ControlledMachine
        {
            get { return GetValue<Machine>(ControlledMachineProperty); }
            private set { SetValue(ControlledMachineProperty, value); }
        }

        public static readonly PropertyData ControlledMachineProperty = RegisterProperty("ControlledMachine", typeof(Machine));

        public override string Title { get { return "Set Machine state"; } }

        public Command StartMachine { get; private set; }
        public Command PauseMachine { get; private set; }
        public Command StopMachine { get; private set; }

        private void OnStartMachineExecute()
        {
            _state = Machine.MachineState.RUNNING;
            //ControlledMachine.SecondState = Machine.MachineState.RUNNING;
        }

        private void OnPauseMachineExecute()
        {
            _state = Machine.MachineState.PAUSED;
            //ControlledMachine.SecondState = Machine.MachineState.PAUSED;
        }

        private void OnStopMachineExecute()
        {
            _state = Machine.MachineState.STOPPED;
            //ControlledMachine.SecondState = Machine.MachineState.STOPPED;
        }
    }
}

【问题讨论】:

    标签: catel memento


    【解决方案1】:

    首先,不要订阅 Saving 事件,而只是重写 Save() 方法。请注意,当您使用 ModelAttribute 装饰模型时,Catel 会为您处理模型状态。因此需要获取对话框的前置状态和后置状态,然后将结果集推送到批处理中。

    例如,我会为对象类(或模型类)创建扩展方法,如下所示:

    public static Dictionary<string, object> GetProperties(this IModel model)
    {
        // todo: return properties
    }
    

    然后您在 InitializeSave 方法中执行此操作,您将拥有 2 组属性(pre state 和 post state)。现在有了这些,计算差异就很容易了:

    public static Dictionary<string, object> GetChangedProperties(Dictionary<string, object> preState, Dictionary<string, object> postState)
    {
        // todo: calculate difference
    }
    

    现在你有区别了,你可以创建一个备忘录批次,它会恢复你预期的确切状态。

    ps。如果您可以在完成后将其放入博客文章中,或者使用此功能创建 PR,那就太好了

    【讨论】:

    • 我不明白为什么我必须这样做,为什么它不能像我实现的那样工作。无论如何,根据上面的信息,我让它的行为符合预期。当对话框创建为初始状态时,我创建了属性的副本。然后在 Save 方法中,我通过属性字典查找该对话框更改的属性,并查看它是否更改。如果它改变了,我创建一个 ActionUndo 并将它添加到一个 Batch 中。最后,我将批次添加到 Memento 服务。当然,可以写一篇关于它的文章。但是什么是 PR?
    • aah,你的意思是把它集成到 ModelBase 和 ViewModelBase 中。认为我需要为此更好地了解 Catel,但可以尝试一下,如果您喜欢,请接受。
    猜你喜欢
    • 1970-01-01
    • 2015-08-10
    • 1970-01-01
    • 2016-02-18
    • 1970-01-01
    • 2011-04-03
    • 2012-07-02
    • 1970-01-01
    • 2016-03-07
    相关资源
    最近更新 更多