5

【VS Code+Qt6】拖放操作 - 东邪独孤

 11 months ago
source link: https://www.cnblogs.com/tcjiaan/p/17429302.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

【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 其实是静态成员,但调用时用变量名或类然都可以。变量名就用成员运算符“.”,类名就用成员运算符“::”。

运行程序后,随便找个地方复制一些文本,然后回到程序窗口,你会有惊喜的。

367389-20230525093635301-1514066886.png

上图表明,程序已经能监听剪贴板的数据变化了。

------------------------------------------------------------- 量子分隔线 ------------------------------------------------------------

好了,下面开始咱们的主题——拖放。这两个动词言简意赅,包含了两个行为:

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 的不同取值会改变鼠标指针的图标,所以说这些值是给用户看的。详细可粗略看看下面表格,不需要深挖。

367389-20230527190442255-494549096.png 复制
367389-20230527190541611-832880555.png 移动
367389-20230527190614052-744542290.png 链接

“复制”是箭头右下角显示加号(+),“移动”是显示向右的箭头,“链接”是一个“右转”大箭头。如果忽略或禁止拖放,就是大家熟悉的一个圈圈里面一条斜线——367389-20230527191155901-180893653.png

在调用 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++ 的插入,因为里面出现了 /*,被识别成了注释,所以上面内容后半部分全绿了。

项目结构是这样的:

367389-20230528121130629-219786457.png

下面是头文件。

#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();
}

运行示例后,打开一个文本编辑器(如记事本),在窗口上按下鼠标左键,拖到文本编辑器,文本就发送到目标窗口了。

367389-20230528121400334-1296054017.gif

然后,咱们来看 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 上了。注意左边是美琪,右边是美雪,下面的标签是她俩的名字。

367389-20230528175632492-144184858.gif

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK