QPlainTextEdit::blockCountの代替手段:Qtで真の「表示行数」を取得する方法

2025-05-16

QPlainTextEdit::blockCount は、QtフレームワークのQPlainTextEditクラスが持つメソッド(関数)です。このメソッドは、QPlainTextEditウィジェット内に表示されているテキストの「ブロック」の数を返します。

「ブロック」とは?

QPlainTextEditにおいて「ブロック」とは、主に段落のような概念を指します。具体的には、改行(\n)によって区切られたテキストのまとまりが1つのブロックとして数えられます。

たとえば、次のようなテキストがQPlainTextEditに入力されているとします。

これは最初の行です。
これは2番目の行です。
これは3番目の行です。

この場合、blockCount()は「3」を返します。

用途

blockCount()メソッドは、以下のような場面で役立ちます。

  • ドキュメントのサイズ把握: テキストドキュメント全体のブロック数を取得することで、その内容の「長さ」を測る指標の一つとして利用できます。
  • テキストの解析や処理: テキストエディタの機能(例: 行番号の表示、特定の行へのジャンプなど)を実装する際に、現在のブロック数を把握するために使用されます。
  • 行数の取得: QPlainTextEditに表示されているテキストの行数を取得したい場合。特に、QPlainTextEditはテキストをブロック単位で管理するため、改行ごとにカウントされる行数として利用できます。

注意点

  • QTextDocumentとの関連: QPlainTextEditは内部的にQTextDocumentを所有しており、blockCount()は実際にはそのQTextDocument内のブロック数を返します。QPlainTextEdit::document()->blockCount()でも同じ値を取得できます。
  • ワードラップとの関係: QPlainTextEditはワードラップ(行の自動折り返し)をサポートしていますが、blockCount()はワードラップによって視覚的に複数行に表示されても、改行がない限り1つのブロックとしてカウントします。つまり、表示上の行数とは異なる場合があります。


QPlainTextEdit::blockCount自体が直接エラーを発生させることは稀ですが、このメソッドの挙動を誤解したり、他の機能と組み合わせる際に予期せぬ結果を招くことがあります。

想定していた行数と異なる

エラー/問題
blockCount()が、ユーザーが視覚的に認識している行数と異なる値を返す。

原因

  • 非表示の改行文字
    テキストデータに目に見えない改行文字や、異なる改行コード(例: CRLF, LF, CR)が含まれている場合、それらがブロックとしてカウントされることがあります。
  • ワードラップ (Word Wrap) の影響
    QPlainTextEditはテキストの自動折り返し(ワードラップ)をサポートしていますが、blockCount()は改行文字(\n)で区切られた「ブロック」の数を数えます。そのため、1つの長い行がワードラップによって複数行に表示されていても、blockCount()は1ブロックとしてカウントします。


QPlainTextEdit::blockCountは、QPlainTextEditウィジェット内のテキストのブロック(改行で区切られた段落/行)数を取得するために使用されます。ここでは、C++とPython (PyQt/PySide) の両方で基本的な使用例を示します。

例1: 現在のブロック数を取得して表示する

この例では、QPlainTextEditにテキストを設定し、その後でblockCount()メソッドを使ってブロック数を取得し、コンソールに表示します。

C++ (Qt Widgets Application)

#include <QApplication>
#include <QMainWindow>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QDebug> // デバッグ出力用

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    QMainWindow window;
    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *layout = new QVBoxLayout(centralWidget);

    QPlainTextEdit *plainTextEdit = new QPlainTextEdit(centralWidget);
    plainTextEdit->setPlainText("これは最初の行です。\n"
                                 "これは2番目の行です。\n"
                                 "そして、これが最後の行です。\n"
                                 "改行で区切られた行はブロックとして数えられます。");

    QLabel *blockCountLabel = new QLabel("現在のブロック数: N/A", centralWidget);
    QPushButton *updateButton = new QPushButton("ブロック数を更新", centralWidget);

    layout->addWidget(plainTextEdit);
    layout->addWidget(blockCountLabel);
    layout->addWidget(updateButton);

    // ボタンがクリックされたときにブロック数を更新するスロット
    QObject::connect(updateButton, &QPushButton::clicked, [&]() {
        int blockCount = plainTextEdit->blockCount();
        blockCountLabel->setText(QString("現在のブロック数: %1").arg(blockCount));
        qDebug() << "ブロック数: " << blockCount;
    });

    // QPlainTextEditのテキストが変更されたときに自動的にブロック数を更新する
    QObject::connect(plainTextEdit, &QPlainTextEdit::blockCountChanged, [&](int newBlockCount) {
        blockCountLabel->setText(QString("現在のブロック数 (変更): %1").arg(newBlockCount));
        qDebug() << "ブロック数が変更されました: " << newBlockCount;
    });


    window.setWindowTitle("QPlainTextEdit Block Count 例");
    window.show();

    return a.exec();
}

