QTextEdit textChanged()の代替手段:Qtテキスト変更イベントの選び方

2025-05-27

Qtプログラミングにおけるvoid QTextEdit::textChanged()は、QTextEditウィジェットの非常に重要なシグナルです。

QTextEditは、複数行のテキスト(プレーンテキストまたはリッチテキスト)を表示・編集するためのウィジェットです。ユーザーがテキストを入力したり、プログラムからテキストが変更されたりするなど、QTextEditの内容が変更されると、このtextChanged()シグナルが**発行(emit)**されます。

  1. シグナル(Signal)であること
    textChanged()は関数(メソッド)のように直接呼び出すものではなく、Qtのシグナル&スロットメカニズムの一部です。何か特定のイベントが発生したときに「通知」を送信する役割を果たします。

  2. テキスト内容の変更を検出
    QTextEdit内のテキストが変更されるたびにこのシグナルが発行されます。これには、ユーザーがキーボードで入力したり、貼り付けたりするだけでなく、setText()などのメソッドを使ってプログラム的にテキストが変更された場合も含まれます。

  3. スロット(Slot)との接続
    このシグナルは、通常、その変更を処理するための「スロット」と呼ばれる別の関数と接続して使用します。例えば、テキストが変更されたら、そのテキストの文字数をカウントして表示する、といった処理を行うスロットを接続できます。

    例 (C++)

    // QTextEdit のインスタンス
    QTextEdit *textEdit = new QTextEdit(this);
    
    // textChanged() シグナルを myTextChangedHandler() スロットに接続
    // C++11 以降のラムダ式を使った接続が一般的です
    connect(textEdit, &QTextEdit::textChanged, this, [=]() {
        // ここにテキスト変更時に実行したい処理を書く
        qDebug() << "テキストが変更されました!";
        qDebug() << "現在のテキスト:" << textEdit->toPlainText();
    });
    
    // または、通常のメンバースロットに接続する場合
    // connect(textEdit, &QTextEdit::textChanged, this, &MyClass::myTextChangedHandler);
    // ...
    // void MyClass::myTextChangedHandler() {
    //     qDebug() << "テキストが変更されました!";
    // }
    

    例 (Python - PyQt)

    from PyQt5.QtWidgets import QApplication, QTextEdit, QWidget, QVBoxLayout
    from PyQt5.QtCore import Qt
    
    class MyWindow(QWidget):
        def __init__(self):
            super().__init__()
            self.initUI()
    
        def initUI(self):
            self.text_edit = QTextEdit(self)
            self.text_edit.textChanged.connect(self.on_text_changed) # シグナルとスロットの接続
    
            layout = QVBoxLayout()
            layout.addWidget(self.text_edit)
            self.setLayout(layout)
    
            self.setWindowTitle('QTextEdit Text Changed Example')
            self.setGeometry(100, 100, 400, 300)
    
        def on_text_changed(self):
            print("テキストが変更されました!")
            print("現在のテキスト:", self.text_edit.toPlainText())
    
    if __name__ == '__main__':
        app = QApplication([])
        window = MyWindow()
        window.show()
        app.exec_()
    
  4. 引数がないこと
    QTextEdit::textChanged()シグナルは、引数を持ちません。テキストの内容そのものが変更されたことを通知するだけです。もし、変更後のテキストの内容を取得したい場合は、接続されたスロットの中でQTextEdit::toPlainText()メソッドなどを呼び出して取得する必要があります。

  5. textEdited()との違い
    QTextEditには、似たようなシグナルとしてtextEdited()もあります。

    • textChanged(): テキストの内容が何らかの方法で変更された場合に発行されます(ユーザーの入力、プログラムによる変更など)。
    • textEdited(): テキストがユーザーによって編集された場合にのみ発行されます(キーボード入力、マウスによるドラッグ&ドロップなど)。プログラムによるsetText()では発行されません。

    一般的には、ユーザーとプログラムの両方からの変更に対応したい場合はtextChanged()を使用し、ユーザーの操作のみに反応したい場合はtextEdited()を使用します。



無限ループ(Infinite Loop)

