QTextEdit undo() の使い方と注意点【Qt 日本語チュートリアル】

2025-05-16

void QTextEdit::undo()

この関数は、Qtのテキストエディットウィジェット (QTextEdit) で、ユーザーが直前に行った操作を取り消す(アンドゥ)ために使用されます。

より詳しく

  • 利用場面
    ユーザーが誤ってテキストを削除したり、意図しない書式設定をしてしまった場合に、「元に戻す」機能を提供するために使用されます。通常は、メニューの「編集」にある「元に戻す」や、キーボードショートカット(Ctrl+Z や Cmd+Z など)にこの関数を接続して利用します。
  • 戻り値
    void 型なので、この関数は何も値を返しません。操作が成功したかどうかを明示的に知る必要はありません。
  • 対応するredo
    取り消した操作を再度やり直すためには、QTextEdit::redo() 関数を使用します。undo()redo() は対になる機能です。
  • 複数回のundo
    undo() 関数は複数回呼び出すことができ、そのたびに直前の操作が順番に取り消されます。
  • undo/redoスタック
    QTextEdit は、ユーザーが行った操作の履歴を内部的に記録しています。undo() 関数はこの履歴の「直前」の操作を取り出し、その操作を逆向きに実行することで、編集前の状態に戻します。
  • 機能
    この関数を呼び出すと、QTextEdit は内部のundo/redoスタックに保存されている直前の操作を元に戻します。これは、テキストの入力、削除、書式設定の変更など、ユーザーが行った編集操作を含みます。


もしユーザーが "Hello" と入力した後、" world" と入力した場合、undo() を一度呼び出すと " world" の入力が取り消され、テキストは "Hello" に戻ります。もう一度 undo() を呼び出すと、最初の "Hello" の入力も取り消され、テキストエディットは空の状態に戻る可能性があります(初期状態によります)。



QTextEdit::undo() に関する一般的なエラーとトラブルシューティング

QTextEdit::undo() 関数自体は直接的なエラーを返すわけではありません(void 型のため)。しかし、その機能が期待通りに動作しない場合、以下のような原因や問題が考えられます。

undo/redo スタックが空である

  • トラブルシューティング
    • ユーザーが何らかの編集操作を行った後に undo() を呼び出すようにしてください。
    • アプリケーションの起動時など、特定のタイミングで undo() を無効化または非表示にすることを検討してください(例えば、QAction::setEnabled(false) など)。
  • 原因
    ユーザーがまだ何も編集操作を行っていない場合や、QTextEdit が初期化された直後の状態では、undo/redo スタックには何も記録されていません。この状態で undo() を呼び出しても、何も起こりません。

undo の深さ(履歴)が制限されている

  • トラブルシューティング
    • QTextEdit::setUndoRedoEnabled(bool) を使用して、undo/redo 機能を明示的に有効にしているか確認してください(デフォルトでは有効です)。
    • QTextEdit::setUndoStack(QUndoStack *stack) を使用して、独自の QUndoStack オブジェクトを設定することで、undo/redo の履歴管理をより細かく制御できます。必要に応じて、QUndoStack のサイズ制限を調整することも可能です。
  • 原因
    QTextEdit は内部的に undo/redo の履歴を保持していますが、その深さにはデフォルトまたは設定された制限がある場合があります。非常に多くの編集操作を行った後では、古い操作は履歴から消えている可能性があり、それ以上 undo() を呼び出しても効果がないことがあります。

カスタム操作が undo/redo に対応していない

  • トラブルシューティング
    • プログラムから QTextEdit の内容を変更する場合は、QUndoCommand を使用して操作をカプセル化し、undo/redo スタックに登録することを検討してください。これにより、カスタムな操作も undo() および redo() の対象に含めることができます。
    • QTextEdit が提供するシグナル(例えば、textChanged())を利用して、変更を監視し、必要に応じてカスタムな undo/redo ロジックを実装することも考えられます。
  • 原因
    QTextEdit の内容をプログラム側から直接変更した場合(例えば、setText() など)、これらの操作は自動的に undo/redo スタックに記録されないことがあります。特に、テキストの置換や大規模な変更を行った場合に注意が必要です。

