【Qt 6】讀寫剪貼板

剪貼板是個啥就不用多介紹了,最直觀的功能是實現應用程序之間數據共享。就是咱們常說的“複製”、“粘貼”功能。

在 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、添加源代碼、鏈接相關的庫。

 

好了,完工了,咱們試試。

先運行一個程序實例,輸入相關信息,點複製按鈕。

然後,關閉這個程序重新運行,或者再運行一個新程序實例,點粘貼按鈕。

這樣,就完成了自定義數據的複製和粘貼。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章