Python (PyQt6)

import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QPlainTextEdit,
                             QVBoxLayout, QWidget, QPushButton, QLabel)
from PyQt6.QtCore import Qt

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QPlainTextEdit Block Count 例")

        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        layout = QVBoxLayout(central_widget)

        self.plainTextEdit = QPlainTextEdit()
        self.plainTextEdit.setPlainText("これは最初の行です。\n"
                                        "これは2番目の行です。\n"
                                        "そして、これが最後の行です。\n"
                                        "改行で区切られた行はブロックとして数えられます。")

        self.blockCountLabel = QLabel("現在のブロック数: N/A")
        update_button = QPushButton("ブロック数を更新")

        layout.addWidget(self.plainTextEdit)
        layout.addWidget(self.blockCountLabel)
        layout.addWidget(update_button)

        # ボタンがクリックされたときにブロック数を更新する
        update_button.clicked.connect(self.update_block_count)

        # QPlainTextEditのテキストが変更されたときに自動的にブロック数を更新する
        self.plainTextEdit.blockCountChanged.connect(self.on_block_count_changed)

        # 初期表示
        self.update_block_count()

    def update_block_count(self):
        """現在のplainTextEditのブロック数を取得し、ラベルを更新する"""
        block_count = self.plainTextEdit.blockCount()
        self.blockCountLabel.setText(f"現在のブロック数: {block_count}")
        print(f"ブロック数: {block_count}")

    def on_block_count_changed(self, new_block_count):
        """blockCountChangedシグナルが発行されたときにラベルを更新する"""
        self.blockCountLabel.setText(f"現在のブロック数 (変更): {new_block_count}")
        print(f"ブロック数が変更されました: {new_block_count}")

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

例2: 特定のブロックへ移動する(行番号ジャンプ)

この例では、QPlainTextEditの現在のブロック数を利用して、ユーザーが指定したブロック(行)へカーソルを移動させる機能を示します。

C++ (Qt Widgets Application)

#include <QApplication>
#include <QMainWindow>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
#include <QInputDialog> // 入力ダイアログ用
#include <QMessageBox>  // メッセージボックス用
#include <QTextCursor>  // カーソル操作用

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    QMainWindow window;
    QWidget *centralWidget = new QWidget(&window);
    window.setCentralWidget(centralWidget);

    QVBoxLayout *layout = new QVBoxLayout(centralWidget);

    QPlainTextEdit *plainTextEdit = new QPlainTextEdit(centralWidget);
    plainTextEdit->setPlainText("これは1行目です。\n"
                                 "これは2行目です。\n"
                                 "これは3行目です。\n"
                                 "これは4行目です。\n"
                                 "これは5行目です。\n"
                                 "……長いテキストが続くと仮定します。\n"
                                 "これは最終行です。");

    QPushButton *goToLineButton = new QPushButton("指定の行へ移動", centralWidget);

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

    QObject::connect(goToLineButton, &QPushButton::clicked, [&]() {
        bool ok;
        int maxBlockCount = plainTextEdit->blockCount();
        int lineNumber = QInputDialog::getInt(&window, "行へ移動", "行番号を入力してください (1から" + QString::number(maxBlockCount) + "まで):",
                                              1, 1, maxBlockCount, 1, &ok);
        if (ok && lineNumber >= 1 && lineNumber <= maxBlockCount) {
            QTextCursor cursor = plainTextEdit->textCursor();
            cursor.movePosition(QTextCursor::Start); // ドキュメントの先頭へ移動
            // 指定された行までブロックを移動
            for (int i = 1; i < lineNumber; ++i) {
                cursor.movePosition(QTextCursor::NextBlock);
            }
            plainTextEdit->setTextCursor(cursor);
            plainTextEdit->ensureCursorVisible(); // カーソルが見えるようにスクロール
        } else if (ok) {
            QMessageBox::warning(&window, "エラー", "無効な行番号です。");
        }
    });

    window.setWindowTitle("QPlainTextEdit 行ジャンプ例");
    window.resize(400, 300);
    window.show();

    return a.exec();
}