シグナルとスロットの接続ミス

  • トラブルシューティング
    • Qt Designer やコード内で、シグナルとスロットの接続が正しく行われているか確認してください。特に、オブジェクト名やシグナル、スロットの記述に誤りがないか注意が必要です。
  • 原因
    undo() 機能に関連するボタンやメニュー項目などのシグナルが、QTextEditundo() スロットに正しく接続されていない可能性があります。

スレッドの問題(稀なケース)

  • トラブルシューティング
    • QMetaObject::invokeMethod() を使用して、undo() の呼び出しをメインスレッドにディスパッチするようにしてください。
  • 原因
    GUI 操作は通常メインスレッドで行う必要があります。もし別のスレッドから undo() を呼び出そうとすると、予期しない動作を引き起こす可能性があります。

派生クラスでの再実装による影響

  • トラブルシューティング
    • カスタム実装を確認し、必要に応じて親クラス (QTextEdit) の undo() を呼び出すようにしてください (QTextEdit::undo()).
  • 原因
    QTextEdit を継承したカスタムウィジェットで undo() を再実装している場合、その実装に誤りがある可能性があります。
  • Qt のドキュメント参照
    QTextEdit および関連するクラス(QUndoStack, QUndoCommand など)の公式ドキュメントを再度確認し、理解を深めることが重要です。
  • シンプルなテストケース
    問題が複雑なコード内で発生している場合は、最小限のコードで問題を再現できるテストケースを作成し、切り分けを行うと効率的です。
  • デバッグ出力
    qDebug() などを利用して、undo() が呼び出されたタイミングや、undo/redo スタックの状態などをログ出力してみると、問題の原因特定に役立つことがあります。


基本的な例:メニューバーの「元に戻す」機能

この例では、メニューバーに「編集」メニューと「元に戻す」アクションを追加し、そのアクションが QTextEditundo() スロットに接続される基本的な流れを示します。

#include <QApplication>
#include <QMainWindow>
#include <QTextEdit>
#include <QMenuBar>
#include <QAction>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow window;
    QTextEdit textEdit;
    window.setCentralWidget(&textEdit);

    // メニューバーの作成
    QMenuBar *menuBar = window.menuBar();
    QMenu *editMenu = menuBar->addMenu(tr("編集(&E)"));

    // 「元に戻す」アクションの作成
    QAction *undoAction = new QAction(tr("元に戻す(&U)"), &window);
    undoAction->setShortcut(QKeySequence::Undo); // 標準的なショートカットキーを設定

    // 「元に戻す」アクションがトリガーされたときに QTextEdit の undo() を呼び出す
    QObject::connect(undoAction, &QAction::triggered, &textEdit, &QTextEdit::undo);

    // メニューに「元に戻す」アクションを追加
    editMenu->addAction(undoAction);

    window.show();
    return a.exec();
}

解説

  1. ヘッダーファイルのインクルード
    必要な Qt のクラスのヘッダーファイルをインクルードしています。
  2. QMainWindow と QTextEdit の作成
    メインウィンドウとその中央ウィジェットとして QTextEdit を作成しています。
  3. メニューバーとメニューの作成
    メニューバーを作成し、「編集」というメニューを追加しています。
  4. 「元に戻す」アクションの作成
    QAction クラスを使用して、「元に戻す」というテキストとショートカットキー(Ctrl+Z や Cmd+Z)を持つアクションを作成しています。
  5. シグナルとスロットの接続
    QObject::connect() を使用して、「元に戻す」アクションの triggered() シグナルを QTextEditundo() スロットに接続しています。これにより、「元に戻す」アクションがトリガーされると、textEdit.undo() が呼び出されます。
  6. メニューへのアクションの追加
    作成した「元に戻す」アクションを「編集」メニューに追加しています。

より高度な例:カスタム Undo/Redo スタックの使用

この例では、QTextEdit にデフォルトの undo/redo スタックではなく、独自の QUndoStack オブジェクトを設定する方法を示します。これにより、undo/redo の履歴管理をより細かく制御できます。

