由於老周的示例代碼都是用 VS Code + CMake + Qt 寫的,為了不誤導人,在標題中還是加上“VS Code”好一些。 上次咱們研究了剪貼板的基本用法,也瞭解了叫 QMimeData 的重要類。為啥要強調這個類?因為接下來扯到的拖放操作也是和它有關係。哦,對了,咱們先避開一下主題,關於剪 ...
由於老周的示例代碼都是用 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 上了。註意左邊是美琪,右邊是美雪,下麵的標簽是她倆的名字。