これは最も一般的で危険な問題です。textChanged()シグナルに接続されたスロットの中で、再度QTextEditの内容を変更するような処理(例: setText(), insertPlainText(), append()など)を行うと、その変更がまたtextChanged()シグナルを発行し、スロットが呼び出され、...というように無限ループに陥ることがあります。

症状

  • デバッガでスタックトレースを見ると、同じ関数が繰り返し呼び出されている。
  • CPU使用率が100%になる。
  • アプリケーションがフリーズする、応答しなくなる。

解決策

  • フラグを使用する
    スロットが実行中であることを示すブール型フラグを導入し、フラグが設定されている間は再帰的な処理をスキップするようにします。

    bool m_isUpdatingText = false; // クラスのメンバ変数
    
    void MyClass::onTextChanged() {
        if (m_isUpdatingText) {
            return; // 既に更新中なら何もしない
        }
    
        m_isUpdatingText = true;
        // ここでテキストを変更する処理
        m_isUpdatingText = false;
    }
    
  • QTextDocument::blockSignals()を使用する
    QTextEditは内部的にQTextDocumentを使用しており、textChanged()シグナルは基本的にQTextDocumentからの変更が伝播したものです。より厳密にシグナルを制御したい場合、QTextEdit::document()->blockSignals(true)を使用することも可能です。

  • シグナルを一時的にブロックする
    スロット内でQTextEditの内容をプログラム的に変更する前に、blockSignals(true)を使ってシグナルの発行を一時的に停止し、変更後にblockSignals(false)で再開します。

    void MyClass::onTextChanged() {
        QTextEdit* textEdit = qobject_cast<QTextEdit*>(sender());
        if (textEdit) {
            textEdit->blockSignals(true); // シグナルを一時的にブロック
            // ここでテキストを変更する処理 (例: textEdit->setText("新しいテキスト");)
            textEdit->blockSignals(false); // シグナルを再開
        }
    }
    

    Python (PyQt)の場合も同様です。

    def on_text_changed(self):
        self.text_edit.blockSignals(True)
        # ここでテキストを変更する処理 (例: self.text_edit.setText("新しいテキスト"))
        self.text_edit.blockSignals(False)
    

不要なシグナル発行(Too Frequent Signals)

textChanged()は、文字を1つ入力するたびに、あるいは書式設定が変更されるたびに発行されます。これにより、重い処理をスロットに接続している場合、パフォーマンスが低下する可能性があります。

症状

  • デバッグ出力が大量に出る。
  • テキスト入力中にラグが発生する。
  • UIの応答が遅くなる。

解決策

  • QTimerを使った遅延処理(Debouncing / Throttling)
    テキスト変更後、一定時間(例: 数百ミリ秒)入力がなければ処理を実行するようにします。これは、検索ボックスのオートコンプリートなどでよく使われる手法です。

    // MyClass.h
    class MyClass : public QObject {
        Q_OBJECT
    public:
        MyClass(QTextEdit* textEdit, QObject* parent = nullptr);
    private slots:
        void onTextChanged();
        void processDelayedText();
    private:
        QTextEdit* m_textEdit;
        QTimer* m_timer;
    };
    
    // MyClass.cpp
    #include <QTimer>
    
    MyClass::MyClass(QTextEdit* textEdit, QObject* parent)
        : QObject(parent), m_textEdit(textEdit) {
        m_timer = new QTimer(this);
        m_timer->setSingleShot(true); // 一度だけ実行
        m_timer->setInterval(500); // 500ミリ秒 (必要に応じて調整)
    
        connect(m_textEdit, &QTextEdit::textChanged, this, &MyClass::onTextChanged);
        connect(m_timer, &QTimer::timeout, this, &MyClass::processDelayedText);
    }
    
    void MyClass::onTextChanged() {
        m_timer->start(); // タイマーを再起動 (前のタイマーがあれば停止して再開)
    }
    
    void MyClass::processDelayedText() {
        // ここに実際に実行したい重い処理を書く
        qDebug() << "最終的なテキストが処理されました:" << m_textEdit->toPlainText();
    }
    
    from PyQt5.QtCore import QTimer
    
    class MyWindow(QWidget):
        def __init__(self):
            # ... (text_editの初期化など)
            self.timer = QTimer(self)
            self.timer.setSingleShot(True)
            self.timer.setInterval(500) # 500ミリ秒
    
            self.text_edit.textChanged.connect(self.on_text_changed)
            self.timer.timeout.connect(self.process_delayed_text)
    
        def on_text_changed(self):
            self.timer.start()
    
        def process_delayed_text(self):
            print("最終的なテキストが処理されました:", self.text_edit.toPlainText())
    
  • textEdited()シグナルの検討
    もしユーザーの直接的な入力のみに反応したい場合は、textChanged()ではなくQTextEdit::textEdited()シグナルを使用することを検討してください。これはプログラム的な変更では発行されません。