#include <QApplication>
#include <QMainWindow>
#include <QTextEdit>
#include <QMenuBar>
#include <QAction>
#include <QUndoStack>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow window;
    QTextEdit textEdit;
    window.setCentralWidget(&textEdit);

    // カスタムの QUndoStack を作成
    QUndoStack *undoStack = new QUndoStack(&window);
    textEdit.setUndoStack(undoStack);

    // メニューバーの作成
    QMenuBar *menuBar = window.menuBar();
    QMenu *editMenu = menuBar->addMenu(tr("編集(&E)"));

    // 「元に戻す」アクション (カスタムスタックを使用)
    QAction *undoAction = new QAction(tr("元に戻す(&U)"), &window);
    undoAction->setShortcut(QKeySequence::Undo);
    QObject::connect(undoAction, &QAction::triggered, undoStack, &QUndoStack::undo);
    editMenu->addAction(undoAction);

    // 「やり直す」アクション (カスタムスタックを使用)
    QAction *redoAction = new QAction(tr("やり直す(&R)"), &window);
    redoAction->setShortcut(QKeySequence::Redo);
    QObject::connect(redoAction, &QAction::triggered, undoStack, &QUndoStack::redo);
    editMenu->addAction(redoAction);

    window.show();
    return a.exec();
}
  1. QUndoStack のインクルードと作成
    QUndoStack クラスのヘッダーファイルをインクルードし、undoStack という名前の QUndoStack オブジェクトを生成しています。
  2. QTextEdit へのカスタムスタックの設定
    textEdit.setUndoStack(undoStack) を使用して、作成したカスタムの QUndoStackQTextEdit に設定しています。これにより、QTextEdit の undo/redo 操作は、このカスタムスタックを通じて管理されるようになります。
  3. 「元に戻す」と「やり直す」アクションの接続
    • 「元に戻す」アクションの triggered() シグナルを、カスタム undoStackundo() スロットに接続しています。
    • 同様に、「やり直す」アクションの triggered() シグナルを、カスタム undoStackredo() スロットに接続しています。
  4. メニューへのアクションの追加
    作成した「元に戻す」と「やり直す」のアクションを「編集」メニューに追加しています。


QUndoStack を直接操作する

QTextEdit は内部的に QUndoStack を使用して undo/redo の履歴を管理しています。QTextEdit::undoStack() 関数を使用すると、この内部の QUndoStack オブジェクトへのポインタを取得できます。これにより、QUndoStack のメソッドを直接呼び出して、undo/redo の動作をより細かく制御できます。

#include <QApplication>
#include <QMainWindow>
#include <QTextEdit>
#include <QMenuBar>
#include <QAction>
#include <QUndoStack>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow window;
    QTextEdit textEdit;
    window.setCentralWidget(&textEdit);

    // QTextEdit の内部 UndoStack を取得
    QUndoStack *stack = textEdit.undoStack();

    // メニューバーの作成
    QMenuBar *menuBar = window.menuBar();
    QMenu *editMenu = menuBar->addMenu(tr("編集(&E)"));

    // 「元に戻す」アクション (QUndoStack の undo() を直接呼び出す)
    QAction *undoAction = new QAction(tr("元に戻す(&U)"), &window);
    undoAction->setShortcut(QKeySequence::Undo);
    QObject::connect(undoAction, &QAction::triggered, stack, &QUndoStack::undo);
    editMenu->addAction(undoAction);

    // 「やり直す」アクション (QUndoStack の redo() を直接呼び出す)
    QAction *redoAction = new QAction(tr("やり直す(&R)"), &window);
    redoAction->setShortcut(QKeySequence::Redo);
    QObject::connect(redoAction, &QAction::triggered, stack, &QUndoStack::redo);
    editMenu->addAction(redoAction);

    // Undo/Redo スタックの状態変化を監視する例
    QObject::connect(stack, &QUndoStack::canUndoChanged, [&](bool canUndo) {
        undoAction->setEnabled(canUndo);
        qDebug() << "Can Undo:" << canUndo;
    });

    QObject::connect(stack, &QUndoStack::canRedoChanged, [&](bool canRedo) {
        redoAction->setEnabled(canRedo);
        qDebug() << "Can Redo:" << canRedo;
    });

    window.show();
    return a.exec();
}

解説

  • QUndoStackcanUndoChanged() および canRedoChanged() シグナルに接続することで、undo/redo が可能かどうかを監視し、UI(例えば、メニュー項目の有効/無効)を更新することができます。
  • 「元に戻す」と「やり直す」のアクションの triggered() シグナルを、QTextEditundo() スロットではなく、取得した QUndoStack オブジェクトの undo() および redo() スロットに直接接続しています。
  • textEdit.undoStack() を呼び出すことで、QTextEdit が内部で使用している QUndoStack オブジェクトへのポインタを取得しています。

