【VS Code+Qt6】拖放操作

由於老周的示例代碼都是用 VS Code + CMake + Qt 寫的,爲了不誤導人,在標題中還是加上“VS Code”好一些。

上次咱們研究了剪貼板的基本用法,也瞭解了叫 QMimeData 的重要類。爲啥要強調這個類?因爲接下來扯到的拖放操作也是和它有關係。哦,對了,咱們先避開一下主題,關於剪貼板,咱們還要說一點:就是如何監聽剪貼板內數據的變化並做出響應。這個嘛,就有點像迅雷監聽剪貼板的功能,發現你複製的東西里包含有下載地址的話,就自動彈出新下載任務窗口。

QClipboard 類有好幾個滿足此功能的信號,說這個前咱們要先知道一下 QClipboard 類包含一個 Mode 枚舉。這個枚舉定義了三個成員:

QClipboard::Clipboard:數據存儲在全局剪貼板中。此模式是各系統通用的,尤其是 Windows。

QClipboard::Selection:通過鼠標選取數據。X 窗口系統是 C/S 架構,數據選擇後會發送到目標窗口,可用鼠標中鍵粘貼。

QClipboard::FindBuffer:macOS 專用的粘貼方式。

所以,我們寫代碼時一般不刻意指定某個 Mode,以保證好的兼容性。現在,咱們回頭再看看 QClipboard 類的幾個信號。

selectionChanged:當全局鼠標選取的數據改變時發出,這個用在 Linux/X11 窗口系統上。

findBufferChanged:一樣道理,只在 macOS 上能用到。

dataChanged:這個比較推薦,不考慮 Mode,只要剪貼板上的數據有變化就會發出,通用性好。

changed:這個最靈活,在發出信號時,會帶上一個 Mode 參數,你在代碼中處理時可以對 Mode 進行分析。

綜上所述,要是隻關心剪貼板上的數據變化,連接 dataChanged 信號最合適。下面來個例子。

CMakeLists.txt:

cmake_minimum_required(VERSION 3.0.0)
set(CMAKE_AUTOMOC ON)
project(myapp VERSION 0.1.0)
find_package(Qt6 COMPONENTS Core Gui Widgets)

# 頭文件與源碼文件都在當前目錄下,“.”是當前目錄
include_directories(.)
set(SRC_LIST CustWindow.cpp main.cpp)

add_executable(myapp ${SRC_LIST})
target_link_libraries(myapp PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Widgets
)

CustWindow.h:

#ifndef CUST_H
#define CUST_H

#include <QApplication>
#include <QWidget>
#include <QMimeData>
#include <QClipboard>
#include <QListWidget>

class MyWindow : public QListWidget
{
    Q_OBJECT

public:
    MyWindow(QWidget* parent=nullptr);

private:
    void onDataChanged();
};

#endif

onDataChanged 是私有成員,待會兒用來連接 QClipboard::dataChanged 信號。這個例子中,老周選用的基類是 QListWidget,它是 QListView 的子類,但用起來比 QListView 方便,不需要手動設置 View / Model,直接可以 addItem,很省事。此處老周是想當剪貼板上放入新的文本數據時在 QListWidget 上添加一個子項。

下面是實現代碼:

#include "CustWindow.h"

MyWindow::MyWindow(QWidget *parent)
    : QListWidget(parent)
{
    // 獲取剪貼板引用
    QClipboard* clb = QGuiApplication::clipboard();
    // 連接信號
    connect(clb, &QClipboard::changed, this, &MyWindow::onDataChanged);
}

void MyWindow::onDataChanged()
{
    QClipboard* clipbd = QApplication::clipboard();
    QString s = clipbd ->text();
    // 如果剪貼板中包含文本,那麼字符串不爲空
    if(!s.isEmpty())
    {
        // 顯示文本
        this->addItem("你複製了:" + s);
    }
}

代碼並不複雜,重要事情有二:第一,連接 QClipboard::dataChanged 信號,與 onDataChanged 方法綁定。第二,在 onDataChanged 方法內,讀取剪貼板上的文本數據,組成新的字符串,調用 addItem 方法,把字符串添加到 QListWidget (基類)對象中。

main 函數的代碼就那樣了,先創建應用程序對象,然後初始化、顯示窗口,再進入事件循環。都是老套路了。

#include "CustWindow.h"
#include <QApplication>

int main(int argc, char** argv) {
    QApplication app(argc, argv);
    MyWindow win;
    win.setWindowTitle("監視粘貼板");
    win.resize(350, 320);
    win.show();
    return app.exec();
}

