Pergunta

I was currently looking to combine the Undo/Redo (based on this article) with the Command-Pattern using ICommand. Reading about the idea of CanExecute, and CanExecuteChanged, I found the CommandManager which propagates the changed state to all registered UIElements. However it seems hard to combine these two if the command stores the changed data itself. Here an example of the current situation:

interface IUndoCommand : ICommand
{
    void Undo();
}

and the Implementation:

public class SampleCommand : IUndoCommand
{
    private Point oldPosition;
    private Shape shape;

    // Gets the currently moved shape object
    public MoveCommand(Shape shape, Point oldPosition)
    {
        this.oldPosition = oldPosition;
        this.newPosition = new Point(Cavas.GetLeft(shape),Canvas.GetTop(shape));
        this.shape = shape;
    }

    public void Execute(object parameter)
    {
        Model.PersistChanges(shape, newPosition);
    }

    public void Undo()
    {
        Model.PersistChanges(shape, oldPosition);
    }

    public bool CanExecute(object parameter)
    {
    }

    public event EventHandler CanExecuteChanged;
}

Conflict

In this implementation the command stores the changes and hence a new instance needs to be created each time when it's executed. However the CommandManager keeps track of the state and informs the UIElements. If I register each instance of the command there, one UIElement has several, equal commands competing about the CanExecute-state. This seems to break the idea, so how does it work?

Of course I could move the state, needed for redo/undo, from the Command to the model, and register exactly one (static) instance of command which will be used allover. But acutally I like the idea of stroring the state in the command.

If I'd omit the ICommand Usage the example of the wpftutorial.net would work - although I haven't fully got the idea with the action/undo action there.

Question

How do you combine these approaches, the undo/redo + CommandManager? Is the only solution to realize saving the state in the Model (considering MVVM as base) or are there are other opportunities?

EDIT:

Is it possible to save the state in the command still using the CommandManager? The ICommand-Interface offers the functionality to keep track of the CanExecute-State, which is a nice idea. However, I don't see a possibility to keep this idea while saving the state in the command (and thus need several different instances).

Foi útil?

Solução

For every one else stumbling over this:

I solved this issue now, using hierarchical ViewModels. Each command has its own ViewModel now.

public class ViewModel 
{

    public ViewModel()
    {
        MoveCommand = new MoveCommand(this);
    }

    public Shape Shape {get;set;}
    public Point CurrentPosition {get;set;}
    public ICommand MoveCommand {get; private set;}
}


public class MoveCommand
{
    ViewModel viewModel;
    Point shiftVector;

    public MoveCommand(ViewModel viewModel, Point shiftVector)
    {
        this.viewModel = viewModel;
        this.shiftVector = shiftVector;
    }

    public void Execute(object parameter)
    {
        shapeVM.CurrentPosition.X += shiftVector.X;
        shapeVM.CurrentPosition.Y += shiftVector.Y;
    }

    public void Undo()
    {
        shapeVM.CurrentPosition.X -= shiftVector.X;
        shapeVM.CurrentPosition.Y -= shiftVector.Y;
    }

    public bool CanExecute(object parameter)
    {
    }

    // Notice here: the events should be passed to the command manager to take care about it
    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove {CommandManager.RequerySuggested -= value;}
    }

The hierarchical part comes into play as follows:

public class BaseViewModel
{
    ObservableCollection<ViewModels> ViewModels;

    // pass the command from the child's viewModel.
    public ICommand MoveCommand
    {
        get
        {
            return SelectedItem.MoveCommand;
        }
    }

    public SelectedItem ViewModel {get;set;}

    public BaseViewModel()
    {

    }

}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top