QUndoStack の主なメソッド

  • clear(): スタックをクリアします。
  • count(): スタック内のコマンド数を取得します。
  • setIndex(int index): スタックの位置を特定の位置に設定します(これにより、複数の undo/redo を一度に行うことが可能です)。
  • index(): 現在のスタックの位置を取得します。
  • push(QUndoCommand *command): 新しい undo コマンドをスタックに追加します。
  • redo(): 直前に元に戻したコマンドをやり直します。
  • undo(): 直前のコマンドを元に戻します。

カスタム QUndoCommand を使用する

QTextEdit の内容をプログラムから変更する場合や、テキスト編集以外のカスタムな操作を undo/redo の対象に含めたい場合は、QUndoCommand を継承した独自のコマンドクラスを作成し、それを QUndoStack::push() メソッドを使用してスタックに追加します。

#include <QApplication>
#include <QMainWindow>
#include <QTextEdit>
#include <QMenuBar>
#include <QAction>
#include <QUndoStack>
#include <QUndoCommand>
#include <QString>
#include <QDebug>

class InsertTextCommand : public QUndoCommand {
public:
    InsertTextCommand(QTextEdit *edit, const QString &text, int pos)
        : textEdit(edit), insertedText(text), position(pos)
    {
        setText(QObject::tr("テキスト挿入: \"%1\"").arg(text));
    }

    void undo() override {
        QTextCursor cursor(textEdit->document());
        cursor.setPosition(position);
        cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, insertedText.length());
        cursor.removeSelectedText();
    }

    void redo() override {
        QTextCursor cursor(textEdit->document());
        cursor.setPosition(position);
        cursor.insertText(insertedText);
    }

private:
    QTextEdit *textEdit;
    QString insertedText;
    int position;
};

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QMainWindow window;
    QTextEdit textEdit;
    window.setCentralWidget(&textEdit);
    QUndoStack undoStack(&window);
    textEdit.setUndoStack(&undoStack);

    // ある操作を実行し、それを UndoStack に登録する例
    QPushButton insertButton("テキスト挿入", &window);
    window.addToolBar(nullptr)->addWidget(&insertButton);

    QObject::connect(&insertButton, &QPushButton::clicked, [&]() {
        QString textToInsert = "追加テキスト";
        int currentPos = textEdit.textCursor().position();
        QUndoCommand *insertCommand = new InsertTextCommand(&textEdit, textToInsert, currentPos);
        undoStack.push(insertCommand);
        textEdit.insertPlainText(textToInsert);
    });

    // メニューバーの設定 (Undo/Redo アクションは前の例と同様に QUndoStack に接続)
    QMenuBar *menuBar = window.menuBar();
    QMenu *editMenu = menuBar->addMenu(tr("編集(&E)"));
    QAction *undoAction = new QAction(tr("元に戻す(&U)"), &window);
    undoAction->setShortcut(QKeySequence::Undo);
    QObject::connect(undoAction, &QAction::triggered, &undoStack, &QUndoStack::undo);
    editMenu->addAction(undoAction);
    QAction *redoAction = new QAction(tr("やり直す(&R)"), &window);
    redoAction->setShortcut(QKeySequence::Redo);
    QObject::connect(redoAction, &QAction::triggered, &undoStack, &QUndoStack::redo);
    editMenu->addAction(redoAction);

    window.show();
    return a.exec();
}

解説

  • メニューの「元に戻す」と「やり直す」のアクションは、前の例と同様に QUndoStackundo() および redo() スロットに接続されています。
  • ボタンがクリックされたときに、InsertTextCommand のインスタンスを作成し、undoStack.push() を使用して undo スタックに追加しています。同時に、QTextEdit にテキストを挿入しています。
  • undo() メソッドと redo() メソッドをオーバーライドして、テキストの挿入と削除の逆操作を定義しています。
  • InsertTextCommand というカスタムクラスが QUndoCommand を継承しています。

この方法を使用すると、QTextEdit の標準的なテキスト編集操作だけでなく、アプリケーション固有のカスタムな操作も undo/redo の対象に含めることができます。