初期化時のシグナル発行

アプリケーションの起動時やUIの初期化時に、QTextEdit::setText()などで初期テキストを設定すると、その際にもtextChanged()シグナルが発行されることがあります。これにより、まだ準備ができていないスロットが呼び出されて、予期しない動作を引き起こす場合があります。

症状

  • 初期化ロジックと衝突する。
  • アプリケーション起動時に、意図しない処理が実行される。

解決策

  • フラグの使用
    初期化が完了したことを示すフラグを導入し、そのフラグがtrueになるまでスロットの処理をスキップするようにします。

    bool m_isInitialized = false; // クラスのメンバ変数
    
    // コンストラクタや初期化関数内で
    MyClass::MyClass(...) {
        // ... QTextEdit の初期化など ...
        m_textEdit->setText("初期テキスト"); // この時にシグナルが発行される可能性がある
        m_isInitialized = true; // 初期化完了
    }
    
    void MyClass::onTextChanged() {
        if (!m_isInitialized) {
            return; // 初期化中なら何もしない
        }
        // 通常のテキスト変更処理
    }
    
  • blockSignals()の使用
    初期化処理中にsetText()などを呼び出す前にblockSignals(true)を呼び出し、初期化後にblockSignals(false)を呼び出すことで、初期化時のシグナル発行を抑制できます。

QTextDocumentとQTextEditの区別

QTextEditは内部的にQTextDocumentを持っており、テキストの実際のデータ管理はQTextDocumentが行います。textChanged()シグナルはQTextEditから発行されますが、より詳細な変更イベントはQTextDocumentのシグナル(例: contentsChange(int position, int charsRemoved, int charsAdded), contentsChanged()など)に接続することで取得できます。

症状

  • QTextEdit::setText()ではない、もっと低レベルなドキュメント変更を検出したい。
  • 変更の種類(文字の追加、削除、書式変更など)に応じて異なる処理をしたいが、textChanged()では区別できない。

解決策

  • QTextDocumentのシグナルを利用する
    QTextEdit::document()メソッドでQTextDocumentのインスタンスを取得し、そのシグナルを接続します。

    connect(textEdit->document(), &QTextDocument::contentsChange, this,
            [=](int position, int charsRemoved, int charsAdded) {
        qDebug() << "contentsChange: pos=" << position << ", removed=" << charsRemoved << ", added=" << charsAdded;
        // 変更された内容に応じて処理を分岐
    });
    
    connect(textEdit->document(), &QTextDocument::contentsChanged, this,
            [=]() {
        qDebug() << "ドキュメントの内容が変更されました (書式変更なども含む)";
    });
    

    これにより、よりきめ細やかなテキスト変更のハンドリングが可能になります。

基本的なことですが、シグナルとスロットの接続が正しく行われていない場合、期待通りにtextChanged()が機能しません。

症状

  • ランタイムエラー(Pythonの場合、またはC++で古いSIGNAL/SLOTマクロを使用した場合)。
  • コンパイルエラー(C++の場合)。
  • テキストを変更しても、スロットが呼び出されない。

解決策

  • デバッグ出力の追加
    スロットの先頭に簡単なqDebug()print()を入れて、そもそもスロットが呼び出されているかを確認します。
  • 接続の確認
    • C++11形式のconnect構文を使用しているか (&QTextEdit::textChanged)。
    • スロットの引数とシグナルの引数が一致しているか(textChanged()は引数なし)。
    • オブジェクトが有効なポインタであるか。
    • スロットのアクセス指定子(public slots:private slots:)が適切か(C++の場合)。