Python (PyQt6)

import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QPlainTextEdit,
                             QVBoxLayout, QWidget, QPushButton, QInputDialog, QMessageBox)
from PyQt6.QtGui import QTextCursor

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("QPlainTextEdit 行ジャンプ例")

        central_widget = QWidget()
        self.setCentralWidget(central_widget)

        layout = QVBoxLayout(central_widget)

        self.plainTextEdit = QPlainTextEdit()
        self.plainTextEdit.setPlainText("これは1行目です。\n"
                                        "これは2行目です。\n"
                                        "これは3行目です。\n"
                                        "これは4行目です。\n"
                                        "これは5行目です。\n"
                                        "……長いテキストが続くと仮定します。\n"
                                        "これは最終行です。")

        go_to_line_button = QPushButton("指定の行へ移動")

        layout.addWidget(self.plainTextEdit)
        layout.addWidget(go_to_line_button)

        go_to_line_button.clicked.connect(self.go_to_line)

    def go_to_line(self):
        max_block_count = self.plainTextEdit.blockCount()
        line_number, ok = QInputDialog.getInt(self, "行へ移動",
                                              f"行番号を入力してください (1から{max_block_count}まで):",
                                              1, 1, max_block_count, 1)

        if ok and 1 <= line_number <= max_block_count:
            cursor = self.plainTextEdit.textCursor()
            cursor.movePosition(QTextCursor.MoveOperation.Start) # ドキュメントの先頭へ移動
            # 指定された行までブロックを移動
            for _ in range(1, line_number):
                cursor.movePosition(QTextCursor.MoveOperation.NextBlock)
            self.plainTextEdit.setTextCursor(cursor)
            self.plainTextEdit.ensureCursorVisible() # カーソルが見えるようにスクロール
        elif ok:
            QMessageBox.warning(self, "エラー", "無効な行番号です。")

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.resize(400, 300)
    window.show()
    sys.exit(app.exec())

例3: 行番号表示のあるテキストエディタ(概念)

これはblockCountを直接使うというよりも、blockCountが基盤となる機能の例です。行番号表示を実装するには、QPlainTextEditblockCountChangedシグナルとupdateRequestシグナルを監視し、ペイントイベントで各ブロックの行番号を描画する必要があります。

// これは完全なコードではありません。概念を示すためのものです。
// 実際の行番号表示には、QPlainTextEditをサブクラス化し、
// paintEvent()をオーバーライドして、行番号を正確に計算して描画する必要があります。

#include <QPlainTextEdit>
#include <QPainter>
#include <QTextBlock>

class CodeEditor : public QPlainTextEdit {
    Q_OBJECT
public:
    CodeEditor(QWidget *parent = nullptr) : QPlainTextEdit(parent) {
        // 行番号表示領域の幅を設定
        lineNumberAreaWidth = 50; // 仮の値
        setViewportMargins(lineNumberAreaWidth, 0, 0, 0); // 行番号表示領域を確保

        // blockCountChanged シグナルに接続して、行番号エリアの更新をトリガー
        connect(this, &QPlainTextEdit::blockCountChanged, this, &CodeEditor::updateLineNumberAreaWidth);
        connect(this, &QPlainTextEdit::updateRequest, this, &CodeEditor::updateLineNumberArea);
        connect(this, &QPlainTextEdit::cursorPositionChanged, this, &CodeEditor::highlightCurrentLine);
    }

protected:
    void paintEvent(QPaintEvent *event) override {
        QPlainTextEdit::paintEvent(event); // デフォルトのペイント処理

        // 行番号の描画 (簡略化された例)
        QPainter painter(viewport());
        painter.fillRect(0, 0, lineNumberAreaWidth, height(), Qt::lightGray); // 行番号エリアの背景
        painter.setPen(Qt::darkGray);

        QTextBlock block = document()->firstBlock();
        int blockNumber = 1;
        while (block.isValid()) {
            if (block.isVisible()) {
                QRect rect = blockBoundingGeometry(block).toRect();
                // 表示されているブロックのY座標を取得
                int y = rect.top(); // + blockBoundingRect(block).height() - fontMetrics().descent();

                // 行番号を描画
                painter.drawText(0, y, lineNumberAreaWidth - 5, fontMetrics().height(),
                                 Qt::AlignRight | Qt::AlignVCenter, QString::number(blockNumber));
            }
            block = block.next();
            blockNumber++;
        }
    }

private:
    int lineNumberAreaWidth;

