C++ で Qt GUI アプリ開発:QUndoStack::push() を活用した元に戻す/やり直す 機能の仕組み


QUndoStack::push() は、Qt GUI において 元に戻す/やり直す 機能を実現するために使用される重要な関数です。この関数は、QUndoCommand オブジェクトを QUndoStack にプッシュすることで、アプリケーションの状態変更を履歴として記録します。これにより、ユーザーは実行した操作を後から元に戻したり、やり直したりすることができます。

QUndoStack と QUndoCommand

  • QUndoCommand
    アプリケーションの状態変更を表すクラスです。
  • QUndoStack
    元に戻す/やり直す 操作の履歴を管理するクラスです。

QUndoStack::push() の動作

QUndoStack::push() 関数は、以下の処理を実行します。

  1. 引数として渡された QUndoCommand オブジェクトを QUndoStack にプッシュします。
  2. QUndoCommand オブジェクトの redo() メソッドを呼び出し、アプリケーションの状態変更を実行します。
  3. canUndo() メソッドを呼び出し、元に戻す操作が可能かどうかを確認します。
  4. undo() メソッドを呼び出し、元に戻す操作が可能であれば実行します。

QUndoStack::push() の例

// ボタンをクリックしたときに実行されるスロット
void MyClass::onButtonClicked() {
  // 元に戻す/やり直す 操作を管理する QUndoStack オブジェクトを作成
  QUndoStack undoStack;

  // テキストエディタの内容を変更する QUndoCommand オブジェクトを作成
  MyTextCommand *textCommand = new MyTextCommand(textEdit);
  textCommand->setText("新しいテキスト");

  // QUndoStack に QUndoCommand オブジェクトをプッシュ
  undoStack.push(textCommand);

  // テキストエディタに新しいテキストを設定
  textEdit->setText("新しいテキスト");
}

この例では、ボタンがクリックされたときに QUndoStack::push() 関数を使用して QUndoCommand オブジェクトを QUndoStack にプッシュしています。これにより、ユーザーはボタンをクリックしてテキストエディタの内容を変更した操作を元に戻したり、やり直したりすることができます。

  • QUndoStack::push() 関数は、アプリケーションの状態を変更する前に呼び出す必要があります。
  • QUndoCommand オブジェクトは、所有権を QUndoStack に移管します。そのため、QUndoStack::push() 関数を呼び出した後に QUndoCommand オブジェクトを直接削除することはできません。


MainWindow.h

#include <QMainWindow>
#include <QTextEdit>
#include <QUndoStack>
#include "mytextcommand.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);

private:
    QTextEdit *textEdit;
    QUndoStack undoStack;
};

MainWindow.cpp

#include "mainwindow.h"
#include "mytextcommand.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    textEdit = new QTextEdit(this);
    setCentralWidget(textEdit);

    QToolBar *toolBar = new QToolBar(this);
    toolBar->addAction(undoStack.createUndoAction(this));
    toolBar->addAction(undoStack.createRedoAction(this));
    addToolBar(Qt::TopToolBarArea, toolBar);
}

MyTextCommand.h

#include <QUndoCommand>
#include <QTextEdit>

class MyTextCommand : public QUndoCommand
{
public:
    MyTextCommand(QTextEdit *textEdit, const QString &oldText, const QString &newText);

    virtual void redo() override;
    virtual void undo() override;

private:
    QTextEdit *textEdit;
    QString oldText;
    QString newText;
};

MyTextCommand.cpp

#include "mytextcommand.h"
#include <QTextDocument>

MyTextCommand::MyTextCommand(QTextEdit *textEdit, const QString &oldText, const QString &newText) :
    QUndoCommand(textEdit),
    textEdit(textEdit),
    oldText(oldText),
    newText(newText)
{
}

void MyTextCommand::redo()
{
    textEdit->setPlainText(newText);
}

void MyTextCommand::undo()
{
    textEdit->setPlainText(oldText);
}

使用方法

このコードをビルドして実行すると、テキストエディタが表示されます。テキストエディタでテキストを入力し、元に戻す ボタンまたは やり直す ボタンをクリックすると、入力したテキストを元に戻したり、やり直したりすることができます。