テキスト変更をコンソールに出力するだけのシンプルな例

これは、textChanged()シグナルが発行されたことを確認する最も基本的な方法です。

C++ (Qt Widgets Application)

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QTextEdit> // QTextEdit を使うために必要
#include <QVBoxLayout> // レイアウトのために必要
#include <QWidget> // 中央ウィジェットのために必要

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT // シグナルとスロットを使うために必須

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_textEdit_textChanged(); // テキスト変更時に呼び出されるスロット

private:
    Ui::MainWindow *ui;
    QTextEdit *textEdit; // QTextEdit のインスタンス
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include <QDebug> // デバッグ出力のために必要

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(nullptr) // UIファイルを使用しないシンプルな例
{
    // QTextEdit の作成
    textEdit = new QTextEdit(this);
    textEdit->setPlaceholderText("ここに何か入力してください..."); // プレースホルダーテキスト

    // シグナルとスロットの接続
    // textEdit の textChanged() シグナルが発行されたら、
    // MainWindow の on_textEdit_textChanged() スロットを呼び出す
    connect(textEdit, &QTextEdit::textChanged, this, &MainWindow::on_textEdit_textChanged);

    // レイアウトの準備
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(textEdit);

    QWidget *centralWidget = new QWidget(this);
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget); // ウィンドウの中央ウィジェットに設定

    setWindowTitle("QTextEdit textChanged() Example (C++)");
    resize(400, 300);
}

MainWindow::~MainWindow()
{
    // UIファイルを使用しない場合、手動でdeleteする必要はない (親が管理する)
}

// QTextEdit のテキストが変更されたときに実行されるスロット
void MainWindow::on_textEdit_textChanged()
{
    // 現在のテキストを取得してデバッグ出力
    qDebug() << "テキストが変更されました。現在のテキスト:" << textEdit->toPlainText();

    // 例: テキストが特定の内容になったらメッセージを表示
    if (textEdit->toPlainText().contains("Qt")) {
        qDebug() << "Qtという単語が含まれています!";
    }
}

Python (PyQt)

main.py

```python import sys from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QVBoxLayout, QWidget from PyQt5.QtCore import pyqtSlot

class MainWindow(QMainWindow): def init(self): super().init() self.setWindowTitle("QTextEdit textChanged() Example (PyQt)") self.setGeometry(100, 100, 400, 300)

    self.text_edit = QTextEdit(self)
    self.text_edit.setPlaceholderText("ここに何か入力してください...")

    # シグナルとスロットの接続
    # text_edit の textChanged シグナルが発行されたら、
    # on_text_changed スロットを呼び出す
    self.text_edit.textChanged.connect(self.on_text_changed)

    layout = QVBoxLayout()
    layout.addWidget(self.text_edit)

    container = QWidget()
    container.setLayout(layout)
    self.setCentralWidget(container)

@pyqtSlot() # スロットであることを明示 (必須ではないが推奨)
def on_text_changed(self):
    # 現在のテキストを取得して出力
    print("テキストが変更されました。現在のテキスト:", self.text_edit.toPlainText())

    # 例: テキストが特定の内容になったらメッセージを表示
    if "Qt" in self.text_edit.toPlainText():
        print("Qtという単語が含まれています!")

if name == "main": app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())


### 2\. テキストの文字数カウントと表示

`QTextEdit`に入力されたテキストの文字数をリアルタイムで表示する例です。

#### C++ (Qt Widgets Application)

**`mainwindow.h`:**

```cpp
// ... (上記と同じヘッダーと基本構造)

#include <QLabel> // 文字数表示のために必要

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_textEdit_textChanged();

private:
    Ui::MainWindow *ui;
    QTextEdit *textEdit;
    QLabel *charCountLabel; // 文字数表示用のラベル
};

mainwindow.cpp

