QPlainTextEditの行数変化を検知!プログラミングサンプルコードと応用例

2025-04-26

QPlainTextEdit::blockCountChanged()とは?

QPlainTextEditは、プレーンテキストを編集するためのウィジェットです。このウィジェットは、テキストを行(ブロック)単位で管理します。blockCountChanged()シグナルは、このQPlainTextEdit内のテキストのブロック数が変化した際に発行されるシグナルです。

どのような時に発行されるのか?

具体的には、以下の様な場合にblockCountChanged()シグナルが発行されます。

  • テキストのクリア
    clear()関数などを呼び出してテキストが全て削除された場合。
  • テキストの置換
    テキストを置換して行数が変化した場合。
  • テキストの削除
    ユーザーがテキストを削除して改行が消えたり、プログラムでテキストを削除して行数が減った場合。
  • テキストの追加
    ユーザーが新しい行を入力したり、プログラムでテキストを追加して改行が発生した場合。

なぜ使うのか?

blockCountChanged()シグナルは、主に以下の様な目的で使用されます。

  • テキストの構造変化の監視
    テキストの構造変化を監視し、特定の処理を実行する場合(例:特定行数のテキストが追加された場合に処理を実行する)にも使えます。
  • スクロールバーの調整
    テキストの行数に応じてスクロールバーの範囲を調整する必要がある場合、このシグナルを捕捉してスクロールバーの設定を更新することができます。
  • 行番号表示の更新
    テキストエディタの横に行番号を表示する場合、テキストの行数が変化するたびに表示を更新する必要があります。このシグナルを捕捉して、行番号表示を更新する関数を呼び出すことができます。

コード例(簡単な行番号表示の更新)

以下は、blockCountChanged()シグナルを使用して、行番号を表示する簡単な例です。

#include <QApplication>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QLabel>

class MyEditor : public QWidget {
public:
    MyEditor(QWidget *parent = nullptr) : QWidget(parent) {
        QVBoxLayout *layout = new QVBoxLayout(this);

        plainTextEdit = new QPlainTextEdit(this);
        lineNumberLabel = new QLabel("1", this);

        layout->addWidget(lineNumberLabel);
        layout->addWidget(plainTextEdit);

        connect(plainTextEdit, &QPlainTextEdit::blockCountChanged, this, &MyEditor::updateLineNumbers);

        updateLineNumbers(); // 初期化
    }

private slots:
    void updateLineNumbers() {
        int blockCount = plainTextEdit->blockCount();
        QString lineNumbers;
        for (int i = 1; i <= blockCount; ++i) {
            lineNumbers += QString::number(i) + "\n";
        }
        lineNumberLabel->setText(lineNumbers);
    }

private:
    QPlainTextEdit *plainTextEdit;
    QLabel *lineNumberLabel;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    MyEditor editor;
    editor.show();
    return app.exec();
}

