Qt 如何讀取編輯儲存顯示 MarkDown檔案
簡述
MarkDown 是一種輕量級、純文字格式語法的語言,使用場景非常豐富,而且非常方便。CSDN的文章編輯就是使用的MarkDown語法,再比如github以及我日常使用的有道雲筆記中都可以使用此語法去編輯文章。瞭解MarkDown語法有助於我們快速編輯對應格式的文章,也可以藉助於對應的工具。CSDN、有道筆記也都有自己編輯文章的工具,操作起來也很方便。但是不同的是,他們都支援基本的MarkDown 語法,但是有一些渲染效果及語法各家都有各自的改動以及拓展,這裡就不詳細說明了,有興趣的小夥伴可以瞭解一下。
CSDN MarkDown 工具欄
有道雲筆記 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),否則會編譯不過的哦。