主頁 > 後端開發 > 【VS Code+Qt6】拖放操作

【VS Code+Qt6】拖放操作

2023-05-29 07:45:36 後端開發

由于老周的示例代碼都是用 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 = https://www.cnblogs.com/tcjiaan/archive/2023/05/28/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 = https://www.cnblogs.com/tcjiaan/archive/2023/05/28/event->mimeData();
    if (data->hasUrls())
    {
        event->acceptProposedAction();
    }
}

void MyLabel::dropEvent(QDropEvent *event)
{
    // 再次驗證一下資料
    const QMimeData *data = https://www.cnblogs.com/tcjiaan/archive/2023/05/28/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 上了,注意左邊是美琪,右邊是美雪,下面的標簽是她倆的名字,

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/553639.html

標籤:其他

上一篇:Python asyncio之協程學習總結

下一篇:返回列表

標籤雲
其他(159867) Python(38178) JavaScript(25460) Java(18141) C(15232) 區塊鏈(8268) C#(7972) AI(7469) 爪哇(7425) MySQL(7214) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5343) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4577) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2434) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1977) 功能(1967) Web開發(1951) HtmlCss(1948) C++(1924) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1878) .NETCore(1862) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • 【VS Code+Qt6】拖放操作

    由于老周的示例代碼都是用 VS Code + CMake + Qt 寫的,為了不誤匯入,在標題中還是加上“VS Code”好一些。 上次咱們研究了剪貼板的基本用法,也了解了叫 QMimeData 的重要類。為啥要強調這個類?因為接下來扯到的拖放操作也是和它有關系。哦,對了,咱們先避開一下主題,關于剪 ......

    uj5u.com 2023-05-29 07:45:36 more
  • Python asyncio之協程學習總結

    ## 實踐環境 Python 3.6.2 ## 什么是協程 **協程**(Coroutine)一種電腦程式組件,該程式組件通過允許暫停和恢復任務,為非搶占式多任務生成子程式。**協程**也可以簡單理解為協作的程式,通過協同多任務處理實作并發的函式的變種(一種可以支持中斷的函式)。 下面,我們通過日常 ......

    uj5u.com 2023-05-29 07:45:19 more
  • Java 網路編程 —— 創建非阻塞的 HTTP 服務器

    ## HTTP 概述 HTTP 客戶程式必須先發出一個 HTTP 請求,然后才能接收到來自 HTTP 服器的回應,瀏覽器就是最常見的 HTTP 客戶程式。HTTP 客戶程式和 HTTP 服務器分別由不同的軟體開發商提供,它們都可以用任意的編程語言撰寫。HTTP 嚴格規定了 HTTP 請求和 HTTP ......

    uj5u.com 2023-05-29 07:45:07 more
  • 527訓練總結

    訓練內容:2023江西省賽VP 賽后總結: 比賽程序: 做了簽到以后純純開始坐牢...... 策略失誤: I題被定位成簽到題也過了十四個人,但是后續沒有花更多的時間去看,一直在鉆“如何存盤圖上路徑”的牛角尖,沒有往“存在巧妙解法”這個角度思考。另外寫dfs的假解法的程序中發現對vector的基本洗掉 ......

    uj5u.com 2023-05-29 07:45:03 more
  • FreeSWITCH添加自定義endpoint

    作業系統 :CentOS 7.6_x64 FreeSWITCH版本 :1.10.9 日常開發程序中會遇到需要擴展FreeSWITCH對接其它系統的情況,這里記錄下撰寫FreeSWITCH自定義endpoint的程序。 一、模塊定義函式 使用FreeSWITCH自帶的框架來定義模塊函式,函式指標及引數 ......

    uj5u.com 2023-05-29 07:44:41 more
  • 【python基礎】基本資料型別-字串型別

    # 1.初識字串 字串就是一系列字符。在python中,用引號括起來文本內容的都是字串。 其語法格式為:‘文本內容’或者“文本內容” 我們發現其中的引號可以是單引號,也可以是雙引號。這樣的靈活性可以使我們進行引號之間的嵌套。 撰寫程式如下所示: ![image](https://img2023 ......

    uj5u.com 2023-05-29 07:44:15 more
  • Rust Web 全堆疊開發之自建TCP、HTTP Server

    # Rust Web 全堆疊開發之自建TCP、HTTP Server ## 課程簡介 ### 預備知識 - Rust 編程語言入門 - https://www.bilibili.com/video/BV1hp4y1k7SV ### 課程主要內容 - WebService - 服務器端Web App - ......

    uj5u.com 2023-05-29 07:43:58 more
  • Python 標準類別庫-因特網資料處理之Base64資料編碼

    該模塊提供將二進制資料編碼為可列印ASCII字符并將這種編碼解碼回二進制資料的功能。它為[RFC 3548](https://tools.ietf.org/html/rfc3548.html)中指定的編碼提供編碼和解碼功能。定義了Base16、Base32和Base64演算法,以及事實上的標準Asci ......

    uj5u.com 2023-05-29 07:43:47 more
  • 關于STL容器的簡單總結

    # 關于STL容器的簡單總結 ## 1、結構體中多載運算子的示例 ``` //結構體小于符號的多載 struct buf { int a,b; bool operator queuea; //定義 a.push(x); //壓入 a.pop(); //彈出 a.size(); //取大小 a.fro ......

    uj5u.com 2023-05-29 07:43:38 more
  • 樹莓派使用HC-SR04超聲波測距

    ### 超聲波模塊介紹 超聲波測距原理很簡單: 1、通過記錄發送超聲波的時間、記錄超聲波回傳的時間,回傳時間與發送時間相減得到超聲波的持續時間。 2、通過公式:(**超聲波持續時間** * **聲波速度**) / **2**就可以得出距離; ![image.png](https://img2023. ......

    uj5u.com 2023-05-29 07:43:26 more