この例では、blockCountChanged()シグナルが発行されるたびにupdateLineNumbers()関数が呼び出され、行番号表示が更新されます。



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

    • 原因
      • テキストの変更がQPlainTextEditのブロック数に影響を与えていない。例えば、改行を伴わないテキストの追加や削除など。
      • シグナルとスロットの接続が正しく行われていない。
      • QPlainTextEditが期待する状態になっていない(初期化されていない、表示されていないなど)。
    • トラブルシューティング
      • テキストの変更が実際にブロック数を変化させているか確認する。
      • connect()関数でシグナルとスロットが正しく接続されているか確認する。特に、シグナルの型とスロットの型が一致しているか確認する。
      • QPlainTextEditが正常に初期化され、表示されているか確認する。
      • デバッガを使用して、シグナルが発行されるべき箇所にブレークポイントを設定し、シグナルが発行されているか確認する。
      • qDebug()を使用して、シグナルが発行されるタイミングでメッセージを出力し、シグナルが発行されているか確認する。
  1. スロット関数が期待通りに実行されない

    • 原因
      • スロット関数内の処理にエラーがある。
      • スロット関数内の処理が時間のかかる処理であるため、UIがフリーズしているように見える。
      • スロット関数内の処理が、他のスレッドからUIスレッドにアクセスしようとしており、スレッドセーフではない。
    • トラブルシューティング
      • スロット関数内の処理をデバッガでステップ実行し、エラー箇所を特定する。
      • 時間のかかる処理は、別のスレッドで実行するか、イベントループをブロックしないように処理を分割する。
      • UIスレッド以外からUI要素にアクセスする場合は、Qt::QueuedConnectionを使用してシグナルとスロットを接続するか、QMetaObject::invokeMethod()を使用してUIスレッドで処理を実行する。
      • スロット関数の処理が他のスレッドと競合していないか確認する。
  2. 行番号表示の更新が遅延する、または正しく表示されない

    • 原因
      • 行番号表示の更新処理が複雑で、時間がかかっている。
      • 行番号表示の更新処理が、テキストの変更と同期していない。
      • 行番号表示の更新処理で、QPlainTextEditのブロック数を誤って取得している。
    • トラブルシューティング
      • 行番号表示の更新処理を最適化し、処理時間を短縮する。
      • blockCountChanged()シグナルが発行された直後に、行番号表示を更新するようにする。
      • QPlainTextEdit::blockCount()関数を使用して、正しいブロック数を取得しているか確認する。
      • 行番号を表示しているウィジェットの更新処理が適切に行われているか確認する。
  3. スクロールバーの調整が正しく行われない

    • 原因
      • スクロールバーの範囲を計算する処理が間違っている。
      • スクロールバーの設定を更新するタイミングが間違っている。
      • スクロールバーの表示/非表示が適切に制御されていない。
    • トラブルシューティング
      • スクロールバーの範囲を計算する処理を見直し、正しい範囲を計算するようにする。
      • blockCountChanged()シグナルが発行された直後に、スクロールバーの設定を更新するようにする。
      • スクロールバーの表示/非表示を制御する処理を見直し、適切なタイミングで表示/非表示を切り替えるようにする。

デバッグのヒント

  • Qtのドキュメントやオンラインフォーラムを参照し、同様の問題が発生していないか確認する。
  • Qtのシグナルとスロットの仕組みを理解し、シグナルとスロットがどのように連携して動作するかを把握する。
  • デバッガを使用して、シグナルとスロットの接続やスロット関数内の処理をステップ実行し、エラー箇所を特定する。
  • qDebug()を使用して、シグナルが発行されるタイミングやスロット関数が実行されるタイミングでメッセージを出力し、処理の流れを確認する。


#include <QApplication>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QLabel>
#include <QStringList>

class LineNumberEditor : public QWidget {
public:
    LineNumberEditor(QWidget *parent = nullptr) : QWidget(parent) {
        QVBoxLayout *layout = new QVBoxLayout(this);

        plainTextEdit = new QPlainTextEdit(this);
        lineNumberLabel = new QLabel(this);

        layout->addWidget(lineNumberLabel);
        layout->addWidget(plainTextEdit);

        connect(plainTextEdit, &QPlainTextEdit::blockCountChanged, this, &LineNumberEditor::updateLineNumbers);

        updateLineNumbers(); // 初期表示
    }

private slots:
    void updateLineNumbers() {
        int blockCount = plainTextEdit->blockCount();
        QStringList lineNumbers;
        for (int i = 1; i <= blockCount; ++i) {
            lineNumbers << QString::number(i);
        }
        lineNumberLabel->setText(lineNumbers.join("\n"));
    }

private:
    QPlainTextEdit *plainTextEdit;
    QLabel *lineNumberLabel;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    LineNumberEditor editor;
    editor.show();
    return app.exec();
}

説明

  • 初期表示のために、コンストラクタ内でupdateLineNumbers()を一度呼び出しています。
  • QStringList::join("\n")を使用して、連番を改行区切りの文字列に変換し、QLabelに表示します。
  • updateLineNumbers()スロットでは、QPlainTextEdit::blockCount()を使用して現在のブロック数を取得し、1からブロック数までの連番をQStringListに格納します。
  • blockCountChanged()シグナルが発行されると、updateLineNumbers()スロットが呼び出されます。
  • LineNumberEditorクラスは、QPlainTextEditと行番号を表示するQLabelを組み合わせたカスタムウィジェットです。
#include <QApplication>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QScrollBar>

