别再只用记事本了!用Qt Widgets花30分钟做个自己的高亮代码编辑器(支持撤销/重做)

发布时间:2026/6/5 8:05:13

别再只用记事本了!用Qt Widgets花30分钟做个自己的高亮代码编辑器(支持撤销/重做) 30分钟打造专业级代码编辑器Qt Widgets实现语法高亮与撤销重做每次临时查看或修改代码片段时系统自带的记事本总是让人抓狂——没有语法高亮、无法撤销操作、连基本的自动缩进都没有。作为开发者我们值得拥有更好的工具。本文将带你用Qt Widgets快速构建一个功能完善的代码编辑器重点实现两大核心功能基于QSyntaxHighlighter的语法高亮和利用QUndoStack的撤销/重做机制。1. 环境准备与项目创建首先确保已安装Qt开发环境推荐Qt 5.15或Qt 6.x版本。打开Qt Creator选择文件→新建文件或项目创建一个Qt Widgets Application项目命名为CodeEditor。在项目配置页面保持默认选项即可。创建完成后你会看到自动生成的mainwindow.h和mainwindow.cpp文件。我们需要在此基础上添加编辑器功能。基础界面组件配置// mainwindow.h #include QMainWindow #include QTextEdit class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent nullptr); private: QTextEdit *textEdit; // 核心编辑组件 };// mainwindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { textEdit new QTextEdit(this); setCentralWidget(textEdit); // 设置基础编辑器属性 textEdit-setLineWrapMode(QTextEdit::NoWrap); // 禁用自动换行 QFont font(Consolas, 10); // 使用等宽字体 textEdit-setFont(font); resize(800, 600); setWindowTitle(Code Editor); }2. 实现语法高亮功能语法高亮是代码编辑器的灵魂。Qt提供了QSyntaxHighlighter类来轻松实现这一功能。我们将创建一个专门的高亮器类来处理C语法。创建语法高亮器类// syntaxhighlighter.h #include QSyntaxHighlighter #include QTextCharFormat class SyntaxHighlighter : public QSyntaxHighlighter { Q_OBJECT public: SyntaxHighlighter(QTextDocument *parent nullptr); protected: void highlightBlock(const QString text) override; private: struct HighlightingRule { QRegExp pattern; QTextCharFormat format; }; QVectorHighlightingRule highlightingRules; QTextCharFormat keywordFormat; QTextCharFormat singleLineCommentFormat; QTextCharFormat multiLineCommentFormat; QTextCharFormat quotationFormat; QTextCharFormat functionFormat; };实现高亮规则// syntaxhighlighter.cpp #include syntaxhighlighter.h SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) { // 设置关键字格式蓝色加粗 keywordFormat.setForeground(Qt::darkBlue); keywordFormat.setFontWeight(QFont::Bold); // 设置注释格式灰色 singleLineCommentFormat.setForeground(Qt::gray); multiLineCommentFormat.setForeground(Qt::gray); // 设置字符串格式深绿色 quotationFormat.setForeground(Qt::darkGreen); // 设置函数格式深红色 functionFormat.setForeground(Qt::darkRed); functionFormat.setFontWeight(QFont::Bold); // 添加C关键字规则 QStringList keywordPatterns; keywordPatterns \\bchar\\b \\bclass\\b \\bconst\\b \\bdouble\\b \\benum\\b \\bexplicit\\b \\bfriend\\b \\binline\\b \\bint\\b \\blong\\b \\bnamespace\\b \\boperator\\b \\bprivate\\b \\bprotected\\b \\bpublic\\b \\bshort\\b \\bsignals\\b \\bsigned\\b \\bslots\\b \\bstatic\\b \\bstruct\\b \\btemplate\\b \\btypedef\\b \\btypename\\b \\bunion\\b \\bunsigned\\b \\bvirtual\\b \\bvoid\\b \\bvolatile\\b; foreach (const QString pattern, keywordPatterns) { HighlightingRule rule; rule.pattern QRegExp(pattern); rule.format keywordFormat; highlightingRules.append(rule); } // 添加单行注释规则 HighlightingRule singleLineCommentRule; singleLineCommentRule.pattern QRegExp(//[^\n]*); singleLineCommentRule.format singleLineCommentFormat; highlightingRules.append(singleLineCommentRule); // 添加多行注释规则 HighlightingRule multiLineCommentRule; multiLineCommentRule.pattern QRegExp(/\\*.*\\*/); multiLineCommentRule.format multiLineCommentFormat; highlightingRules.append(multiLineCommentRule); // 添加字符串规则 HighlightingRule quotationRule; quotationRule.pattern QRegExp(\.*\); quotationRule.format quotationFormat; highlightingRules.append(quotationRule); // 添加函数定义规则 HighlightingRule functionRule; functionRule.pattern QRegExp(\\b[A-Za-z0-9_](?\\()); functionRule.format functionFormat; highlightingRules.append(functionRule); } void SyntaxHighlighter::highlightBlock(const QString text) { foreach (const HighlightingRule rule, highlightingRules) { QRegExp expression(rule.pattern); int index expression.indexIn(text); while (index 0) { int length expression.matchedLength(); setFormat(index, length, rule.format); index expression.indexIn(text, index length); } } }在MainWindow中使用高亮器// 在MainWindow构造函数中添加 new SyntaxHighlighter(textEdit-document());3. 实现撤销/重做功能Qt内置的QUndoStack提供了强大的撤销/重做框架。我们将利用它来实现编辑器的历史记录功能。设置撤销/重做系统// mainwindow.h #include QUndoStack #include QUndoView class MainWindow : public QMainWindow { // ... private: QUndoStack *undoStack; QUndoView *undoView; // 可选显示撤销历史的面板 };// mainwindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { // ...之前的初始化代码... // 初始化撤销系统 undoStack new QUndoStack(this); // 可选添加撤销历史面板 undoView new QUndoView(undoStack); undoView-setWindowTitle(tr(Command List)); undoView-show(); // 连接文本编辑器的撤销/重做信号 connect(textEdit-document(), QTextDocument::undoAvailable, this, [this](bool available) { // 更新UI中的撤销动作状态 ui-actionUndo-setEnabled(available); }); connect(textEdit-document(), QTextDocument::redoAvailable, this, [this](bool available) { // 更新UI中的重做动作状态 ui-actionRedo-setEnabled(available); }); }创建自定义编辑命令// texteditcommand.h #include QUndoCommand #include QTextCursor #include QTextDocument class TextEditCommand : public QUndoCommand { public: enum Type { Insert, Remove }; TextEditCommand(Type type, QTextDocument *doc, const QTextCursor cursor, const QString text, QUndoCommand *parent nullptr); void undo() override; void redo() override; private: Type cmdType; QTextDocument *document; int position; QString insertedText; QString removedText; };// texteditcommand.cpp #include texteditcommand.h TextEditCommand::TextEditCommand(Type type, QTextDocument *doc, const QTextCursor cursor, const QString text, QUndoCommand *parent) : QUndoCommand(parent), cmdType(type), document(doc) { position cursor.position(); if (type Insert) { insertedText text; setText(QString(插入: %1).arg(text.left(10))); } else { removedText cursor.selectedText(); setText(QString(删除: %1).arg(removedText.left(10))); } } void TextEditCommand::undo() { QTextCursor cursor(document); cursor.setPosition(position); if (cmdType Insert) { cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, insertedText.length()); cursor.removeSelectedText(); } else { cursor.insertText(removedText); } } void TextEditCommand::redo() { QTextCursor cursor(document); cursor.setPosition(position); if (cmdType Insert) { cursor.insertText(insertedText); } else { cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, removedText.length()); cursor.removeSelectedText(); } }连接编辑器操作到命令系统// 在MainWindow中添加槽函数 void MainWindow::onTextChanged() { // 这里可以添加更精细的命令管理逻辑 // 例如合并连续输入字符为单个命令 } void MainWindow::setupEditorConnections() { connect(textEdit, QTextEdit::textChanged, this, MainWindow::onTextChanged); // 连接撤销/重做动作 connect(ui-actionUndo, QAction::triggered, undoStack, QUndoStack::undo); connect(ui-actionRedo, QAction::triggered, undoStack, QUndoStack::redo); }4. 增强功能与界面优化现在我们已经实现了核心功能接下来添加一些实用功能来提升编辑器体验。添加行号显示// linenumberarea.h #include QWidget #include QTextEdit class LineNumberArea : public QWidget { public: LineNumberArea(QTextEdit *editor) : QWidget(editor), textEdit(editor) {} QSize sizeHint() const override { return QSize(textEdit-lineNumberAreaWidth(), 0); } protected: void paintEvent(QPaintEvent *event) override { textEdit-lineNumberAreaPaintEvent(event); } private: QTextEdit *textEdit; };// 在QTextEdit子类中添加 int lineNumberAreaWidth() { int digits 1; int max qMax(1, blockCount()); while (max 10) { max / 10; digits; } int space 3 fontMetrics().horizontalAdvance(QLatin1Char(9)) * digits; return space; } void lineNumberAreaPaintEvent(QPaintEvent *event) { QPainter painter(lineNumberArea); painter.fillRect(event-rect(), Qt::lightGray); QTextBlock block firstVisibleBlock(); int blockNumber block.blockNumber(); int top (int)blockBoundingGeometry(block).translated(contentOffset()).top(); int bottom top (int)blockBoundingRect(block).height(); 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; } }添加查找/替换功能// mainwindow.h private slots: void find(); void replace(); void replaceAll(); private: QDialog *findDialog; QLineEdit *findLineEdit; QLineEdit *replaceLineEdit; QCheckBox *caseSensitiveCheckBox; QCheckBox *wholeWordsCheckBox;// mainwindow.cpp void MainWindow::setupFindDialog() { findDialog new QDialog(this); findDialog-setWindowTitle(tr(查找/替换)); QVBoxLayout *layout new QVBoxLayout; findLineEdit new QLineEdit; replaceLineEdit new QLineEdit; caseSensitiveCheckBox new QCheckBox(tr(区分大小写)); wholeWordsCheckBox new QCheckBox(tr(全字匹配)); QPushButton *findButton new QPushButton(tr(查找)); QPushButton *replaceButton new QPushButton(tr(替换)); QPushButton *replaceAllButton new QPushButton(tr(全部替换)); QPushButton *closeButton new QPushButton(tr(关闭)); connect(findButton, QPushButton::clicked, this, MainWindow::find); connect(replaceButton, QPushButton::clicked, this, MainWindow::replace); connect(replaceAllButton, QPushButton::clicked, this, MainWindow::replaceAll); connect(closeButton, QPushButton::clicked, findDialog, QDialog::close); QFormLayout *formLayout new QFormLayout; formLayout-addRow(tr(查找:), findLineEdit); formLayout-addRow(tr(替换为:), replaceLineEdit); QHBoxLayout *optionsLayout new QHBoxLayout; optionsLayout-addWidget(caseSensitiveCheckBox); optionsLayout-addWidget(wholeWordsCheckBox); QHBoxLayout *buttonLayout new QHBoxLayout; buttonLayout-addWidget(findButton); buttonLayout-addWidget(replaceButton); buttonLayout-addWidget(replaceAllButton); buttonLayout-addWidget(closeButton); layout-addLayout(formLayout); layout-addLayout(optionsLayout); layout-addLayout(buttonLayout); findDialog-setLayout(layout); } void MainWindow::find() { QString searchString findLineEdit-text(); if (searchString.isEmpty()) return; QTextDocument::FindFlags flags; if (caseSensitiveCheckBox-isChecked()) flags | QTextDocument::FindCaseSensitively; if (wholeWordsCheckBox-isChecked()) flags | QTextDocument::FindWholeWords; bool found textEdit-find(searchString, flags); if (!found) { QMessageBox::information(this, tr(查找), tr(找不到\%1\).arg(searchString)); } }添加主题支持// mainwindow.h private slots: void changeTheme(const QString themeName); private: void setupThemes(); QMapQString, QMapQString, QColor themes;// mainwindow.cpp void MainWindow::setupThemes() { // 默认主题 QMapQString, QColor defaultTheme; defaultTheme[background] Qt::white; defaultTheme[text] Qt::black; defaultTheme[lineNumbers] Qt::lightGray; themes[Default] defaultTheme; // Dark主题 QMapQString, QColor darkTheme; darkTheme[background] QColor(53, 53, 53); darkTheme[text] Qt::white; darkTheme[lineNumbers] QColor(80, 80, 80); themes[Dark] darkTheme; // Solarized Light主题 QMapQString, QColor solarizedLight; solarizedLight[background] QColor(253, 246, 227); solarizedLight[text] QColor(101, 123, 131); solarizedLight[lineNumbers] QColor(238, 232, 213); themes[Solarized Light] solarizedLight; } void MainWindow::changeTheme(const QString themeName) { if (!themes.contains(themeName)) return; QMapQString, QColor theme themes[themeName]; QString styleSheet QString( QTextEdit { background-color: %1; color: %2; } LineNumberArea { background-color: %3; } ).arg(theme[background].name(), theme[text].name(), theme[lineNumbers].name()); setStyleSheet(styleSheet); // 同时更新语法高亮颜色 updateSyntaxHighlightingForTheme(themeName); }

相关新闻