    void updateLineNumberAreaWidth(int newBlockCount) {
        // blockCountに基づいて行番号エリアの幅を調整するロジック
        // (例: 桁数が増えたら幅を広げる)
        // ここでは単純化
        viewport()->setContentsMargins(lineNumberAreaWidth, 0, 0, 0);
    }

    void updateLineNumberArea(const QRect &rect, int dy) {
        // スクロールや更新イベントに応じて行番号エリアを再描画
        if (dy)
            scroll(0, dy); // 行番号エリアもスクロール
        else
            update(0, rect.y(), lineNumberAreaWidth, rect.height()); // 特定の領域を更新

        // 完全に再描画が必要な場合は、update(0, 0, lineNumberAreaWidth, height());
    }

    void highlightCurrentLine() {
        // 現在の行をハイライトするロジック
        // extraSelections()を使って行をハイライトする
    }
};


テキストコンテンツを直接解析する

QPlainTextEdit::blockCountが返すのは、内部的なテキストドキュメントのブロック数であり、基本的に改行文字 (\n) の数+1に相当します。このため、テキストコンテンツを直接取得して解析することで、同様の結果を得ることができます。

目的
改行文字の数を数える。

方法
QPlainTextEdit::toPlainText()でテキスト全体を取得し、その文字列を改行文字で分割したり、改行文字の出現回数を数えたりします。

例 (Python/PyQt)

plainTextEdit = QPlainTextEdit()
plainTextEdit.setPlainText("Line 1\nLine 2\nLine 3")

# 方法1: splitlines() を使う
lines = plainTextEdit.toPlainText().splitlines()
line_count_split = len(lines)
print(f"Splitlines method: {line_count_split} lines")

# 方法2: count() を使う(末尾の改行を考慮しない場合)
# 行数が1以上なら、改行数 + 1 がブロック数に相当
text_content = plainTextEdit.toPlainText()
if text_content:
    newline_count = text_content.count('\n')
    line_count_count = newline_count + 1
else:
    line_count_count = 0 # 空のテキストの場合
print(f"Count method: {line_count_count} lines")

# QPlainTextEdit::blockCount() と比較
print(f"blockCount(): {plainTextEdit.blockCount()} blocks")

利点
QPlainTextEditに依存しない一般的な文字列操作であり、特定のテキスト形式の処理に柔軟に対応できます。 欠点: QPlainTextEditの内部的なドキュメント構造(例えば、空のブロック)を正確に反映しない場合があります。また、パフォーマンスがblockCount()より劣る可能性があります。

QTextDocument::lineCount() を使用する(非推奨/注意が必要)

以前のQtバージョンにはQTextDocument::lineCount()というメソッドが存在したようですが、現在のQtバージョン(Qt 5.x, Qt 6.x)の公式ドキュメントではQTextDocumentクラスに直接このメソッドは見当たりません。QTextLayoutなどの下位レベルのクラスに似た機能が存在する可能性はありますが、直接テキストドキュメントの「行数」を取得する標準的な公開APIとしてはblockCount()が一般的です。

もし過去のコードでこれを見かけた場合、そのコードが古いQtバージョンで書かれているか、非公開/内部的なメソッドを使用している可能性があります。

なぜblockCountが「行数」として使われるのか?

QPlainTextEditは、テキストをブロック単位で管理します。これは、基本的に改行文字 (\n) で区切られたテキストのまとまりを指します。通常のプレーンテキストエディタでは、この「ブロック」がユーザーが認識する「行」と一致することがほとんどです。そのため、blockCount()が「行数」として扱われることが多いのです。