class ScrollBarAdjuster : public QWidget {
public:
    ScrollBarAdjuster(QWidget *parent = nullptr) : QWidget(parent) {
        QVBoxLayout *layout = new QVBoxLayout(this);

        plainTextEdit = new QPlainTextEdit(this);
        layout->addWidget(plainTextEdit);

        connect(plainTextEdit, &QPlainTextEdit::blockCountChanged, this, &ScrollBarAdjuster::adjustScrollBar);

        adjustScrollBar(); // 初期調整
    }

private slots:
    void adjustScrollBar() {
        int blockCount = plainTextEdit->blockCount();
        QScrollBar *verticalScrollBar = plainTextEdit->verticalScrollBar();
        if (blockCount > 10) { // 例:10行を超えたらスクロールバーを表示
            verticalScrollBar->setEnabled(true);
            verticalScrollBar->setRange(0, blockCount - 1); //スクロール範囲の設定
        } else {
            verticalScrollBar->setEnabled(false);
            verticalScrollBar->setValue(0); //スクロール位置を初期値に戻す
        }
    }

private:
    QPlainTextEdit *plainTextEdit;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    ScrollBarAdjuster adjuster;
    adjuster.show();
    return app.exec();
}

説明

  • QScrollBar::setEnabled()でスクロールバーの有効/無効を切り替え、QScrollBar::setRange()でスクロール範囲を設定します。
  • 初期調整のために、コンストラクタ内でadjustScrollBar()を一度呼び出しています。
  • adjustScrollBar()スロットでは、QPlainTextEdit::blockCount()を使用して現在のブロック数を取得し、ブロック数が10を超えた場合にスクロールバーを有効化し、範囲を設定します。10行以下の場合スクロールバーを無効にし、スクロール位置を初期値に戻します。
  • blockCountChanged()シグナルが発行されると、adjustScrollBar()スロットが呼び出されます。
  • ScrollBarAdjusterクラスは、QPlainTextEditのブロック数に応じてスクロールバーを調整するカスタムウィジェットです。
#include <QApplication>
#include <QPlainTextEdit>
#include <QMessageBox>
#include <QVBoxLayout>
#include <QWidget>

class BlockCountMonitor : public QWidget {
public:
    BlockCountMonitor(QWidget *parent = nullptr) : QWidget(parent) {
        QVBoxLayout *layout = new QVBoxLayout(this);

        plainTextEdit = new QPlainTextEdit(this);
        layout->addWidget(plainTextEdit);

        connect(plainTextEdit, &QPlainTextEdit::blockCountChanged, this, &BlockCountMonitor::checkBlockCount);
    }

private slots:
    void checkBlockCount() {
        int blockCount = plainTextEdit->blockCount();
        if (blockCount > 20) {
            QMessageBox::information(this, "行数超過", "行数が20行を超えました!");
            disconnect(plainTextEdit, &QPlainTextEdit::blockCountChanged, this, &BlockCountMonitor::checkBlockCount); //メッセージ表示後、シグナルを解除
        }
    }

private:
    QPlainTextEdit *plainTextEdit;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    BlockCountMonitor monitor;
    monitor.show();
    return app.exec();
}
  • メッセージを表示した後、disconnect()関数を使用して、blockCountChanged()シグナルとcheckBlockCount()スロットの接続を解除します。これによって、メッセージが何度も表示されるのを防ぎます。
  • checkBlockCount()スロットでは、QPlainTextEdit::blockCount()を使用して現在のブロック数を取得し、ブロック数が20を超えた場合にQMessageBox::information()を使用してメッセージボックスを表示します。
  • blockCountChanged()シグナルが発行されると、checkBlockCount()スロットが呼び出されます。
  • BlockCountMonitorクラスは、QPlainTextEditの行数が20行を超えたらメッセージボックスを表示するカスタムウィジェットです。


QPlainTextEdit::textChanged() シグナルを使う

textChanged()シグナルは、QPlainTextEditのテキストが変更されるたびに発行されます。このシグナルを利用して、テキストの変更後にブロック数を再計算し、必要な処理を行うことができます。

#include <QApplication>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QLabel>
#include <QStringList>

class LineNumberEditor : public QWidget {
public:
    LineNumberEditor(QWidget *parent = nullptr) : QWidget(parent) {
        QVBoxLayout *layout = new QVBoxLayout(this);

        plainTextEdit = new QPlainTextEdit(this);
        lineNumberLabel = new QLabel(this);

        layout->addWidget(lineNumberLabel);
        layout->addWidget(plainTextEdit);

        connect(plainTextEdit, &QPlainTextEdit::textChanged, this, &LineNumberEditor::updateLineNumbers);

        updateLineNumbers(); // 初期表示
    }

private slots:
    void updateLineNumbers() {
        int blockCount = plainTextEdit->blockCount();
        QStringList lineNumbers;
        for (int i = 1; i <= blockCount; ++i) {
            lineNumbers << QString::number(i);
        }
        lineNumberLabel->setText(lineNumbers.join("\n"));
    }

private:
    QPlainTextEdit *plainTextEdit;
    QLabel *lineNumberLabel;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    LineNumberEditor editor;
    editor.show();
    return app.exec();
}

