Qt QPlainTextEdit カーソル移動 (moveCursor) の詳細解説と代替手法

2025-04-26

QPlainTextEdit::moveCursor() メソッドとは

QPlainTextEdit::moveCursor() メソッドは、QPlainTextEdit ウィジェット(複数行のプレーンテキストを表示・編集するためのクラス)内のテキストカーソル(キャレット)の位置をプログラム的に移動させるために使用されます。

メソッドのシグネチャ

void QPlainTextEdit::moveCursor(QTextCursor::MoveOperation operation, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor)
  • mode (QTextCursor::MoveMode)
    移動中にテキストの選択を行うかどうかを指定する列挙型の値です。
    • QTextCursor::MoveAnchor (デフォルト): 移動してもテキストは選択されません。
    • QTextCursor::KeepAnchor: 移動前のカーソル位置をアンカーポイントとして保持し、移動後の位置までテキストが選択されます。
  • operation (QTextCursor::MoveOperation)
    カーソルをどのように移動させるかを指定する列挙型の値です。例えば、次の単語へ移動、前の行へ移動、ドキュメントの先頭へ移動など、さまざまな移動操作が定義されています。

主な QTextCursor::MoveOperation の種類

以下は QTextCursor::MoveOperation の主な種類とその説明です。

  • QTextCursor::PreviousLine
    Up と同じです。
  • QTextCursor::NextLine
    Down と同じです。
  • QTextCursor::PreviousCharacter
    Left と同じです。
  • QTextCursor::NextCharacter
    Right と同じです。
  • QTextCursor::NextBlock
    カーソルを次の段落の先頭へ移動します。
  • QTextCursor::PreviousBlock
    カーソルを前の段落の先頭へ移動します。
  • QTextCursor::NextWord
    カーソルを次の単語の先頭へ移動します。
  • QTextCursor::PreviousWord
    カーソルを前の単語の先頭へ移動します。
  • QTextCursor::EndOfDocument
    カーソルをドキュメントの末尾へ移動します。
  • QTextCursor::StartOfDocument
    カーソルをドキュメントの先頭へ移動します。
  • QTextCursor::EndOfBlock
    カーソルを現在の段落の末尾へ移動します。
  • QTextCursor::StartOfBlock
    カーソルを現在の段落(空行で区切られたテキストの塊)の先頭へ移動します。
  • QTextCursor::EndOfLine
    カーソルを行の末尾へ移動します。
  • QTextCursor::StartOfLine
    カーソルを行の先頭へ移動します。
  • QTextCursor::Down
    カーソルを1行下へ移動します。
  • QTextCursor::Up
    カーソルを1行上へ移動します。
  • QTextCursor::Right
    カーソルを右へ1文字移動します。
  • QTextCursor::Left
    カーソルを左へ1文字移動します。
  • QTextCursor::NoMove
    カーソル位置を変更しません。

使用例

#include <QApplication>
#include <QPlainTextEdit>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPlainTextEdit textEdit;
    textEdit.setPlainText("これは一行目のテキストです。\nこれは二行目のテキストです。\nこれは三行目のテキストです。");
    textEdit.show();

    // カーソルを一行下の先頭へ移動
    textEdit.moveCursor(QTextCursor::Down);
    textEdit.moveCursor(QTextCursor::StartOfLine);

    // カーソルをドキュメントの末尾へ移動し、そこから前の単語を選択
    textEdit.moveCursor(QTextCursor::EndOfDocument);
    textEdit.moveCursor(QTextCursor::PreviousWord, QTextCursor::KeepAnchor);

    return a.exec();
}

この例では、QPlainTextEdit にテキストを設定し、moveCursor() メソッドを使ってカーソルを移動させたり、テキストを選択したりしています。

  • 現在のカーソル位置を取得したり、より複雑なカーソル操作を行ったりする場合は、QPlainTextEdit::textCursor() メソッドで QTextCursor オブジェクトを取得し、そのオブジェクトのメソッドを使用します。
  • moveCursor() メソッドは、QPlainTextEdit が内部的に保持している QTextCursor オブジェクトを操作します。


一般的なエラーとトラブルシューティング

QPlainTextEdit::moveCursor() 自体は比較的単純なメソッドですが、その使用方法やコンテキストによっては意図しない動作やエラーが発生することがあります。以下に一般的なケースとその対処法を説明します。

無効な QTextCursor::MoveOperation の使用

  • トラブルシューティング
    • どの移動操作が現在のテキストエディタの状態に適しているかをよく理解する必要があります。
    • テキストの内容やカーソル位置に応じて、適切な MoveOperation を選択するようにロジックを組む必要があります。
    • デバッグ時には、カーソル移動前後のカーソル位置をログ出力するなどして、意図通りに移動しているか確認すると良いでしょう。
  • エラー
    コンパイルエラーは少ないですが、論理的な誤りとして、意図しない移動操作を指定してしまうことがあります。例えば、空のテキストエディタに対して QTextCursor::NextWord を呼び出しても何も起こりません。