順便說一下,exec 其實是靜態成員,但調用時用變量名或類然都可以。變量名就用成員運算符“.”,類名就用成員運算符“::”。

運行程序後,隨便找個地方複製一些文本,然後回到程序窗口,你會有驚喜的。

上圖表明,程序已經能監聽剪貼板的數據變化了。

------------------------------------------------------------- 量子分隔線 ------------------------------------------------------------

好了,下面開始咱們的主題——拖放。這兩個動詞言簡意賅,包含了兩個行爲:

a、拖(Drag):數據發送者,發起數據共享操作。此行爲一般是鼠標(或筆,或手指,或其他)在某個對象上按下並移動特定距離後觸發。

b、放(Drop):把拖動的數據放置到目標對象上,數據接收者提取到數據內容,並結束整個共享操作。一般是鬆開鼠標按鍵(或筆,或手指,或其他)時結束拖放操作。

由於拖放操作是由鼠標等指針設備引發的,爲了減少誤操作,通常會附加兩個約束條件:

1、鼠標按下後一段時間,這個時間可以很短。可通過 QApplication::setStartDragTime 方法設置你喜歡的值,單位是毫秒。默認 500 ms。

2、鼠標按下後必須移動一定的距離。這個距離可以從 QApplication::startDragDistance 方法獲取,也可以通過 setStartDragDistance 方法修改。這距離指的是“曼哈頓”距離,這個距離是兩個點在與X軸和Y軸平行的距離之和,就是正東、正西、正南、正北的方向。總之不是直線距離,這是爲了避開大量浮點、開平方等複雜運算,提升速度。具體可以查資料。不懂這個也不影響編程,Qt 的 QPoint 類自帶 manhattanLength 方法,可以獲得兩點相減後的曼哈頓距離。

 ----------------------------------------------------------------------------------------------------

QDrag類

這個類是拖放操作的核心,因爲它的 exec 方法會啓動一個拖放操作。拖放操作與剪貼板類似,也是使用 QMimeData 類來封送數據的。在調用 QDrag::exec 之前要用 setMimeData 方法設置要傳遞的數據。

exec 方法返回時,拖放操作已結束。其返回值是 Qt::DropAction 枚舉,拖放操作完成時所返回的值可由數據接收者設置。

DropAction枚舉

該枚舉定義下面幾個值:

1、CopyAction:表示拖放操作將複製數據;

2、MoveAction:表示拖放操作會移動數據;

3、LinkAction:僅僅建立從數據源到數據目標的鏈接;

4、IgnoreAction:無操作(忽略)。

其實,這些 Action 是反饋給用戶看的,在數據傳遞的過程中毫無干擾。也就是說,不管是 Copy 還是 Move,只不過是一種“語義”,具體怎麼處理數據,還是 coder 說了算。

DropAction 的不同取值會改變鼠標指針的圖標,所以說這些值是給用戶看的。詳細可粗略看看下面表格,不需要深挖。

複製
移動
鏈接

“複製”是箭頭右下角顯示加號(+),“移動”是顯示向右的箭頭,“鏈接”是一個“右轉”大箭頭。如果忽略或禁止拖放,就是大家熟悉的一個圈圈裏面一條斜線——

在調用 QDrag::exec 方法時你可以指定 DropAction 值,通常有兩個參數要賦值:

Qt::DropAction exec(Qt::DropActions supportedActions = Qt::MoveAction);
Qt::DropAction exec(Qt::DropActions supportedActions, Qt::DropAction 
defaultAction);
supportedActions 參數表示你規定數據接收者只能使用的 DropAction 值。比如,你在發起拖放時指定 supportedActions = CopyAction | MoveAction,那麼,數據接收者在讀取數據時,你只能向用戶反饋“複製”或“移動”圖標,你不能用 LinkAction。
而 defaultAction 參數是給數據接收者建議的操作,只能在 supportedActions 的值裏面選。
比如,supportedAction = Move | Copy | Link,那麼,defaultAction = Copy 可以,defaultAction = Move 也可以。

拖放操作啓動的條件

拖放操作是與鼠標有關的,一般會在處理 mousePress、mouseMove 事件時觸發。

數據接收者