利点

  • blockCountChanged()よりも多くの状況で処理をトリガーできる。
  • テキストの変更をより細かく検知できる。

欠点

  • ブロック数に関係ないテキスト変更でも処理が実行される。
  • blockCountChanged()よりも頻繁にシグナルが発行されるため、処理が重くなる可能性がある。

QPlainTextEdit::document()->contentsChanged() シグナルを使う

QPlainTextEditのドキュメントのコンテンツが変更されたときに発行されるシグナルです。こちらもテキストの変更を検知できますが、textChanged()よりも少し抽象的なレベルでの変更を検知します。

// 例:QPlainTextEditのドキュメントの変更を検知
connect(plainTextEdit->document(), &QTextDocument::contentsChanged, this, &MyClass::handleContentsChanged);

void MyClass::handleContentsChanged() {
    // ドキュメントのコンテンツが変更されたときの処理
    updateLineNumbers(); //ブロック数を再計算して更新
}

利点

  • textChanged()よりも少し抽象的なレベルでの変更を検知できる。
  • ドキュメント全体の変更を検知できる。

欠点

  • ドキュメントの変更の具体的な内容を知るには、追加の処理が必要になる場合がある。
  • blockCountChanged()よりも頻繁にシグナルが発行される可能性がある。

タイマーを使う

一定時間ごとにQPlainTextEditのブロック数をチェックし、変更があった場合に処理を行う方法です。

#include <QApplication>
#include <QPlainTextEdit>
#include <QTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QLabel>
#include <QTimer>

class LineNumberEditor : public QWidget {
public:
    LineNumberEditor(QWidget *parent = nullptr) : QWidget(parent), previousBlockCount(0) {
        QVBoxLayout *layout = new QVBoxLayout(this);

        plainTextEdit = new QPlainTextEdit(this);
        lineNumberLabel = new QLabel(this);

        layout->addWidget(lineNumberLabel);
        layout->addWidget(plainTextEdit);

        timer = new QTimer(this);
        connect(timer, &QTimer::timeout, this, &LineNumberEditor::checkBlockCount);
        timer->start(100); // 100ミリ秒ごとにチェック
        updateLineNumbers(); // 初期表示
    }

private slots:
    void checkBlockCount() {
        int currentBlockCount = plainTextEdit->blockCount();
        if (currentBlockCount != previousBlockCount) {
            previousBlockCount = currentBlockCount;
            updateLineNumbers();
        }
    }

    void updateLineNumbers() {
        int blockCount = plainTextEdit->blockCount();
        QStringList lineNumbers;
        for (int i = 1; i <= blockCount; ++i) {
            lineNumbers << QString::number(i);
        }
        lineNumberLabel->setText(lineNumbers.join("\n"));
    }

private:
    QPlainTextEdit *plainTextEdit;
    QLabel *lineNumberLabel;
    QTimer *timer;
    int previousBlockCount;
};

int main(int argc, char *argv[]) {
    QApplication app(argc, argv);
    LineNumberEditor editor;
    editor.show();
    return app.exec();
}

利点

  • 処理のタイミングを細かく制御できる。
  • シグナルを使わないため、シグナルの接続やスロットの管理が不要になる。

欠点

  • リアルタイム性が低くなる。
  • 定期的にブロック数をチェックするため、処理が重くなる可能性がある。

QPlainTextEdit::insertPlainText()やQPlainTextEdit::removeSelectedText()などをオーバーライドする

QPlainTextEditを継承し、テキストの挿入や削除などの関数をオーバーライドすることで、ブロック数の変更を検知し、必要な処理を行うことができます。

利点

  • 独自の処理を実装しやすい。
  • テキストの変更をより細かく制御できる。
  • QPlainTextEditの内部構造を理解する必要がある。
  • QPlainTextEditの継承が必要になるため、コードが複雑になる可能性がある。