QTextCursor::MoveMode の誤用

  • トラブルシューティング
    • テキストを選択する必要があるかどうかを明確にし、目的に合った MoveMode を選択してください。
    • 選択範囲をプログラム的に制御したい場合は、QTextCursor オブジェクトを直接操作する(setPosition(), setSelection() など)方が柔軟な場合があります。
  • エラー
    QTextCursor::KeepAnchor を意図せず使用してしまい、予期しないテキスト選択が発生することがあります。逆に、テキストを選択したい場面でデフォルトの QTextCursor::MoveAnchor を使用してしまうと、選択が行われません。

カーソルが範囲外に移動しようとする

  • トラブルシューティング
    • カーソルが移動しようとしている方向の境界(ドキュメントの最初や最後、行の最初や最後など)に達しているかどうかを事前に確認する必要がある場合があります。
    • 必要であれば、現在のカーソル位置を取得 (QPlainTextEdit::textCursor().position()) し、テキストの長さを取得 (QPlainTextEdit::document()->characterCount()) して、移動が可能かどうかを判断するロジックを追加します。
  • エラー
    例えば、ドキュメントの先頭で QTextCursor::Up を呼び出したり、末尾で QTextCursor::Down を呼び出したりしても、カーソル位置は変わりません。これはエラーではありませんが、期待した動作にならない可能性があります。

QPlainTextEdit が空の場合

  • トラブルシューティング
    • moveCursor() を呼び出す前に、QPlainTextEdit にテキストが設定されていることを確認してください。
    • 空の場合の処理を別途実装する必要があるかもしれません。
  • エラー
    空の QPlainTextEdit に対して moveCursor() を呼び出しても、ほとんどの操作は何も影響を与えません。これはエラーではありませんが、期待する動作ではない可能性があります。

他の操作との競合

  • トラブルシューティング
    • カーソル操作が重要な場合は、他の処理との同期を考慮する必要があるかもしれません。
    • シグナルとスロットの接続順序や、イベントの処理順序などを確認してください。
  • エラー
    スロットやイベントハンドラの中で moveCursor() を使用する場合、他の処理(例えば、ユーザーの入力や他のカーソル移動処理)とタイミングが重なり、予期しないカーソル位置になることがあります。

QTextCursor オブジェクトの有効性

  • トラブルシューティング
    • カーソル操作を行う直前に QPlainTextEdit::textCursor() を呼び出して、常に最新の QTextCursor オブジェクトを使用するように心がけてください。
  • エラー
    まれに、QPlainTextEdit::textCursor() で取得した QTextCursor オブジェクトが、その後の QPlainTextEdit の状態変化によって無効になるようなケースが考えられます(ただし、通常は Qt が適切に管理します)。

カスタムテキストレイアウトの影響

  • トラブルシューティング
    • カスタムレイアウトの実装を再確認し、moveCursor() の各 MoveOperation がどのように影響を受けるかを理解する必要があります。
  • エラー
    QPlainTextEdit のテキストレイアウトをカスタマイズしている場合、単語や行の区切りがデフォルトとは異なる解釈になる可能性があり、moveCursor() の動作が期待通りにならないことがあります。
  • ステップ実行
    デバッガを使用して、moveCursor() が呼び出される際のプログラムの状態をステップ実行で確認します。
  • 選択範囲の確認
    QPlainTextEdit::textCursor().selectionStart()QPlainTextEdit::textCursor().selectionEnd() を使用して、選択範囲が意図通りになっているか確認します。
  • カーソル位置の確認
    QPlainTextEdit::textCursor().position() を使用して、カーソル移動前後の位置をログ出力するなどして確認します。


基本的なカーソル移動

#include <QApplication>
#include <QPlainTextEdit>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPlainTextEdit textEdit;
    textEdit.setPlainText("これは一行目のテキストです。\nこれは二行目のテキストです。\nこれは三行目のテキストです。");
    textEdit.show();

    QTextCursor cursor = textEdit.textCursor();

    // カーソルを一行下に移動
    cursor.movePosition(QTextCursor::Down);
    textEdit.setTextCursor(cursor);
    qDebug() << "一行下へ移動後のカーソル位置:" << cursor.position();

    // カーソルを右に3文字移動
    for (int i = 0; i < 3; ++i) {
        cursor.movePosition(QTextCursor::Right);
    }
    textEdit.setTextCursor(cursor);
    qDebug() << "右へ3文字移動後のカーソル位置:" << cursor.position();

    // カーソルを行の先頭に移動
    cursor.movePosition(QTextCursor::StartOfLine);
    textEdit.setTextCursor(cursor);
    qDebug() << "行の先頭へ移動後のカーソル位置:" << cursor.position();

    return a.exec();
}