接收數據是在 Drop 操作時發生——即東西已順利拖到目標上,並且放開鼠標按鍵。
數據接收者將通過處理下面幾個神祕事件來提取數據的:
1、dragEnter:鼠標把某個東西拖進當前對象的邊界時發生。假設當前對象是一個窗口,那麼,當拖動進入窗口的邊沿時就會觸發該事件;
2、dragMove:東西被拖進來了,過了邊界,但鼠標仍在移動。此時會不斷觸發 dragMove 事件。這事件是連續發生的,除非你鼠標不動。要是一時手抖放開了鼠標左鍵,那就觸發了 drop 事件,拖放操作結束;
3、dragLeave:東西拖進來後,沒有釋放,繼續拖,最終離開當前對象的邊界——拖出去了(斬了),就會觸發 dragLeave 事件;
4、drop:釋放鼠標,標誌拖放操作結束。此時你得讀出別人傳給你的數據了。
dragLeave 事件一般很少處理,幹得比較多的是 dragEnter 和 drop。當 dragEnter 發生時,通常要分析一下,拖過來的數據是不是我想要的。別人扔給你的有可能是炸彈,所以要判斷一下,不接受的數據直接 ignore(忽略)。如果數據是你想要的,就 accept 它,然後在釋放時會發生 drop 事件;在 drop 事件中把你要的數據讀出來就完事了。當然了,QMimeData 中的數據你不見得要全讀出來,你只取你所要的部分。如果在 dragEnter 事件中拒絕數據,那麼釋放時是不會發生 drop 事件的。
 