説明

  • MyTextCommand クラスは、テキスト編集操作を表す QUndoCommand の派生クラスです。
  • undoStack メンバ変数は、QUndoStack オブジェクトを表します。
  • textEdit メンバ変数は、テキストエディタウィジェットを表します。
  • MainWindow クラスは、Qt GUI アプリケーションのメインウィンドウを表します。
  • undo() メソッドは、テキストエディタに古いテキストを設定します。
  • redo() メソッドは、テキストエディタに新しいテキストを設定します。
  • コンストラクタは、編集対象となるテキストエディタウィジェットと、変更前後のテキストを受け取ります。


Qt GUI において元に戻す/やり直す 機能を実現するには、QUndoStack::push() 関数以外にもいくつかの代替方法があります。以下では、代表的な代替方法と、それぞれの特徴について説明します。

QMemento パターン

QMemento パターンは、オブジェクトの状態をスナップショットとして保存し、そのスナップショットを使用してオブジェクトの状態を復元するパターンです。このパターンは、QUndoStack を使用せずに元に戻す/やり直す 機能を実現するシンプルで効率的な方法です。

利点

  • QUndoStack を使用するよりもメモリ使用量が少ない
  • 軽量で効率的
  • シンプルで理解しやすい

欠点

  • 依存関係の管理が煩雑になる場合がある
  • 複雑なオブジェクトの状態を保存するには不向き

class MyObject {
public:
    void setState(const QString &state) {
        oldState = currentState;
        currentState = state;
    }

    void undo() {
        if (!oldState.isEmpty()) {
            currentState = oldState;
            oldState.clear();
        }
    }

    void redo() {
        // ここでは何も行わない
    }

private:
    QString currentState;
    QString oldState;
};

カスタム履歴管理クラス

カスタム履歴管理クラスを作成することで、QUndoStack の機能を拡張したり、独自の元に戻す/やり直す ロジックを実装することができます。この方法は、高度なカスタマイズが必要な場合に有効です。

利点

  • 独自の元に戻す/やり直す ロジックを実装できる
  • QUndoStack の機能を拡張できる

欠点

  • デバッグが難しい
  • 開発コストが高い

class MyHistoryManager {
public:
    void push(const MyCommand &command) {
        history.push_back(command);
    }

    void undo() {
        if (!history.empty()) {
            history.back().undo();
            history.pop_back();
        }
    }

    void redo() {
        if (!redoHistory.empty()) {
            redoHistory.back().redo();
            redoHistory.pop_back();
        }
    }

private:
    std::vector<MyCommand> history;
    std::vector<MyCommand> redoHistory;
};

シグナルとスロット

シグナルとスロットを使用して、アプリケーションの状態変更を通知し、その通知に基づいて元に戻す/やり直す 処理を実行することができます。この方法は、シンプルな元に戻す/やり直す 機能を実装する場合に有効です。

利点

  • コード量が少ない
  • シンプルで理解しやすい

欠点

  • 依存関係の管理が煩雑になる場合がある
  • 複雑なオブジェクトの状態変更には不向き

class MyObject {
public:
    Q_SIGNALS:
        void stateChanged(const QString &state);

signals:
    stateChanged(const QString &state);

private slots:
    void onStateChanged(const QString &state) {
        // 元に戻す/やり直す 処理を実行
    }

public:
    void setState(const QString &state) {
        emit stateChanged(state);
    }
};

外部ライブラリ

Qt以外にも、元に戻す/やり直す 機能を提供するライブラリがいくつか存在します。これらのライブラリを使用することで、より高度な元に戻す/やり直す 機能を実装することができます。

代表的なライブラリ

利点

  • 高度な元に戻す/やり直す 機能を実装できる

欠点

  • デバッグが難しい
  • 学習コストが高い

QUndoStack::push() 関数は、Qt GUI において元に戻す/やり直す 機能を実現するための代表的な方法ですが、他にもいくつかの代替方法があります。それぞれの方法には利点と欠点があるため、アプリケーションの要件に応じて適切な方法を選択する必要があります。

  • Qt ドキュメント: シグナルとスロット