表示されている行数(視覚的な行数)を取得する

これはQPlainTextEdit::blockCountがカバーしない最も一般的なシナリオです。blockCountはワードラップ(行の折り返し)を考慮せず、改行文字の数に基づいています。実際に画面に表示されている行数を取得するには、より複雑なアプローチが必要です。

目的
ワードラップによって折り返された行も含め、現在表示されている視覚的な行数を取得したい。

方法

  • カスタムペイントイベントでの計算
    行番号表示などの機能でよく行われますが、QPlainTextEditをサブクラス化し、paintEvent()内で各ブロックのblockBoundingGeometry()blockBoundingRect()を使用して、そのブロックが画面上のどこに位置し、何行を占めているかを計算します。
  • QPlainTextEditの表示領域とスクロールバーの位置から推測
    これはより間接的な方法で、現在表示されている最初のブロックから最後のブロックまでの範囲を特定し、その範囲内の各ブロックの視覚的な行数を合計します。
  • QTextLayout を利用した各ブロックの行数計算
    QTextBlockは自身のQTextLayoutを持っています。このQTextLayoutlineCount()メソッドを呼び出すことで、そのブロックがワードラップによって何行に折り返されているかを知ることができます。これを全ての可視ブロックに対して合計することで、視覚的な行数を近似できます。

例 (概念的なコード、複雑なため完全ではありません)

// QTextLayout::lineCount() を利用した視覚的な行数計算の概念

int getVisibleLineCount(QPlainTextEdit* editor) {
    int totalVisibleLines = 0;
    QTextBlock currentBlock = editor->firstVisibleBlock();
    int firstVisibleBlockNumber = currentBlock.blockNumber();

    QTextBlock lastBlock = editor->document()->lastBlock();

    // visibleBlocksThroughViewport() のような直接的なメソッドはないため、
    // ビューポート内で可視なブロックをループで検出
    while (currentBlock.isValid()) {
        QRectF blockRect = editor->blockBoundingGeometry(currentBlock);
        // ビューポート内に一部でも表示されているブロックを対象とする
        if (blockRect.intersects(editor->viewport()->rect())) {
            // そのブロックが何行に折り返されているか
            if (currentBlock.layout()) { // layout() は null の場合があるためチェック
                totalVisibleLines += currentBlock.layout()->lineCount();
            } else {
                // レイアウトがない場合は1行として数える(基本的には発生しないはず)
                totalVisibleLines += 1;
            }
        }
        if (currentBlock == lastBlock) {
            break; // 最後のブロックまで到達したら終了
        }
        currentBlock = currentBlock.next();
    }
    return totalVisibleLines;
}

利点
ユーザーが実際に画面で見て認識する「行数」に最も近い値を取得できます。 欠点: 複雑な計算が必要であり、パフォーマンスに影響を与える可能性があります。特に大量のテキストや頻繁な更新がある場合に注意が必要です。

QTextCursor を利用したブロック操作

QTextCursorはテキストドキュメント内の位置と選択範囲を操作するための強力なツールです。これを使ってブロックを移動したり、ブロック情報を取得したりできます。

目的
特定のブロックへの移動、ブロックの属性取得、ブロック単位でのテキスト操作。

方法
QTextCursor::movePosition(QTextCursor::NextBlock)などでブロックを移動し、QTextCursor::block()で現在のブロックを取得できます。


最後のブロックのテキストを取得する

QPlainTextEdit* editor = new QPlainTextEdit();
editor->setPlainText("Line A\nLine B\nLine C");

QTextCursor cursor = editor->textCursor();
cursor.movePosition(QTextCursor::End); // ドキュメントの末尾へ移動
cursor.movePosition(QTextCursor::PreviousBlock); // 最後のブロックへ移動

QTextBlock lastBlock = cursor.block();
qDebug() << "Last block text:" << lastBlock.text(); // "Line C"

利点
テキストドキュメントの内部構造を意識した高度な操作が可能になります。

QPlainTextEdit::blockCountは、改行で区切られた論理的な行数を知るための最もシンプルで効率的な方法です。