MarkDown 是一种轻量级、纯文本格式语法的语言,使用场景非常丰富,而且非常方便。的文章编辑就是使用的MarkDown语法,再比如github以及我日常使用的有道云笔记中都可以使用此语法去编辑文章。了解MarkDown语法有助于我们快速编辑对应格式的文章,也可以借助于对应的工具。、有道笔记也都有自己编辑文章的工具,操作起来也很方便。但是不同的是,他们都支持基本的MarkDown 语法,但是有一些渲染效果及语法各家都有各自的改动以及拓展,这里就不详细说明了,有兴趣的小伙伴可以了解一下。
如果不了解具体可以看下MarkDown 介绍。今天就讲解如何使用Qt来读取/编辑/保存/显示 MarkDown文件。具体QtExample中有详细的例子(直接在QCreator示例中中搜索MarkDown即可),下面的叙述也是通过这个项目进行展开的。
左边为输入框内容,右边为MarkDown语法显示效果
下面讲述的是用Qt实现一个MarkDown编辑器,类似和有道云笔记的效果,但是没有工具栏,有兴趣的小伙伴可以自己添加,说白了也就是添加对应的语法语句。代码大部分摘自Qt提供的例子中的代码,这里我将之进行了简化。
以下是实现代码
class MarkDownTest : public QWidget { Q_OBJECT public: MarkDownTest(QWidget *parent = Q_NULLPTR); private: QWebEngineView* m_webEngineView; QTextEdit* m_inputMdContentEdit; Document m_content; }; MarkDownTest::MarkDownTest(QWidget *parent) : QWidget(parent) { m_webEngineView = new QWebEngineView; m_webEngineView->setContextMenuPolicy(Qt::NoContextMenu); PreviewPage* page = new PreviewPage(this); m_webEngineView->setPage(page); QWebChannel *channel = new QWebChannel(this); channel->registerObject(QStringLiteral("content"), &m_content); page->setWebChannel(channel); m_webEngineView->setUrl(QUrl("qrc:/Resources/index.html")); m_inputMdContentEdit = new QTextEdit; connect(m_inputMdContentEdit, &QTextEdit::textChanged, this, [=] { m_content.setText(m_inputMdContentEdit->toPlainText()); }); QFile defaultTextFile(":/Resources/default.md"); defaultTextFile.open(QIODevice::ReadOnly); m_inputMdContentEdit->setText(QString::fromLocal8Bit(defaultTextFile.readAll())); QHBoxLayout* hLayout = new QHBoxLayout(this); hLayout->addWidget(m_inputMdContentEdit); hLayout->addWidget(m_webEngineView); hLayout->addStretch(); hLayout->setSpacing(0); hLayout->setMargin(0); }为什么要重写QWebEnginePage,原因我们可以从下面代码中找到,当触发url链接跳转时会重新加载页面,为了防止MarkDown语句中包含链接,点击之后当前页面跳转至链接页面覆盖了当前MarkDown显示页面。所以在这里进行拦截,将MarkDown语句中的链接点击转至浏览器中显示。加了if的判断是防止我们加载的index.html无法显示。
所以这里讲述的就是重写了QWebEnginePage所要达到的效果。
class PreviewPage : public QWebEnginePage { Q_OBJECT public: explicit PreviewPage(QObject *parent = nullptr) : QWebEnginePage(parent) {} protected: bool acceptNavigationRequest(const QUrl &url, NavigationType type, bool isMainFrame) { QString strUrl = url.toString(); // Only allow qrc:/index.html. if (url.scheme() == QString("qrc")) return true; // 从浏览器中打开链接; QDesktopServices::openUrl(url); return false; } };下图为Qt助手中acceptNavigationRequest的详解。
这里提供Document类将输入框m_inputMdContentEdit对象中的MarkDown语句内容显示到页面上。通过QTextEdit::textChanged信号通知Document对象,如果文字修改过,Document对象发出textChanged信号去通知页面。
m_inputMdContentEdit = new QTextEdit; connect(m_inputMdContentEdit, &QTextEdit::textChanged, this, [=] { m_content.setText(m_inputMdContentEdit->toPlainText()); });然而代码中我们并没有看到Document对象的textChanged信号与哪个槽函数绑定去通知页面改变内容。
先了解一下QWebChannel类,从助手文档中我们看到QWebChannel好比是C++代码与HTML/JS 交互的桥梁,如果我们想要使用QObject的信号槽机制,那么需要在HTML中加载qwebchannel.js(这个文件在Qt例子中也提供了,文末会提供链接下载工程),所以我们想要Document对象的textChanged信号去通知HTML修改内容,那就需要把Document对象注册到QWebChannel中。
QWebChannel *channel = new QWebChannel(this); channel->registerObject(QStringLiteral("content"), &m_content);这里使用QWebEngineView加载自己的MarkDown页面。
m_webEngineView->setUrl(QUrl("qrc:/Resources/index.html"));我们看一下index.html的内容,主要四个部分,markdown.css、marked.min.js、qwebchannel.js、以及Html语句中Body部分。
<!doctype html> <html lang="en"> <meta charset="utf-8"> <head> <link rel="stylesheet" type="text/css" href="3rdparty/markdown.css"> <script src="3rdparty/marked.min.js"></script> <script src="qwebchannel.js"></script> </head> <body> <div id="placeholder"></div> <script> 'use strict'; // (1) var placeholder = document.getElementById('placeholder'); var updateText = function(text) { placeholder.innerHTML = marked(text); } // (2) new QWebChannel(qt.webChannelTransport, function(channel) { var content = channel.objects.content; updateText(content.text); content.textChanged.connect(updateText); } ); </script> </body> </html>MarkDown语法的样式都包含在这个文件中,以下只展示了部分样式。
body{ margin: 0 auto; font-family: Georgia, Palatino, serif; color: #444444; line-height: 1; max-width: 960px; padding: 30px; } h1, h2, h3, h4 { color: #111111; font-weight: 400; } h1, h2, h3, h4, h5, p { margin-bottom: 24px; padding: 0; } h1 { font-size: 48px; } h2 { font-size: 36px; /* The bottom margin is small. It's designed to be used with gray meta text * below a post title. */ margin: 24px 0 6px; } h3 { font-size: 24px; } h4 { font-size: 21px; } h5 { font-size: 18px; }将编辑框中内容转为MarkDown语法效果的html代码(MarkDown的语法解析器)
我们上文中提到了,用于QObject对象的信号槽机制。
以下代码即通过qwebchannel.js以及QWebChannel来实现C++与JS的交互,以及实现类似信号槽机制。
当界面上输入框内容改变,通知Document对象,发出textChanged信号来调用JS中的updateText方法,通过marked方法将输入框中的内容进行转换成对应的html代码,通过markdown.css样式表达到最终效果。
以上即为整个讲解过程,实现代码虽然简单,但是大家还是要了解一下具体实现的原理,可以从下面链接下载源码,然后对以上讲解的点去对照代码以及Qt助手文档更深入的了解,以便加深理解。
还有注意的是大家记得安装web相关的库哦(webchannel 、webengine 、webenginewidgets),否则会编译不过的哦。
源码下载链接 密码:wpza
欢迎关注公众号 : 前行中的Qt猪