爲了讓大夥伴們更好地理解,drag 和 drop 兩個過程咱們分開說。
接下來,我們實現把文本數據從當前窗口拖到其他程序(如記事本)。
下面是 CMake 文件。
cmake_minimum_required(VERSION 3.20)
project(DragDemo LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
find_package(
    Qt6
    COMPONENTS
        Core Gui Widgets
    REQUIRED
)
# 找到項目下所有頭文件和源文件
file(GLOB_RECURSE SRC_LIST include/*.h src/*.cpp)
include_directories(include)
add_executable(DragDemo WIN32 ${SRC_LIST})
target_link_libraries(
    DragDemo
    PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Widgets
)

代碼插件沒有 CMake 的,老周用的是 C++ 的插入,因爲裏面出現了 /*,被識別成了註釋,所以上面內容後半部分全綠了。

項目結構是這樣的:

 

下面是頭文件。

#pragma once

#include <QWidget>
#include <QPainter>
#include <QMouseEvent>

class Demo : public QWidget
{
    Q_OBJECT
public:
    Demo(QWidget* parent=nullptr);
protected:
    void paintEvent(QPaintEvent* event) override;
    void mousePressEvent(QMouseEvent* event) override;
    void mouseMoveEvent(QMouseEvent* event) override;
private:
    // 這個私有變量用來臨時存儲鼠標按下的座標
    QPoint m_curpt;
};

這個類沒什麼特別的,就是一個自定義窗口。其中,重寫 paintEvent 方法,在窗口上畫提示文字。這個只爲了好看,你可以省略。

重點是重寫 mousePress 和 mouseMove 兩個事件,mousePress 時記下鼠標按下的座標,然後在 mouseMove 中再次獲取鼠標的座標,和按下時的座標相減,看看它們的曼哈頓距離是否符合啓動拖放的條件。

咱們來看實現代碼。

Demo::Demo(QWidget *parent)
{
    this->setWindowTitle("拖動示例");
    this->resize(258, 240);
    this->move(659, 520);
}

void Demo::paintEvent(QPaintEvent *event)
{
    QRect rect=event->rect();
    // 在窗口上繪製文本
    QFont font;
    font.setFamily("華文仿宋");     //字體名稱
    font.setPointSize(24);          //字體大小(點)
    font.setBold(true);             //加粗
    QPainter painter(this);
    // 設置字體
    painter.setFont(font);
    // 計算一下文本所佔空間
    QString textToDraw = "從此窗口拖動";
    QRect textRect = painter.fontMetrics().boundingRect(rect, Qt::AlignCenter, textToDraw);
    // 移動文本矩形,讓它的中心點和窗口矩形的中心點對齊
    textRect.moveCenter(rect.center());
    // 設置繪製文本的畫筆
    QPen pen;
    pen.setColor(QColor("red"));
    painter.setPen(pen);
    // 開始塗鴉
    painter.drawText(textRect.toRectF(), textToDraw);
    painter.end();
}

void Demo::mousePressEvent(QMouseEvent *event)
{
    // 獲取鼠標按下的座標點
    m_curpt = event->pos();
}

void Demo::mouseMoveEvent(QMouseEvent *event)
{
    // 獲取鼠標現在的位置座標
    QPoint curloc = event->pos();
    // 和剛纔按下去的座標比較
    if((m_curpt - curloc).manhattanLength() < QApplication::startDragDistance())
    {
        // 距離不夠,不啓動拖放
        return;
    }
    // 準備拖放
    QString str = "石灰水化死屍可作化肥";  //要傳送的數據
    QMimeData* mdata = new QMimeData;
    // 打包
    mdata -> setText(str);
    // 發快遞
    // QDrag(QObject *dragSource)
    // dragSource 指的是發起拖放操作的對象
    // 這裏是當前窗口
    QDrag drag(this);
    // 設置數據
    drag.setMimeData(mdata);
    // 出發
    auto result = drag.exec(Qt::CopyAction | Qt::LinkAction, Qt::CopyAction);
    QString displaymsg = "數據傳遞完畢,操作結果:";
    if(result & Qt::CopyAction)
    {
        displaymsg += "複製";
    }
    else if(result & Qt::LinkAction)
    {
        displaymsg += "鏈接";
    }
    else if(result & Qt::IgnoreAction)
    {
        displaymsg += "忽略";
    }
    else
    {
        displaymsg += "未知";
    }
    QMessageBox::information(this, "提示", displaymsg, QMessageBox::Ok);
}

paintEvent 的重寫不是重點,不過老周簡單說下。

a、創建 QFont 實例,你看名字都知道是什麼鬼了,是的,設置字體參數;

b、計算文本”從此窗口拖動“要佔多少空間,核心是調用 QFontMetrics 類的 boundingRect 方法。這裏要注意,調用的是這個重載:

QRect QFontMetrics::boundingRect(const QRect &r, int flags, const QString &text, int tabstops = 0, int *tabarray = (int *)nullptr) const

也就是說,不能調用只傳文本的重載,那個重載計算出來的 rect 寬度會變小,導致繪製出來的字符串少了一個字符(原因不明)。但,調用上面這個有N多參數的重載是沒問題。區別就在於給也一個 r 參數,這個參數提供一個矩形區域作爲約束。這裏老周用整個窗口的空間作爲約束。可能是給的空間足夠大,所以計算出來的寬度就足夠。於是老周厚着臉皮翻了一下 Qt 的源碼,這兩重載所使用的處理方法不一樣,參數比較多的那個裏面調用的是 qt_format_text 函數,參數較少的那個裏面用的是 QStackTextEngine 類。有興趣的夥伴可以去翻翻。

moveCenter 是使矩形平移,並且中心點對準窗口矩形區域的中心點。這裏可以讓繪製的文本處在窗口的中央。

接下來說說  mousePress 事件,這裏就很簡單了,就是直接記錄鼠標的位置。不過,有點不嚴謹,拖放操作沒聽說過用鼠標右鍵操作的吧?所以,此處最好判斷一下,是不是左鍵按下。

void Demo::mousePressEvent(QMouseEvent *event)
{
    if(!(event -> buttons() & Qt::LeftButton))
    {
        return;
    }
    // 獲取鼠標按下的座標點
    m_curpt = event->pos();
}

mouseMove 事件也是如此。

void Demo::mouseMoveEvent(QMouseEvent *event)
{
    if(!(event -> buttons() & Qt::LeftButton))
    {
        return;
    }
    ……
}

QDrag::exec 方法是在 mouseMove 事件中啓動的,這個就和剪貼板的操作相似了。先創建 QMimeData,設置文本數據,然後創建 QDrag 實例,設置 MimeData,然後就調用 exec 方法。

最後是整個程序的 main 函數。

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    Demo window;
    window.show();
    return QApplication::exec();
}

運行示例後,打開一個文本編輯器(如記事本),在窗口上按下鼠標左鍵,拖到文本編輯器,文本就發送到目標窗口了。

 

然後,咱們來看 drop 操作。

要想讓某個組件支持放置行爲,你必須調用:

setAcceptDrops(true);

默認是不開啓的,所以必須調用一次 setAcceptDrops 方法。

當某個組件(可以是窗口,按鈕,標籤,文本框等)支持放置行爲後,把數據拖到該組件上會引發 dragEnter、dragMove 等事件;釋放鼠標時會發生 drop 事件,表示整個拖放操作結束。這個上文已講過,下面重點看幾個事件參數。注意了,這幾個廝實際上是有繼承關係的。

class QDropEvent : public QEvent
class QDragMoveEvent : public QDropEvent
class QDragEnterEvent : public QDragMoveEvent
// 下面這個是特例
class QDragLeaveEvent : public QEvent

QDragLeaveEvent 是直接派生自 QEvent 的,因爲它是在 dragLeave 事件發生時使用,數據被拖出當前對象,一般不需要額外攜帶什麼參數,所以這個事件類比較特殊。

QDropEvent 類用於 drop 事件,因爲這時候你得讀取數據了,所以它會夾帶私貨。這些私貨分兩類:

1、跟鼠標有關的。比如 buttons 返回鼠標按下了哪個鍵;modifiers 返回值表示用戶是否在拖動的同時按下 Ctrl、Alt、Shift 等按鍵。position 返回鼠標指針的當前座標。這些參數咱們通常用不上的。

2、和共享的數據相關的。這個是最需要的。mimeData 返回 QMimeData 對象的指針,然後咱們就能讀數據了。source 返回發起拖放操作的對象,一般我們的程序不太關注數據源。

不管讀不讀取數據,作爲數據接收者,我們是文明的,有禮貌的。拖放操作完成時咱們應該響應一下發送者—— QDrag::exec 方法(如果數據是從其他程序拖過來的,那麼,拖放的發起者就不一定是調用 exec 方法,畢竟人家不見得是用 Qt 寫的,說不定是用 WPF 做的)。

扯遠了,回到主題,向數據發送者反饋,還是涉及到了 DropActions 的事。DropEvent 提供了這些成員,可以訪問 action。

1、possibleActions 方法,對應的是 exec 方法的 supportedActions 參數;

2、proposedAction 方法,對應 exec 方法的 defaultAction 參數。

還記得前文說過的 exec 方法的兩個參數嗎?嗯,是滴,possibleActions 就是 supportedActions 參數提供的有效範圍,你只能在這些值中選一個。proposedAction 是建議的值,也就是 defaultAction 參數提供的默認值。

所以,如果我們的程序比較在意使用什麼 action 的話,你得好好分析一下這兩個方法返回的值了。不過,多數時候,我們只關心 mimeData 返回的內容,因爲那是要提取的數據。

如果你成功接收了數據,那麼要調用 acceptProposedAction 方法,表示數據和 defaultAction 你都接受了。

如果你不想用 defaultAction 參數推薦的默認 action,那麼,你可以調用 QDropEvent::setDropAction 方法自己設置一個 action,但你設置的 action 必須在 possibleActions 中允許的。如果你調用了 setDropAction 方法,就等於修改了默認 action,所以這時候你只能調用 accept 方法來接受,不能再調用 acceptProposedAction 方法了。不然,acceptProposedAction 方法會還原默認 action 的值。

如果你發現數據不是你想要的,或者數據發送者給的 DropAction 你不接受,那你就調用 ignore 方法忽略,或者你什麼都不做也可以(默認會 ignore 掉事件)。

QDragEnterEvent 和 QDragMoveEvent 都是 QDropEvent 的子類,所以成員都是差不多的。就不用老周再廢話了。

瞭解這幾個類的關係,你就知道怎麼處理接收拖動的過程了。下面我們來個例子,把圖片文件拖到咱們的程序,然後會顯示該圖片。就是拖動打開文件了。

從 QLabel 類派生出一個類,咱們就用它來接收並顯示圖片。Qt 沒有專門顯示圖片的組件,一般用 QLabel 來顯示圖片。當然,QPushButton 等按鈕組件也可以顯示圖片,不過通常用作顯示小圖標。有大夥伴會說,QGraphicsView 什麼什麼的不用嗎?那個就太大動作了,簡直是殺小強用牛刀,沒有必須,我就想顯示個圖片而已。

#pragma once

#include <QLabel>
#include <QDragEnterEvent>
#include <QResizeEvent>
#include <QDropEvent>

class MyLabel : public QLabel
{
    Q_OBJECT
public:
    MyLabel(QWidget* parent=nullptr);
protected:
    void dragEnterEvent(QDragEnterEvent* event) override;
    void dropEvent(QDropEvent* event) override;
    void resizeEvent(QResizeEvent* event) override;
private:
    // 用來緩存圖像
    QPixmap m_image;
};

事件不算多,就重寫三個事件。另外,還聲明瞭一個私有成員 m_image 用來存圖像資源。你可能會問了,QLabel 不是可以設置和獲取 QPixmap 對象嗎,爲什麼要特地用一個私有成員來保存?因爲 QLabel 上顯示的圖像,咱們一般會縮小一下再顯示。經過縮小後的 QPixmap 對象,再重新放大就變得很模糊了。所以,QLabel::Pixmap 不保存原圖。

在構造函數中,讓這個標籤組件支持放置。

MyLabel::MyLabel(QWidget *parent)
    : QLabel(parent)
{
    this->setAcceptDrops(true);
    this->setStyleSheet("background-color: gray");
}

setAcceptDrops 開啓 drop 支持。還有一個是 setStyleSheet,這裏老周是用 QSS 來設置標籤的背景顏色爲難看的灰色。這是 Qt 搞的裝X玩意兒,用起來有點像 HTML 中的 CSS。

又有夥伴問了,QLabel 不是有個帶 text 參數的構造函數嗎?對,不過這裏不需要,咱們這個自定義組件不顯示文本。

然後,實現 resizeEvent,當大小改變時,咱們也調整一下標籤上的圖像大小(其實是重新加載縮放過的圖像)。

void MyLabel::resizeEvent(QResizeEvent *event)
{
    if(!m_image.isNull())
    {
        // 獲取當前新調整的大小
        QSize labelsize = event->size();
        // 縮放圖像
        auto pixmap = m_image.scaled(labelsize, Qt::KeepAspectRatio, Qt::SmoothTransformation);
        // 重新設置圖像
        this->setPixmap(pixmap);
    }
}

最後就是跟drop 有關的兩個事件了。

void MyLabel::dragEnterEvent(QDragEnterEvent *event)
{
    // 檢查一下是不是所需要的數據
    const QMimeData *data = event->mimeData();
    if (data->hasUrls())
    {
        event->acceptProposedAction();
    }
}

void MyLabel::dropEvent(QDropEvent *event)
{
    // 再次驗證一下數據
    const QMimeData *data = event->mimeData();
    if (data->hasUrls())
    {
        // 讀數據
        QList<QUrl> paths = data->urls();
        if (paths.size() > 0)
        {
            QUrl p = paths.at(0);
            QString locfile = p.toLocalFile();
            m_image.load(locfile);
        }
        // 縮放一下
        auto pix = m_image.scaled(this->size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
        this->setPixmap(pix);
        event->acceptProposedAction();
    }
}

dragEnter 的時候,只是看看有沒有想要的數據,不讀。讀取是在 drop 事件中完成。但是爲了防止概率 0.001% 的靈異事件發生,在 drop 事件處理時還要再檢驗一下數據是不是有效。

文件拖進來,一般是 URL 類型,獲取到的對象是 QUrl 類型,它的格式是 file:///xxxxx,這個路徑在 load 方法中加載不了,於是得用 toLocalFile 方法,將 URL 轉換爲本地文件路徑,這樣就能在 QPixmap::load 方法中加載圖像了。

下面,定義一個窗口,實例化兩個 MyLabel 組件,放在網格佈局中第一行的兩個單元格內。

/* 頭文件 */
#pragma once

#include <QWidget>
#include "custlabel.h"
#include <QGridLayout>

class MyWindow : public QWidget
{
    Q_OBJECT

public:
    MyWindow(QWidget* parent=nullptr);
private:
    MyLabel *lbImg1, *lbImg2;
    QLabel *lb1, *lb2;
    QGridLayout *layout;
};

兩個 QLabel 組件用來顯示普通文本,咱們自己弄的 MyLabel 組件用來顯示圖片。QGridLayout 是佈局用的,以網格形式佈局(行、列)。

MyWindow::MyWindow(QWidget *parent)
    :QWidget(parent)
{
    setWindowTitle("放置圖像");
    resize(450, 400);
    // 初始化
    lb1 = new QLabel("美琪", this);
    lb2 = new QLabel("美雪", this);
    lb1->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
    lb2->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed));
    lbImg1 = new MyLabel(this);
    lbImg2 = new MyLabel(this);
    layout = new QGridLayout(this);
    // 佈局
    layout->addWidget(lbImg1, 0, 0);
    layout->addWidget(lbImg2, 0, 1);
    layout->addWidget(lb1, 1, 0);
    layout->addWidget(lb2, 1, 1);
}

setSizePolicy 那兩行是爲了讓 QLabel 組件的高度固定,因爲 QGridLayout 這個王八不能設置固定的行高和列寬,所以只能出此下策了。

寫上 main 函數。

int main(int argc, char* argv[])
{
    QApplication app(argc, argv);
    MyWindow win;
    win.show();
    return QApplication::exec();
}

運行程序後,就可以把圖片文件拖到兩個 MyLabel 上了。注意左邊是美琪,右邊是美雪,下面的標籤是她倆的名字。

 

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