Qt 如何读取编辑保存显示 MarkDown文件

简述

MarkDown 是一种轻量级、纯文本格式语法的语言,使用场景非常丰富,而且非常方便。CSDN的文章编辑就是使用的MarkDown语法,再比如github以及我日常使用的有道云笔记中都可以使用此语法去编辑文章。了解MarkDown语法有助于我们快速编辑对应格式的文章,也可以借助于对应的工具。CSDN、有道笔记也都有自己编辑文章的工具,操作起来也很方便。但是不同的是,他们都支持基本的MarkDown 语法,但是有一些渲染效果及语法各家都有各自的改动以及拓展,这里就不详细说明了,有兴趣的小伙伴可以了解一下。


CSDN MarkDown 工具栏

在这里插入图片描述


有道云笔记 MarkDown 工具栏

在这里插入图片描述

如果不了解具体可以看下MarkDown 介绍。今天就讲解如何使用Qt来读取/编辑/保存/显示 MarkDown文件。具体QtExample中有详细的例子(直接在QCreator示例中中搜索MarkDown即可),下面的叙述也是通过这个项目进行展开的。


MarkDown 语法示例

在这里插入图片描述


效果图

左边为输入框内容,右边为MarkDown语法显示效果
在这里插入图片描述


代码解说

下面讲述的是用Qt实现一个MarkDown编辑器,类似CSDN和有道云笔记的效果,但是没有工具栏,有兴趣的小伙伴可以自己添加,说白了也就是添加对应的语法语句。代码大部分摘自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);
}

PreviewPage

为什么要重写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_content

class Document : public QObject
{
    Q_OBJECT
        Q_PROPERTY(QString text MEMBER m_text NOTIFY textChanged FINAL)
public:
    explicit Document(QObject *parent = nullptr) : QObject(parent) {}

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

signals:
    void textChanged(const QString &text);

private:
    QString m_text;
};

这里提供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.css

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;
}

marked.min.js

将编辑框中内容转为MarkDown语法效果的html代码(MarkDown的语法解析器)

代码片段(1)
// 获取到div对象;
 var placeholder = document.getElementById('placeholder');
// 更新div里面的内容;
  var updateText = function(text) {
  	// 通过marked方法将Qt界面上输入框中的内容转为html格式,也就是MarkDown的语法解析器;
      placeholder.innerHTML = marked(text);
  }

qwebchannel.js

我们上文中提到了,用于QObject对象的信号槽机制。

以下代码即通过qwebchannel.js以及QWebChannel来实现C++与JS的交互,以及实现类似信号槽机制。

代码片段(2)
  new QWebChannel(qt.webChannelTransport,
    function(channel) {
   	  // 这里获取Docunent对象;
      var content = channel.objects.content;
      updateText(content.text);
      // 将Docunent对象的textChanged信号与updateText方法绑定;
      content.textChanged.connect(updateText);
    }
  );

当界面上输入框内容改变,通知Document对象,发出textChanged信号来调用JS中的updateText方法,通过marked方法将输入框中的内容进行转换成对应的html代码,通过markdown.css样式表达到最终效果。


以上即为整个讲解过程,实现代码虽然简单,但是大家还是要了解一下具体实现的原理,可以从下面链接下载源码,然后对以上讲解的点去对照代码以及Qt助手文档更深入的了解,以便加深理解。

还有注意的是大家记得安装web相关的库哦(webchannel 、webengine 、webenginewidgets),否则会编译不过的哦。

源码下载链接 密码:wpza


欢迎关注公众号 : 前行中的Qt猪
在这里插入图片描述

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页