#include "mainwindow.h"
#include <QDebug>
#include <QLabel> // QLabel を使うために必要
#include <QVBoxLayout>
#include <QWidget>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(nullptr)
{
    textEdit = new QTextEdit(this);
    textEdit->setPlaceholderText("ここに何か入力してください...");

    charCountLabel = new QLabel("文字数: 0", this); // ラベルの初期化

    connect(textEdit, &QTextEdit::textChanged, this, &MainWindow::on_textEdit_textChanged);

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(textEdit);
    layout->addWidget(charCountLabel); // レイアウトにラベルを追加

    QWidget *centralWidget = new QWidget(this);
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);

    setWindowTitle("QTextEdit Character Count Example (C++)");
    resize(400, 300);

    // 初期状態での文字数更新
    on_textEdit_textChanged();
}

MainWindow::~MainWindow()
{
}

void MainWindow::on_textEdit_textChanged()
{
    QString currentText = textEdit->toPlainText();
    int charCount = currentText.length(); // 文字数を取得

    charCountLabel->setText(QString("文字数: %1").arg(charCount)); // ラベルを更新
}
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QVBoxLayout, QWidget, QLabel
from PyQt5.QtCore import pyqtSlot

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTextEdit Character Count Example (PyQt)")
        self.setGeometry(100, 100, 400, 300)

        self.text_edit = QTextEdit(self)
        self.text_edit.setPlaceholderText("ここに何か入力してください...")

        self.char_count_label = QLabel("文字数: 0", self) # ラベルの初期化

        self.text_edit.textChanged.connect(self.on_text_changed)

        layout = QVBoxLayout()
        layout.addWidget(self.text_edit)
        layout.addWidget(self.char_count_label) # レイアウトにラベルを追加

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # 初期状態での文字数更新
        self.on_text_changed()

    @pyqtSlot()
    def on_text_changed(self):
        current_text = self.text_edit.toPlainText()
        char_count = len(current_text) # 文字数を取得

        self.char_count_label.setText(f"文字数: {char_count}") # ラベルを更新

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())

スロット内でQTextEditの内容をプログラム的に変更する必要がある場合に、無限ループを避けるための例です。ここでは、入力されたテキストを大文字に変換する処理を例にします。

#include "mainwindow.h"
#include <QDebug>
#include <QVBoxLayout>
#include <QWidget>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(nullptr)
{
    textEdit = new QTextEdit(this);
    textEdit->setPlaceholderText("ここに何か入力してください。大文字に変換されます。");

    connect(textEdit, &QTextEdit::textChanged, this, &MainWindow::on_textEdit_textChanged);

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(textEdit);

    QWidget *centralWidget = new QWidget(this);
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);

    setWindowTitle("QTextEdit Infinite Loop Prevention (C++)");
    resize(400, 300);
}

MainWindow::~MainWindow()
{
}

void MainWindow::on_textEdit_textChanged()
{
    QString originalText = textEdit->toPlainText();
    QString upperText = originalText.toUpper(); // テキストを大文字に変換

    // 無限ループを防ぐために、シグナルを一時的にブロックする
    if (originalText != upperText) // 実際に変更がある場合のみ処理
    {
        textEdit->blockSignals(true); // シグナルを一時的にブロック
        textEdit->setPlainText(upperText); // 大文字に変換したテキストを設定
        textEdit->blockSignals(false); // シグナルを再開

        // カーソル位置を元の位置に戻す (任意)
        QTextCursor cursor = textEdit->textCursor();
        cursor.setPosition(originalText.length()); // 編集前の長さに設定 (例)
        textEdit->setTextCursor(cursor);
    }
}
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QVBoxLayout, QWidget
from PyQt5.QtCore import pyqtSlot, QTextCursor

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QTextEdit Infinite Loop Prevention (PyQt)")
        self.setGeometry(100, 100, 400, 300)

        self.text_edit = QTextEdit(self)
        self.text_edit.setPlaceholderText("ここに何か入力してください。大文字に変換されます。")

        self.text_edit.textChanged.connect(self.on_text_changed)

        layout = QVBoxLayout()
        layout.addWidget(self.text_edit)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    @pyqtSlot()
    def on_text_changed(self):
        original_text = self.text_edit.toPlainText()
        upper_text = original_text.upper() # テキストを大文字に変換

        # 無限ループを防ぐために、シグナルを一時的にブロックする
        if original_text != upper_text: # 実際に変更がある場合のみ処理
            self.text_edit.blockSignals(True) # シグナルを一時的にブロック
            self.text_edit.setPlainText(upper_text) # 大文字に変換したテキストを設定
            self.text_edit.blockSignals(False) # シグナルを再開

            # カーソル位置を元の位置に戻す (任意)
            cursor = self.text_edit.textCursor()
            cursor.setPosition(len(original_text)) # 編集前の長さに設定 (例)
            self.text_edit.setTextCursor(cursor)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(app.exec_())


