1. 程式人生 > >Qt 如何讀取編輯儲存顯示 MarkDown檔案

Qt 如何讀取編輯儲存顯示 MarkDown檔案

簡述

MarkDown 是一種輕量級、純文字格式語法的語言,使用場景非常豐富,而且非常方便。CSDN的文章編輯就是使用的MarkDown語法,再比如github以及我日常使用的有道雲筆記中都可以使用此語法去編輯文章。瞭解MarkDown語法有助於我們快速編輯對應格式的文章,也可以藉助於對應的工具。CSDN、有道筆記也都有自己編輯文章的工具,操作起來也很方便。但是不同的是,他們都支援基本的MarkDown 語法,但是有一些渲染效果及語法各家都有各自的改動以及拓展,這裡就不詳細說明了,有興趣的小夥伴可以瞭解一下。

CSDN MarkDown 工具欄

在這裡插入圖片描述

有道雲筆記 MarkDown 工具欄

在這裡插入圖片描述

如果不瞭解具體可以看下

MarkDown 介紹。今天就講解如何使用Qt來讀取/編輯/儲存/顯示 MarkDown檔案。具體QtExample中有詳細的例子(直接在QCreator示例中中搜索MarkDown即可),下面的敘述也是通過這個專案進行展開的。

MarkDown 語法示例

在這裡插入圖片描述

效果圖

左邊為輸入框內容,右邊為MarkDown語法顯示效果 在這裡插入圖片描述

程式碼解說

下面講述的是用Qt實現一個MarkDown編輯器,類似CSDN和有道雲筆記的效果,但是沒有工具欄,有興趣的小夥伴可以自己新增,說白了也就是新增對應的語法語句。程式碼大部分摘自Qt提供的例子中的程式碼,這裡我將之進行了簡化。原理就是編寫一個html,提供一個方法讓編輯器中輸入的MarkDown語句顯示到html頁面中,然後

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),否則會編譯不過的哦。