WPF + CommunityToolkit.Mvvm, как реализовать команды отмены/повтора

мне интересно, как лучше всего реализовать пользовательские команды отмены/повтора?

В моем случае я реализую редактор диаграмм и хочу создать команды «Отменить/Повторить» для отмены манипуляций с элементами холста, например, для отмены вращения, перемещения или изменения размера элемента.

Идеи, в которых я не уверен: -Написать собственную реализацию ICommand или производный класс от RelayCommand и хранить команды в основной модели представления в стеках; -Напишите репозиторий, который будет хранить стеки команд в памяти сеанса, и службу, которая будет реализовывать методы Register, Execute и Undo.

Отменить/Повторить можно только команды или, например, изменения свойств?

Klaus Gütter 31.08.2024 21:07

Привет Клаус, только для команд

Alexey Rodionov 31.08.2024 22:44

Команда — это не то, на чем стоит сосредоточиться. Основная проблема заключается в представлении различных версий. Связанный список различий или набор просмотров обычно являются опциями.

Andy 01.09.2024 01:12
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
3
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если кому-то интересно, вот как я реализовал это для своего случая:

/// <summary>
/// An interface that defines base methods to manage undoable commands.
/// </summary>
public interface IUndoableCommandManager
{
    /// <summary>
    /// Executes command.
    /// </summary>
    /// <param name = "command">Target command with execution logic.</param>
    public void Execute(IUndoableCommand command);
    /// <summary>
    /// Cancels the effects of an command execution.
    /// </summary>
    public void Undo();
    /// <summary>
    /// Performs command execution again.
    /// </summary>
    public void Redo();
    /// <summary>
    /// Clears history of all commands inside manager.
    /// </summary>
    public void Clear();
}

/// <summary>
/// A class that implements <see cref = "IUndoableCommandManager"/>. Manages undoable commands.
/// </summary>
public sealed class UndoableCommandManager : IUndoableCommandManager
{
    private readonly Stack<IUndoableCommand> _undoStack = [];
    private readonly Stack<IUndoableCommand> _redoStack = [];

    /// <summary>
    /// Gets a value indicating whether an undo operation can be performed.
    /// </summary>
    private bool CanUndo => _undoStack.Count > 0;

    /// <summary>
    /// Gets a value indicating whether a redo operation can be performed.
    /// </summary>
    private bool CanRedo => _redoStack.Count > 0;

    /// <inheritdoc/>
    public void Execute(IUndoableCommand command)
    {
        command.Execute(null);
        _undoStack.Push(command);
        _redoStack.Clear();
    }

    /// <inheritdoc/>
    public void Undo()
    {
        if (CanUndo)
        {
            var command = _undoStack.Pop();
            command.Undo();
            _redoStack.Push(command);
        }
    }

    /// <inheritdoc/>
    public void Redo()
    {
        if (CanRedo)
        {
            var command = _redoStack.Pop();
            command.Execute(null);
            _undoStack.Push(command);
        }
    }

    /// <inheritdoc/>
    public void Clear()
    {
        _undoStack.Clear();
        _redoStack.Clear();
    }
}

/// <summary>
/// An interface expanding <see cref = "ICommand"/> with undo behavior.
/// </summary>
public interface IUndoableCommand : ICommand
{
    /// <summary>
    /// Undoes previous execution.
    /// </summary>
    void Undo();
}

/// <summary>
/// Initializes a new instance of the <see cref = "UndoableCommand"/> class that can undo previous execution.
/// </summary>
public sealed class UndoableCommand : IUndoableCommand
{
    private readonly Action _execute;
    private readonly Action _undo;

    /// <inheritdoc/>
    public event EventHandler? CanExecuteChanged;

    /// <summary>
    /// Initializes a new instance of the <see cref = "UndoableCommand"/> class.
    /// </summary>
    /// <param name = "execute">The execution logic.</param>
    /// <param name = "undo">The undo behavior logic.</param>
    public UndoableCommand(Action execute, Action undo)
    {
        ArgumentNullException.ThrowIfNull(execute);
        ArgumentNullException.ThrowIfNull(undo);

        _execute = execute;
        _undo = undo;
    }

    /// <inheritdoc/>
    public bool CanExecute(object? parameter = null)
    {
        return true;
    }

    /// <inheritdoc/>
    public void Execute(object? parameter = null)
    {
        _execute();
    }

    /// <inheritdoc/>
    public void Undo()
    {
        _undo();
    }
}

Другие вопросы по теме