QTextEdit::textEdited()シグナルを使用する

最も直接的な代替手段であり、最もよく使われます。

  • 欠点
    プログラムによる変更を検出できないため、もしプログラム側からテキストが変更された際にも特定の処理を行いたい場合は、別途その場所で処理を呼び出す必要があります。
  • 利点
    スロット内でプログラム的にQTextEditの内容を変更する場合(例: 入力チェック、自動整形など)、textChanged()で発生する無限ループを避けるのに非常に有効です。blockSignals(true/false)を使用する必要がなくなります。
  • 説明
    textEdited()シグナルは、textChanged()と同様にテキストの変更を通知しますが、ユーザーによる編集(キーボード入力、ペースト、ドラッグ&ドロップなど)の場合にのみ発行されます。setText()insertPlainText()などのプログラム的な変更では発行されません。

使用例(概念)

// C++
connect(textEdit, &QTextEdit::textEdited, this, &MyClass::on_textEdit_userEdited);

// Python (PyQt)
self.text_edit.textEdited.connect(self.on_text_edited_by_user)

QTextDocumentのシグナルを使用する

QTextEditは内部的にQTextDocumentを使用してテキストデータを管理しています。より低レベルで詳細なテキスト変更イベントを扱いたい場合、QTextDocumentのシグナルが有用です。

  • QTextDocument::contentsChanged()
    • 説明
      QTextEdit::textChanged()と同様に、ドキュメントの内容が変更された場合に発行されます。これは、テキストの追加・削除だけでなく、書式設定の変更(フォント、色、太字など)によっても発行されます。
    • 利点
      textChanged()よりも低レベルで、書式変更も検出できます。
    • 欠点
      textChanged()と同じく引数がないため、具体的な変更内容を知るにはtoPlainText()などで改めてテキストを取得する必要があります。
  • QTextDocument::contentsChange(int position, int charsRemoved, int charsAdded)
    • 説明
      このシグナルは、ドキュメントのどこで、何文字が削除され、何文字が追加されたかという具体的な変更情報を提供します。
    • 利点
      変更の種類(削除、追加)や位置、量に基づいてきめ細やかな処理を行うことができます。例えば、特定のキーワードの追加を検出したり、特定の文字範囲の変更を監視したりするのに役立ちます。
    • 欠点
      書式設定の変更では発行されません。また、引数があるため、スロットの定義が少し複雑になります。

使用例(概念)

// C++
// QTextEdit のドキュメントを取得し、そのシグナルに接続
connect(textEdit->document(), &QTextDocument::contentsChange, this,
        [](int position, int charsRemoved, int charsAdded) {
    qDebug() << "コンテンツ変更: 位置" << position << ", 削除" << charsRemoved << ", 追加" << charsAdded;
});

connect(textEdit->document(), &QTextDocument::contentsChanged, this,
        []() {
    qDebug() << "ドキュメントの内容が変更されました (書式変更含む)";
});

// Python (PyQt)
self.text_edit.document().contentsChange.connect(self.on_document_contents_change)
self.text_edit.document().contentsChanged.connect(self.on_document_contents_changed_generic)

QTimerを使った遅延処理(Debouncing / Throttling)

textChanged()シグナルが頻繁に発行されることによるパフォーマンス問題を解決するための方法です。これは厳密には代替シグナルではありませんが、textChanged()発行頻度が高すぎる場合の解決策として重要です。

  • 欠点
    リアルタイム性が少し失われます。
  • 利点
    重い処理(例: データベース検索、複雑な解析、UI更新)をテキスト入力のたびに実行するのを避け、パフォーマンスを向上させます。
  • 説明
    ユーザーがテキストを入力し終えるまで、あるいは入力が一定時間停止するまで、関連する処理の実行を遅延させます。

