首先看最終效果:
主要要實現的地方是行號的顯示,還有選中行的高亮。
項目結構
整個程式只有三個檔案,最主要的只有一個CodeEditor類,它是繼承自QPlainTextEdit,這個類相比於普通的TextEdit更適合於做富 文字編輯器。
標頭檔
//codeeditor.h#ifndef CODEEDITOR_H#define CODEEDITOR_H#include <QPlainTextEdit>#include <QObject>QT_BEGIN_NAMESPACEclass QPaintEvent;class QResizeEvent;class QSize;class QWidget;QT_END_NAMESPACEclass LineNumberArea;//![codeeditordefinition]class CodeEditor : public QPlainTextEdit{ Q_OBJECTpublic: CodeEditor(QWidget *parent = 0); void lineNumberAreaPaintEvent(QPaintEvent *event); int lineNumberAreaWidth();protected: void resizeEvent(QResizeEvent *event);private slots: void updateLineNumberAreaWidth(int newBlockCount); void highlightCurrentLine(); void updateLineNumberArea(const QRect &, int);private: LineNumberArea *lineNumberArea;
};//![codeeditordefinition]//![extraarea]class LineNumberArea : public QWidget{public: LineNumberArea(CodeEditor *editor) : QWidget(editor) { codeEditor = editor; } QSize sizeHint() const { return QSize(codeEditor->lineNumberAreaWidth(),
0); }protected: void paintEvent(QPaintEvent *event) { codeEditor->lineNumberAreaPaintEvent(event); }private:
Code Editor *codeEditor;};//![extraarea]#endif
標頭檔中包含了兩個類的定義:繼承QPlainTextEdit的CodeEditor和繼承QWidget的LineNumberArea。
寫在一起的原因:在顯示行號的時候需要用到QPlainTextEdit的protected方法,為了方便就直接寫死在CodeEditor中。
在編輯器中,當代碼的行數發生改變,或者編輯器的大小發生改變,行號都需要重新繪製,為此而建立了兩個slot:updateLineNumberWidth() 和 updateLineNumberArea()。
cpp檔案
//codeeditor.cpp#include <QtWidgets>#include "codeeditor.h"//![constructor]CodeEditor::CodeEditor(QWidget *parent) : QPlainTextEdit(parent){ lineNumberArea = new LineNumberArea(this); connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int))); connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int))); connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine())); updateLineNumberAreaWidth(0); highlightCurrentLine();}//![constructor]//![extraAreaWidth]int CodeEditor::lineNumberAreaWidth(){ int digits = 1; int max = qMax(1, blockCount()); while (max >= 10) { max /= 10; ++digits; } int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits; return space;}//![extraAreaWidth]//![slotUpdateExtraAreaWidth]void CodeEditor::updateLineNumberAreaWidth(int /* newBlockCount */){ setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);}//![slotUpdateExtraAreaWidth]//![slotUpdateRequest]void CodeEditor::updateLineNumberArea(const QRect &rect, int dy){ if (dy) lineNumberArea->scroll(0, dy); else lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height()); if (rect.contains(viewport()->rect())) updateLineNumberAreaWidth(0);}//![slotUpdateRequest]//![resizeEvent]void CodeEditor::resizeEvent(QResizeEvent *e){ QPlainTextEdit::resizeEvent(e); QRect cr = contentsRect(); lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));}//![resizeEvent]//![cursorPositionChanged]void CodeEditor::highlightCurrentLine(){ QList<QTextEdit::ExtraSelection> extraSelections; if (!isReadOnly()) { QTextEdit::ExtraSelection selection; QColor lineColor = QColor(Qt::yellow).lighter(160); selection.format.setBackground(lineColor); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = textCursor(); selection.cursor.clearSelection(); extraSelections.append(selection); } setExtraSelections(extraSelections);}//![cursorPositionChanged]//![extraAreaPaintEvent_0]void CodeEditor::lineNumberAreaPaintEvent(QPaintEvent *event){ QPainter painter(lineNumberArea); painter.fillRect(event->rect(), Qt::lightGray);//![extraAreaPaintEvent_0]//![extraAreaPaintEvent_1] QTextBlock block = firstVisibleBlock(); int blockNumber = block.blockNumber(); int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top(); int bottom = top + (int) blockBoundingRect(block).height();//![extraAreaPaintEvent_1]//![extraAreaPaintEvent_2] while (block.isValid() && top <= event->rect().bottom()) { if (block.isVisible() && bottom >= event->rect().top()) { QString number = QString::number(blockNumber + 1); painter.setPen(Qt::black); painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(), Qt::AlignRight, number); } block = block.next(); top = bottom; bottom = top + (int) blockBoundingRect(block).height(); ++blockNumber; }}//![extraAreaPaintEvent_2]在cpp中,首先是類的建構函式,初始化私人成員,連結相應的signal和slot。計算linenumbeearea的寬度並高亮第一行也是必須的。
lineNumberAreaWidth():用於計算 LineNumberArea的寬度,取得最大行數,將其位元與數字9的寬度相乘,再加上3的間隔就可以了。
updateLineNumberAreaWidth(int):更新LineNumberArea的寬度,主要用到了setViewportMargins(),設定內邊距。當行數不斷增加,只要將左邊的內邊距加大就可以了。
updateLineNumberArea(const QRect &rect, int dy):當有換行出現的時候,對LineNumberArea進行更新
resizeEvent(QResizeEvent *e):當視窗大小變化的時候,LineNumberArea的大小也要進行變化。
highlightCurrentLine():當游標位置發生改變的時候,高亮的位置也發生改變。
lineNumberAreaPaintEvent(QPaintEvent *event):主要負責顯示行號,首先是繪製黑色的背景,然後迴圈繪製行號。在plain text edit中,每一行都只包含一個 QTextBlock。
因此,出現折行的話也不用擔心行號不正確。注意要進行兩次判斷,一次是calid,依次是visible。