この例では、まず QPlainTextEdit にテキストを設定し、textCursor() メソッドで現在のカーソルを取得しています。その後、QTextCursor::DownQTextCursor::RightQTextCursor::StartOfLine などの MoveOperationmovePosition() メソッドに渡してカーソルを移動させています。移動後のカーソル位置は position() メソッドで取得し、デバッグ出力しています。最後に、移動後の QTextCursor オブジェクトを setTextCursor()QPlainTextEdit に設定することで、GUI 上のカーソル位置が更新されます。

テキストの選択を伴うカーソル移動

#include <QApplication>
#include <QPlainTextEdit>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPlainTextEdit textEdit;
    textEdit.setPlainText("これは選択されるテキストです。");
    textEdit.moveCursor(QTextCursor::StartOfDocument); // カーソルを先頭に移動
    textEdit.show();

    // カーソルを末尾まで移動しながらテキストを選択
    textEdit.moveCursor(QTextCursor::EndOfDocument, QTextCursor::KeepAnchor);
    qDebug() << "全選択後の選択開始位置:" << textEdit.textCursor().selectionStart();
    qDebug() << "全選択後の選択終了位置:" << textEdit.textCursor().selectionEnd();
    qDebug() << "全選択後の選択されたテキスト:" << textEdit.textCursor().selectedText();

    // 選択を解除してカーソルを先頭に戻す
    QTextCursor cursor = textEdit.textCursor();
    cursor.clearSelection();
    cursor.movePosition(QTextCursor::StartOfDocument);
    textEdit.setTextCursor(cursor);

    // 次の単語まで移動しながらテキストを選択
    textEdit.moveCursor(QTextCursor::NextWord, QTextCursor::KeepAnchor);
    qDebug() << "次の単語まで選択後の選択されたテキスト:" << textEdit.textCursor().selectedText();

    return a.exec();
}

この例では、moveCursor() の第二引数に QTextCursor::KeepAnchor を指定することで、カーソル移動中にテキストが選択される様子を示しています。selectionStart()selectionEnd()selectedText() メソッドを使って、選択範囲の開始位置、終了位置、そして選択されたテキストの内容を取得しています。また、clearSelection() メソッドで選択を解除する方法も示しています。

特定の行や段落への移動

#include <QApplication>
#include <QPlainTextEdit>
#include <QDebug>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QPlainTextEdit textEdit;
    textEdit.setPlainText("一行目\n二行目\n三行目\n\n一段落目。\n二段落目。");
    textEdit.show();

    // カーソルを3行目の先頭に移動
    QTextCursor cursor = textEdit.textCursor();
    for (int i = 0; i < 2; ++i) {
        cursor.movePosition(QTextCursor::Down);
    }
    cursor.movePosition(QTextCursor::StartOfLine);
    textEdit.setTextCursor(cursor);
    qDebug() << "3行目の先頭へのカーソル位置:" << cursor.position();

    // カーソルを次の段落の先頭に移動
    cursor.movePosition(QTextCursor::NextBlock);
    textEdit.setTextCursor(cursor);
    qDebug() << "次の段落の先頭へのカーソル位置:" << cursor.position();

    return a.exec();
}

この例では、ループを使って複数行下へ移動したり、QTextCursor::NextBlock を使って次の段落の先頭へ移動したりする方法を示しています。

イベントに応じたカーソル移動

#include <QApplication>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QVBoxLayout>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget window;
    QVBoxLayout layout(&window);
    QPlainTextEdit textEdit;
    textEdit.setPlainText("テキスト\nテキスト\nテキスト");
    QPushButton upButton("上へ");
    QPushButton downButton("下へ");

    layout.addWidget(&textEdit);
    layout.addWidget(&upButton);
    layout.addWidget(&downButton);
    window.show();

    QObject::connect(&upButton, &QPushButton::clicked, [&]() {
        textEdit.moveCursor(QTextCursor::Up);
    });

    QObject::connect(&downButton, &QPushButton::clicked, [&]() {
        textEdit.moveCursor(QTextCursor::Down);
    });

    return a.exec();
}

この例では、ボタンのクリックイベントに応じて moveCursor() を呼び出し、QPlainTextEdit のカーソルを上下に移動させる簡単なGUIアプリケーションを示しています。ラムダ式を使って、ボタンがクリックされたときに実行する処理を簡潔に記述しています。



