用Qt写一个简单的Markdown编辑器

李肖遥

共 6156字,需浏览 13分钟

 · 2021-06-17

关注、星标公众号,直达精彩内容

ID:技术让梦想更伟大

作者:李肖遥

Markdown编辑器

Markdown 是一种具有纯文本格式语法的轻量级标记语言,Markdown Editor 演示了如何使用 QWebChannel 和 JavaScript 库为自定义标记语言提供富文本预览工具。

Markdown 编辑器主窗口分为编辑器和预览区,编辑器支持 Markdown 语法,使用 QPlainTextEdit 实现;文档在预览区渲染为富文本,通过QWebEngineView实现。

为了呈现文本,Markdown 文本在 Web 引擎内的 JavaScript 库的帮助下转换为 HTML 格式,预览是通过 QWebChannel 从编辑器更新的。

下图演示如何将 Web 引擎集成到混合桌面应用程序中。

我们使用例子,左侧输入Markdown语法的文本,右侧就是其预览的样式了。

实现原理以及步骤

公开文档文本

通过 QWebChannel 将要渲染的当前 Markdown 文本暴露给 Web 引擎,所以我们需要以某种方式通过 Qt 元类型系统使当前文本可用,这是通过使用将文档文本公开为 Q_PROPERTY 的专用 Document 类来完成的:

通过 QWebChannel 将要渲染的当前 Markdown 文本暴露给 Web 引擎,将文档文本公开为 Q_PROPERTY 的专用 Document 类,然后通过 Qt 元类型系统使当前文本可用。

Document 类使用setText()方法包装要在C++ 端设置的 QString,并在运行时将其作为带有 textChanged 信号的文本属性公开。

定义 setText 方法如下:

void Document::setText(const QString &text)
{
    if (text == m_text)
        return;
    m_text = text;
    emit textChanged(m_text);
}

预览文本

实现自己的 PreviewPage 类,该类公开继承自 QWebEnginePage。

重新实现了虚拟的 acceptNavigationRequest 方法来阻止页面导航离开当前文档,从而将外部链接重定向到系统浏览器,代码如下:

bool PreviewPage::acceptNavigationRequest(const QUrl &url,
                                          QWebEnginePage::NavigationType /*type*/,
                                          bool /*isMainFrame*/)
{
    // Only allow qrc:/index.html.
    if (url.scheme() == QString("qrc"))
        return true;
    QDesktopServices::openUrl(url);
    return false;
}

创建主窗口

MainWindow 类继承了 QMainWindow 类,该类声明了与菜单中的操作相匹配的私有槽,以及 isModified() 辅助方法。

主窗口的实际布局在.ui文件中指定,小部件和操作在运行时在 ui 成员变量中可用。

m_filePath 保存当前加载文档的文件路径, m_content 是 Document 类的一个实例。

不同对象的实际设置是在 MainWindow 构造函数中完成的,代码如下:

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->editor->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
    ui->preview->setContextMenuPolicy(Qt::NoContextMenu);

构造函数首先调用 setupUi 来根据 UI 文件构造小部件和菜单操作,文本编辑器字体设置为具有固定字符宽度的字体,并且 QWebEngineView 小部件不显示上下文菜单。

PreviewPage *page = new PreviewPage(this);
ui->preview->setPage(page);

这里构造函数确保我们的自定义 PreviewPage 被 ui->preview 中的 QWebEngineView 实例使用。

connect(ui->editor, &QPlainTextEdit::textChanged,
        [this]() { m_content.setText(ui->editor->toPlainText()); });

QWebChannel *channel = new QWebChannel(this);
channel->registerObject(QStringLiteral("content"), &m_content);
page->setWebChannel(channel);

这里编辑器的 textChanged 信号连接到更新 m_content 中的文本的 lambda, 然后这个对象被QWebChannel以content为名暴露给JS端。

ui->preview->setUrl(QUrl("qrc:/index.html"));

现在我们实际上可以从资源中加载 index.html 文件。有关该文件的更多信息,请参阅创建索引文件。

connect(ui->actionNew, &QAction::triggered, this, &MainWindow::onFileNew);
connect(ui->actionOpen, &QAction::triggered, this, &MainWindow::onFileOpen);
connect(ui->actionSave, &QAction::triggered, this, &MainWindow::onFileSave);
connect(ui->actionSaveAs, &QAction::triggered, this, &MainWindow::onFileSaveAs);
connect(ui->actionExit, &QAction::triggered, this, &MainWindow::onExit);
connect(ui->editor->document(), &QTextDocument::modificationChanged,ui->actionSave, &QAction::setEnabled);

菜单项连接到相应的成员槽,保存项目的激活或停用取决于用户是否已编辑内容。

QFile defaultTextFile(":/default.md");
defaultTextFile.open(QIODevice::ReadOnly);
ui->editor->setPlainText(defaultTextFile.readAll());
}

最后,我们从资源中加载一个默认文档 default.md就可以了。

创建索引文件

<!doctype html>
<html lang="en">
<meta charset="utf-8">
<head>
  <link rel="stylesheet" type="text/css" href="3rdparty/markdown.css">
  <script src="3rdparty/marked.js"></script>
  <script src="qrc:/qtwebchannel/qwebchannel.js"></script>
</head>
<body>
  <div id="placeholder"></div>
  <script>
  'use strict';

  var placeholder = document.getElementById('placeholder');

  var updateText = function(text) {
      placeholder.innerHTML = marked(text);
  }

  new QWebChannel(qt.webChannelTransport,
    function(channel) {
      var content = channel.objects.content;
      updateText(content.text);
      content.textChanged.connect(updateText);
    }
  );
  </script>
</body>
</html>

index.html 中加载了一个自定义样式表和两个 JavaScript 库。markdown.css 是一个 Markdown 友好样式表;marked.js 是一个 Markdown 解析器和编译器,旨在提高速度;qwebchannel.js 是 QWebChannel 模块的一部分。

在  元素中,首先定义一个占位符元素,并将其作为 JavaScript 变量使用,然后定义 updateText 辅助方法,最后设置 Web 通道来访问内容代理对象,并确保在 content.text 更改时调用 updateText()。

最后

  • Qt安装目录找到源码

\Q{你的Qt版本}\Examples{你的Qt版本}\webenginewidgets\markdowneditor

  • 相关链接

https://doc.qt.io/qt-5/qtwebengine-webenginewidgets-markdowneditor-example.html

‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

推荐阅读:


嵌入式编程专辑
Linux 学习专辑
C/C++编程专辑
Qt进阶学习专辑

关注我的微信公众号,回复“加群”按规则加入技术交流群。


点击“阅读原文”查看更多分享。

浏览 36
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报