使用例
(上記で示しましたが、再掲)

// C++ (QTimerの初期化とconnectは別途行う)
void MyClass::onTextChanged() {
    m_timer->start(); // テキスト変更のたびにタイマーを再起動
}

void MyClass::processDelayedText() {
    // タイマーがタイムアウトしたときに実行される処理
    qDebug() << "最終的なテキストが処理されました:" << m_textEdit->toPlainText();
}

QEvent::FocusOutイベントの利用(テキスト編集完了時)

ユーザーがQTextEditからフォーカスを外したときにのみ処理を実行したい場合(例: フォームのバリデーション、変更の保存)。

  • 欠点
    リアルタイム性は全くありません。ユーザーが編集中にテキストの状態を監視する必要がある場合には不向きです。
  • 利点
    ユーザーが編集を終えた時点でのみ処理が実行されるため、中間的な状態での不要な処理を防ぎます。
  • 説明
    QTextEditがフォーカスを失ったときにイベントを捕捉し、その時点でテキストの内容を取得して処理します。

使用例(イベントフィルタ、またはサブクラス化)

// C++ (MyClassがQObjectを継承し、イベントフィルタを実装する例)
bool MyClass::eventFilter(QObject *watched, QEvent *event) {
    if (watched == textEdit && event->type() == QEvent::FocusOut) {
        qDebug() << "テキスト編集が完了し、フォーカスが失われました。現在のテキスト:" << textEdit->toPlainText();
        // ここで最終的なテキスト処理を行う
        return true; // イベントを処理したことを示す
    }
    return QObject::eventFilter(watched, event); // 他のイベントは基底クラスに任せる
}

// 初期化時にフィルタをインストール
// textEdit->installEventFilter(this);

より高度な制御が必要な場合、QTextEditをサブクラス化し、特定のイベントハンドラをオーバーライドすることができます。

  • 欠点
    複雑性が増し、Qtの内部動作に関する理解が必要になります。ほとんどのケースではシグナル&スロットで十分です。
  • 利点
    非常にきめ細やかな制御が可能で、カスタムの入力処理や特殊な動作を実装できます。
  • 説明
    keyPressEvent(), mouseReleaseEvent(), inputMethodEvent()などをオーバーライドして、テキスト変更に繋がる低レベルなユーザー操作を直接捕捉します。

使用例(概念)

// C++ (MyTextEdit.h)
class MyTextEdit : public QTextEdit {
    Q_OBJECT
public:
    explicit MyTextEdit(QWidget *parent = nullptr);

protected:
    void keyPressEvent(QKeyEvent *event) override;
    // 他のイベントハンドラもオーバーライド可能

signals:
    void myCustomTextChangedSignal(const QString& newText); // カスタムシグナル

};

// C++ (MyTextEdit.cpp)
#include <QKeyEvent>

MyTextEdit::MyTextEdit(QWidget *parent) : QTextEdit(parent) {}

void MyTextEdit::keyPressEvent(QKeyEvent *event) {
    QTextEdit::keyPressEvent(event); // 基底クラスの処理を呼び出す

    // ここでキー入力後のテキスト状態をチェックし、必要に応じてカスタムシグナルを発行
    emit myCustomTextChangedSignal(toPlainText());
}

QTextEdit::textChanged()は最も便利で一般的なシグナルですが、アプリケーションの具体的な要件に応じて、上記の代替手段や組み合わせを検討することで、より効率的で堅牢なテキスト処理を実現できます。

  • 低レベルなユーザー操作を直接捕捉し、非常にカスタムな動作を実装
    サブクラス化とイベントオーバーライド
  • ユーザーが編集を完了した時点でのみ処理
    QEvent::FocusOut
  • 頻繁なシグナル発行によるパフォーマンス問題
    QTimerによる遅延処理
  • 書式変更も含むドキュメント全体変更
    QTextDocument::contentsChanged()
  • 詳細な変更情報(位置、量): QTextDocument::contentsChange()
  • ユーザー入力のみに反応
    textEdited()