QTextCursor オブジェクトを直接操作する

QPlainTextEdit のカーソル操作の基本は QTextCursor クラスです。QPlainTextEdit::textCursor() メソッドで QTextCursor オブジェクトを取得し、そのオブジェクトのメソッドを直接操作することで、より詳細な制御が可能です。

  • テキストの挿入・削除
    insertText(), deleteChar(), deletePreviousChar(), removeSelectedText() などのメソッドを使ってテキストを編集すると、カーソル位置もそれに伴って変化します。
  • setSelection(int anchor, int position)
    選択範囲の開始位置 anchor と終了位置 position を明示的に設定します。
  • select(QTextCursor::SelectionType selection)
    あらかじめ定義された選択範囲(例えば、現在の単語、現在の行、現在のブロックなど)を選択します。
  • movePosition(QTextCursor::MoveOperation op, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor, int n = 1)
    moveCursor() と同様の移動操作を行いますが、移動量 n を指定できます。例えば、QTextCursor::Rightn=5 で指定すると、カーソルを右に5文字移動します。
  • setPosition(int pos, QTextCursor::MoveMode mode = QTextCursor::MoveAnchor)
    カーソルの位置をドキュメント内の絶対的な文字インデックス pos に設定します。modeQTextCursor::KeepAnchor にすると、移動前の位置から pos までのテキストが選択されます。


QTextCursor cursor = textEdit->textCursor();
// カーソルをドキュメントの5文字目に移動
cursor.setPosition(5);
textEdit->setTextCursor(cursor);

// 現在の単語を選択
cursor.select(QTextCursor::WordUnderCursor);
textEdit->setTextCursor(cursor);

// カーソルを現在の位置から10文字右に移動し、選択
int start = cursor.position();
cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, 10);
int end = cursor.position();
textEdit->setTextCursor(cursor);
qDebug() << "選択範囲:" << start << " - " << end;

シグナルとスロットの利用

QPlainTextEdit は、カーソル位置が変化した際に cursorPositionChanged() シグナルを発行します。このシグナルにスロットを接続することで、カーソル位置の変化を監視し、必要に応じて他の処理を行うことができます。


connect(textEdit, &QPlainTextEdit::cursorPositionChanged, this, &MyClass::onCursorPositionChanged);

void MyClass::onCursorPositionChanged()
{
    QTextCursor currentCursor = textEdit->textCursor();
    qDebug() << "カーソル位置が変更されました:" << currentCursor.position();
    // 必要に応じて他の処理を行う
}

この方法では、moveCursor() を直接呼び出す代わりに、ユーザーの操作や他のプログラム的な変更によってカーソル位置が変化した際に、自動的に処理を実行できます。

テキスト操作による間接的なカーソル移動

テキストの挿入や削除を行うと、カーソルは通常、その操作の終了位置に移動します。これを利用して、間接的にカーソルを目的の位置に移動させることができます。


// 特定の行の末尾に移動したい場合(行番号がわかっている場合)
int lineNumber = 2;
QTextCursor cursor = textEdit->textCursor();
cursor.movePosition(QTextCursor::StartOfDocument);
for (int i = 0; i < lineNumber - 1; ++i) {
    cursor.movePosition(QTextCursor::Down);
}
cursor.movePosition(QTextCursor::EndOfLine);
textEdit->setTextCursor(cursor);

この例では、moveCursor() を繰り返し使用して目的の行まで移動していますが、より複雑なテキスト解析を行って行頭や行末の情報を取得し、setPosition() を使用することも可能です。

ビューポートの操作

QPlainTextEdit はスクロール可能なビューポートを持っています。ensureCursorVisible() メソッドを使用すると、現在のカーソル位置がビューポート内に表示されるようにスクロールすることができます。これは直接的なカーソル移動ではありませんが、ユーザーに特定のカーソル位置を見せたい場合に役立ちます。


textEdit->moveCursor(QTextCursor::EndOfDocument);
textEdit->ensureCursorVisible();
  • ユーザーに特定のカーソル位置を見せたい場合
    ensureCursorVisible() を使用します。
  • テキスト編集の結果としてカーソルが移動することを期待する場合
    insertText()delete() などのテキスト操作メソッドを使用します。
  • カーソル位置の変化に応じて処理を行いたい場合
    cursorPositionChanged() シグナルを利用します。
  • 絶対的な位置や詳細な制御が必要な場合
    QTextCursor オブジェクトを直接操作する (setPosition(), movePosition() など) のが適しています。
  • 特定の操作に基づいてカーソルを移動させたい場合 (例: 次の単語へ、前の行へ)
    moveCursor() が簡潔で便利です。