剪貼板是個啥就不用多介紹了,最直觀的功能是實現應用程序之間數據共享。就是咱們常說的“複製”、“粘貼”功能。
在 Qt 中,QClipboard 類提供了相關 API 讓應用程序具備讀/寫剪貼板的能力。數據通過 QMimeData 類包裝。該類使用 MIME 類型來標識數據。比如,要包裝的數據是純文本內容,就使用 text/plain;如果是 PNG 圖像數據,就用 image/png。當然,自定義類型也是可以的,如 application/xxx。
QMimeData 的核心方法是 setData 和 data。setData 方法用來放入數據,data 方法用來取出數據。setData 方法的簽名如下:
void setData(const QString &mimetype, const QByteArray &data);
mimetype 參數爲字符串,指定數據的 MIME 類型;data 參數就是數據本尊,類型爲字節序列。通過 setData 方法的簽名,咱們也能知道,QMimeData 類可以放任意內容。要獲取數據時,data 方法需要通過 MIME 類型來檢索。
爲了便於存取常見的數據——如文本、圖像、HTML文本等,QMimeData 類提供一些封裝好的方法成員:
文本 | setText | 設置普通文本 |
text | 獲取普通文本 | |
hasText
|
判斷是否存在文本數據 | |
HTML文本 |
setHtml
|
設置 HTML 文本 |
html
|
獲取HTML文本 | |
hasHtml
|
判斷是否存在 HTML 文本數據 | |
URL |
setUrls
|
設置 URL 列表,參數爲 QList<QUrl> |
urls | 獲取 URL 列表 | |
hasUrls
|
檢測是否存在 URL 列表 | |
圖像 |
setImageData
|
設置圖像數據 |
imageData
|
獲取圖像數據 | |
hasImage
|
判斷是否存在圖像數據 | |
顏色 |
setColorData
|
設置顏色數據 |
colorData
|
獲取顏色數據 | |
hasColor
|
是否存在顏色數據 |
QClipboard 類不能直接實例化使用,它由 QGuiApplication 類的靜態成員 clipboard 公開。該靜態成員返回 QClipboard 類的指針,程序代碼將通過這個指針來訪問 QClipboard 對象。由於 QApplication 類派生自 QGuiApplication,當然也繼承了 clipboard 成員。
下面做一個簡單的練習:複製和粘貼文本。
MyWindow 類的頭文件。
class MyWindow : public QWidget { Q_OBJECT public: MyWindow(QWidget* parent = nullptr); ~MyWindow(); private: void _initUi(); // 私有方法 // 下面是私有字段 QLineEdit* _txtInput; QLabel* _lbTxt; QPushButton* _btnCopy; QPushButton* _btnPaste; // 用來佈局控件的 QGridLayout* _layout; // 下面成員響應 clicked 信號 void onCopy(); void onPaste(); };
_initUi 方法負責初始化窗口上的東西。這個窗口有四個組件:一個 QLineEdit 用來輸入文本;一個 QLabel 用來顯示文本;然後是兩個按鈕—— 執行“複製”和“粘貼”操作。
後面兩個方法 onCopy 和 onPaste 分別與兩個按鈕的 clicked 信號綁定。
構造函數的實現比較簡單,就是調用 _initUi 方法。
MyWindow::MyWindow(QWidget* parent) : QWidget::QWidget(parent) { // 初始化UI this -> _initUi(); } MyWindow::~MyWindow() { }
析構函數這裏啥也不做。
下面是 _intUi 的實現代碼。
void MyWindow::_initUi() { // 設置一下窗口 this->setWindowTitle("複製粘貼文本"); this->setGeometry(560, 480, 320, 150); this->setMinimumSize(300, 150); _txtInput = new QLineEdit(); _lbTxt = new QLabel(); _btnCopy = new QPushButton("複製"); _btnPaste = new QPushButton("粘貼"); _layout = new QGridLayout(this); // 設置空白 _layout->setSpacing(12); // 放置各控件 _layout->addWidget(_txtInput, 0, 0); _layout->addWidget(_btnCopy, 1, 0); _layout->addWidget(_lbTxt, 0, 2); _layout->addWidget(_btnPaste, 1, 2); _layout->setColumnStretch(0, 2); _layout->setColumnStretch(1, 1); _layout->setColumnStretch(2, 2); // 綁定信號和槽 connect(_btnCopy, &QPushButton::clicked, this, &MyWindow::onCopy); connect(_btnPaste, &QPushButton::clicked, this, &MyWindow::onPaste); }
QGridLayout 類也是一個組件,以網格方式佈局各組件。網格的行和列是自動劃分的。上面代碼中其實用到了三列兩行:
1、QLineEdit 在第一列第一行;
2、第二列空着,沒放東西;
3、 QLabel 組件在第三列第一行;
4、“複製”按鈕在第一列第二行;
5、“粘貼”按鈕在第二行第二行。
這三行是設定空間比例的。
_layout->setColumnStretch(0, 2); _layout->setColumnStretch(1, 1); _layout->setColumnStretch(2, 2);
這意思就是,列的總寬平均分爲4份,第一列和第三列都佔兩份,第二列只佔一份。
隨後是與 clicked 信號綁定的兩個私有方法。
void MyWindow::onCopy() { // 獲得 QClipboard 的引用 QClipboard* clboard = QApplication::clipboard(); // 設置文本數據 clboard -> setText(_txtInput -> text()); } void MyWindow::onPaste() { // 過程差不多 QClipboard* cb = QApplication::clipboard(); QString s = cb->text(); // 顯示粘貼的文本 _lbTxt->setText(s); }
最後是 main 函數的代碼:
int main(int argc, char** argv) { QApplication app(argc, argv); MyWindow win; win.show(); return app.exec(); }
運行程序,先輸入一些文本,點擊“複製”;再點擊“粘貼”,被複制的文本就會顯示出來了。
這個例子用了 QClipboard 類公開的封裝方法,不需要操作 QMimeData 類。針對常用的數據格式,可直接用。
1、text、setText:設置或獲取文本;
2、setImage 和 image:設置或獲取圖像(QImage類型);
3、setPixmap 和 pixmap:設置或獲取圖像(QPixmap類型)。
後面兩個是讀寫圖像的。
對於圖像數據的複製和粘貼,操作流程差不多,大夥伴有興趣可以試試。
前文提到過,除了常見的數據格式外,QMimeData 允許自定義格式,用 MIME 來標識。
接下來,咱們做個練習,複製和粘貼生日賀卡信息。假設生日賀卡信息包括姓名、生日、祝福語。咱們用自定義的數據格式將其複製,也可以粘貼到應用程序上。MIME 類型爲 application/bug。
以下是自定義窗口類的頭文件定義。
#ifndef APP_H #define APP_H #include <qwidget.h> #include <qpushbutton.h> #include <qlineedit.h> #include <qdatetimeedit.h> class CustWind : public QWidget { Q_OBJECT public: CustWind(QWidget* parent = nullptr); private: QLineEdit* txtName; QLineEdit* txtWish; QDateEdit* txtDate; QPushButton* btnCopy; QPushButton* btnPaste; // 與 clicked 信號綁定的方法(Slots) void onCopy(); void onPaste(); }; #endif
在包含頭文件時,用帶 .h 後綴和不用後是一樣的,既可以用 <QWidget> 也可以用 <qwidget.h>,只是作兼容之用。
在構造函數中,初始化各類可視對象。
CustWind::CustWind(QWidget *parent) : QWidget::QWidget(parent) { // 初始化組件 txtName = new QLineEdit(); txtWish = new QLineEdit(); txtDate = new QDateEdit(); // 有效日期範圍 txtDate -> setMinimumDate(QDate(1950, 1, 1)); txtDate -> setMaximumDate(QDate(2085, 12, 31)); btnCopy = new QPushButton("複製生日賀卡"); btnPaste = new QPushButton("粘貼生日賀卡"); // 佈局 QFormLayout* layout = new QFormLayout(this); layout->addRow("姓名:", txtName); layout->addRow("生日:", txtDate); layout->addRow("祝福語:", txtWish); QVBoxLayout *sublayout = new QVBoxLayout(); sublayout->addWidget(btnCopy); sublayout->addWidget(btnPaste); layout ->addRow(sublayout); // 連接信號和槽 connect(btnCopy,&QPushButton::clicked,this,&CustWind::onCopy); connect(btnPaste,&QPushButton::clicked,this,&CustWind::onPaste); }
這個例子咱們用 QFormLayout 來佈局界面。其含義和 HTML 中 <form> 元素差不多,即表單。
下面重點說兩個 slot 方法。
第一個是 onCopy ,由複製按鈕的點擊觸發。
void CustWind::onCopy() { // 獲取數據 QString name = txtName->text(); QString wish = txtWish->text(); QDate birthdate = txtDate->date(); if(name.isEmpty()) { return; //姓名是空的 } // 開始序列化 QByteArray data; QBuffer buff(&data); // buffer 要先打開 buff.open(QBuffer::WriteOnly); QDataStream output(&buff); // 寫入數據 output << name << birthdate << wish; buff.close(); // 包裝數據 QMimeData packet; // 錯誤 packet.setData("application/bug", data); // 把數據放到剪貼板 QClipboard* cb = QApplication::clipboard(); cb->setMimeData(&packet); QMessageBox::information(this,"恭喜","生日賀卡複製成功",QMessageBox::Ok); }
Qt 裏面常用 QDataStream 類來做序列化和反序列化操作。由於它有運算符重載,我們可以使用 C++ 入門時最熟悉的 <<、>> 運算符來輸入輸出。向 QDataStream 對象寫入數據時:
dataStream << a << b << c;
反序列化時:
dataStream >> a >> b >> c;
運算符很TM生動形象地描述出數據的流動方向。注意輸入和輸出時,數據的順序必須一致。
QMimeData 類用 setData 方法設置自定義數據時,參數接收的類型是 QByteArray(字節數組)而不是 QDataStream 對象,因此,我們要用 QBuffer 類來中轉一下。
1、創建 QByteArray 實例;
2、創建 QBuffer 實例,關聯 QByteArray 實例;
3、創建 QDataStream 實例,關聯 QBuffer 實例。
QDataStream 類需要 QIODevice 的派生類來完成讀寫操作,而 QByteArray 類不是 QIODevice 的子類,故要用 QBuffer 來過渡一下。當然了,老周這裏爲了讓這個思路更清晰一些,所以“中規中矩”地寫代碼。其實,QDataStream 類有接收 QByteArray 類型的參數的,可以省略 QBuffer 的代碼。QDataStream 類內部自動創建 QBuffer 對象。
更正:上面 onCopy 方法的代碼有問題,當數據被“粘貼”(讀取)後會引發空指針引用,致使程序崩掉。爲了給大夥們提供參考,避免犯同類錯誤,所以,老周沒有刪除上面的代碼。現在我放出修改後的 onCopy 方法。
void CustWind::onCopy() { …… // 包裝數據 QMimeData* packet = new QMimeData; packet -> setData("application/bug", data); // 把數據放到剪貼板 QClipboard* cb = QApplication::clipboard(); cb->setMimeData(packet); …… }
問題出在創建 QMimeData 實例時,我第一次的代碼是棧上分配實例的,Qt 無法在後續的事件循環中管理其內存。所以,QMimeData 要用指針類型,以 new 運算符來創建實例。
第二個 slot 方法是 onPaste,實現賀卡的粘貼。
void CustWind::onPaste() { // 訪問剪貼板 QClipboard* cb=QApplication::clipboard(); const QMimeData* dataPack = cb->mimeData(); // 判斷一下有沒有我們要的數據 if(!dataPack->hasFormat("application/bug")) { return; } // 取出數據 QByteArray data = dataPack->data("application/bug"); // 反序列化 QBuffer buff(&data); // 記得先打開 buffer buff.open(QBuffer::ReadOnly); QDataStream input(&buff); // 注意讀的順序 QString name; QDate birth; QString wish; input >> name >> birth >> wish; // 顯示數據 this->txtName->setText(name); this->txtDate->setDate(birth); this->txtWish->setText(wish); }
反序列化的原理與序列化是一樣的,只是反向操作罷了。注意讀寫數據的順序,寫的時候是姓名 - 生日 - 祝福語,讀的時候也必須按這個順序。
最後,是 main 函數。
int main(int argc, char* argv[]) { QApplication myapp(argc, argv); CustWind mwindow; mwindow.show(); return myapp.exec(); }
CMake 文件(CMakeLists.txt)就按照標準文檔上直接抄就行了。
cmake_minimum_required(VERSION 3.20) project(demo LANGUAGES CXX) find_package( Qt6 REQUIRED COMPONENTS Core Gui Widgets ) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) add_executable(demo app.h app.cpp) target_link_libraries( demo PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets )
重點就是三步:
1、聲明 CMake 文檔支持的版本,應用項目的名稱(target)。
2、auto moc 一定要打開,否則編譯時會掛。
3、添加源代碼、鏈接相關的庫。
好了,完工了,咱們試試。
先運行一個程序實例,輸入相關信息,點複製按鈕。
然後,關閉這個程序重新運行,或者再運行一個新程序實例,點粘貼按鈕。
這樣,就完成了自定義數據的複製和粘貼。