在开始主题前,先看一个 C++ 例子:

#include <iostream>

struct Data
{
int a;
int b;
}; // 注意这里
struct Data *s; void doSome()
{
Data k;
k.a = 100;
k.b = 300;
// 注意这里,会出大事
s = &k;
} int main()
{
// 先调用了函数
doSome();
// 再输出 Data 结构体的内容
std::cout << "a = " << s->a << '\n';
std::cout << "b = " << s->b << '\n';
return 0;
}

不要问这个例子的功能,问就是超能力。其实这个例子没啥功能,纯粹是为了运行后出错而写的。有同学会疑惑:这程序好像没啥问题。嗯,看着是没啥问题,我们预期的情况是:a 的值是 100,b 的值是 300。

遗憾的是,运行结果是这样的:

a = -858993460
b = -858993460

啥玩意儿?下面咱们就扒一下到底哪里出事了。

这个例子先定义了一个结构体叫 Data,里面有两个字段 a、b。然后声明 Data 类型的指针变量,在 doSome 函数中让变量 s 引用了一个 Data 实例的实例。在 main 函数中,先调用 doSome 函数,然后再输出 a、b 的值。这里就出现一个问题了:s 引用的 k 是在 doSome 函数内创建的,而且它的数据分配在栈上,当 doSome 函数执行结束时,k 的生命周期也差不多了。当调用 doSome 函数之后访问 s,此时 s 所指向的对象已经没有了,所以 a、b 输出的是一个“脏”的值。

若是把 k 改为 static,那结果就不一样了。

void doSome()
{
static Data k;
k.a = 100;
k.b = 300;
// 注意这里,会出大事
s = &k;
}

控制台将输出:

a = 100
b = 300

如果你不相信上述现象,也可以把例子改成这样:

#include <iostream>

class Test
{
public:
Test()
{
std::cout << "Test 构造函数 ..." << std::endl;
} ~Test()
{
std::cout << "Test 析构函数 ..." << std::endl;
}
int a,b;
}; // 注意这里
Test *s; void doSome()
{
Test k;
k.a= 100;
k.b = 300;
// 注意这里,会出大事
s = &k;
} int main()
{
// 先调用了函数
std::cout << "调用doSome函数前\n";
doSome();
std::cout << "调用doSome函数后\n";
// 再输出a、b的内容
std::cout << "a = " << s->a << '\n';
std::cout << "b = " << s->b << '\n';
return 0;
}

运行上述代码,得到的输出为:

Test 构造函数 ...
Test 析构函数 ...
调用doSome函数后
a = -858993460
b = -858993460

这样就能清楚地知道,s 引用的对象在退出 doSome 函数之前就已经析构了。除了使用 static 关键字外,也可以让 Test 对象分配在堆上。

void doSome()
{
Test *k = new Test;
k->a = 100;
k->b = 300;
// 复制的是地址,不是对象
s = k;
}

把 k 赋值给 s,只是把指向的地址复制一遍罢了,对象实例并没有复制。栈上的数据会因变量的生命周期而被回收,但堆上的东西需要 delete。所以,在调用完 doSome 函数后,堆上的东西还在,所以输出的 a、b 值不会“脏”。按理说,s 用完了应该 delete 的,不过,我没写 delete 语句,毕竟这里 main 函数马上就执行完了,程序都结束了,堆上的东西早没了,所以,这里就偷偷懒吧,不必管它。

下面再来看一个 Qt 程序:

#include <QWidget>
#include <QApplication>
#include <QVBoxLayout>
#include <QPushButton> int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// 创建两个按钮
QPushButton btnA("Yes");
QPushButton btnB("No");
// 创建顶层窗口
QWidget window; // 构建对象树
btnA.setParent(&window);
btnB.setParent(&window);
// 设置按钮在窗口中的位置
btnA.move(28, 30);
btnB.move(28, 75); // 显示窗口
window.show(); return QApplication::exec();
}

上述程序也是一个有问题的程序,但它能运行,只是在关闭窗口时报错。

Unhandled exception at 0x00007FFDD029C1F9 (ntdll.dll) in myapp.exe: 0xC0000374: 堆已损坏。 (parameters: 0x00007FFDD03118A0).

这个问题和第一个例子的有点像但又不完全一样。这个 Qt 程序是一个经典错误,问题出在两个 QPushButton 对象被析构了两次。由于所有变量都是在栈上分配的,上述程序的压入顺序是 btnA - btnB - window。按照后进先出的规则,window 变量是最新定义的,它首先发生析构。由于 btnA、btnB 调用了 setParent 方法设置了对象树关系,当 window 析构时会删除 btnA、btnB。又因变量生命周期的原因,在 window 析构之后,btnA 和 btnB 又发生析构(可刚才 window 让它们析构过了)。

解决方法:1、调整声明变量的顺序,先声明 window 变量,再声明其他变量;2、用指针。

下面代码改为用指针类型。

#include <QWidget>
#include <QApplication>
#include <QVBoxLayout>
#include <QPushButton> int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// 创建两个按钮
QPushButton *btnA = new QPushButton("Yes");
QPushButton *btnB = new QPushButton("No");
// 创建顶层窗口
QWidget *window = new QWidget; // 构建对象树
btnA->setParent(window);
btnB->setParent(window);
// 设置按钮在窗口中的位置
btnA->move(28, 30);
btnB->move(28, 75); // 显示窗口
window->show(); return QApplication::exec();
}

这里咱们也不需要 delete,毕竟窗口和两个按钮在应用程序运行期间它们都必须存在的,只到了程序退出时才销毁,那就没必要 delete 了。

所以说:

1、不是所有指针变量都要 delete 的,因为它引用的可能不是堆上的对象,没准是栈上的对象;

2、不是所有 new 出来的对象就非要 delete 不可,主要看它的生命周期是否该结束。如果是短暂使用的,在应用程序运行期间不需要一直存在的,用完就要 delete。有些 new 出来的对象可能要传递给其他对象用,并由它们负责释放,那也不需要 delete,比如包装剪贴板数据的 QMimeData 类。

==========================================================================

好了,以上一大段内容就当作科普,正片现在才开始。本篇咱们看一下特殊的 QAction 类——QWidgetAction。看名字也可以联想到,它是可以把一个 QWidget 用作 action 的类。这个有什么用呢?作用就是你可以在菜单里做些交互功能。

QWidgetAction 类有两种用法:

1、直接用,这是最简单方法。实例化后调用 setDefaultWidget 方法设置一个 widget;

2、派生出子类,重写 createWidget 方法,创建你需要的组件对象。

先看第一种用法,非常好办,你想在菜单项上显示什么组件就创建它,然后调用 setDefaultWidget 方法就行了。

// 头文件
#ifndef APP_H
#define APP_H #include <QMainWindow>
#include <QWidget>
#include <QAction>
#include <QSpinBox>
#include <QMenu>
#include <QMenuBar>
#include <QWidgetAction> class MyWindow : public QMainWindow
{
public:
MyWindow();
}; #endif
/*---------------------------------------------*/
// 代码文件
MyWindow::MyWindow()
:QMainWindow((QWidget*)nullptr)
{
// 创建菜单栏
QMenuBar *menubar = this->menuBar();
// 创建菜单
QMenu *menu = menubar->addMenu("应用程序");
// 添加两个普通action,意思一下
menu->addAction("打开文件");
menu->addAction("关闭文件");
// 下面才是主角
QWidgetAction *widgetAct = new QWidgetAction(menu);
// 创建一个数字组件
QSpinBox *spinbox = new QSpinBox;
// 设置一下有效范围
spinbox->setRange(0, 1000);
// 设置当前值
spinbox->setValue(250);
// 设置为 QWidgetAction 的默认组件
widgetAct->setDefaultWidget(spinbox);
// 把action添加到菜单中
menu->addAction(widgetAct);
}

应用程序窗口继承了 QMainWindow 类,因为这个类比较方便构建菜单栏、工具栏、状态栏、停靠栏。咱们用它来创建一个菜单栏对象(QMenuBar),然后添加一个叫“应用程序”的菜单(QMenu)。

“应用程序”菜单的前两个菜单项是普通的 action,第三个是 QWidgetAction 对象。在 new 出 QWidgetAction 后,先初始化一下 QSpinBox 组件,然后调用 setDefaultWidget 方法,这样 QSpinBox 组件就能显示在菜单项上了。

在 main 函数中显示主窗口。

int main(int argc, char** argv)
{
QApplication app(argc, argv);
MyWindow *win = new MyWindow;
win->setWindowTitle("自定义菜单项");
win->resize(450, 400);
win->show();
return QApplication::exec();
}

好了,见证奇迹的时候到了,看看效果。

另一种用法,就是从 QWidgetAction 类派生。然后重写这个方法:

QWidget *createWidget(QWidget *parent);

parent 是父级对象,由调用者传递,这取决于这个自定义的 action 用在什么容器上了,如果用在菜单上,就是 QMenu 对象。返回值就是创建的自定义组件了。

另外,如果在析构自定义组件时有特殊处理,还可以重写 delete 方法。

void deleteWidget(QWidget *widget);

widget 参数是要被删除的自定义组件实例。如果无其他要实现的需求,没必要重写它。

下面咱们来个示例:自定义组件做个带三个滑块的界面。组件名称为 CustWidget,基类是 QFrame。选择 QFrame 作为基类是方便设置边框。

// 头文件
#ifndef CUSTWIDGET_H
#define CUSTWIDGET_H
#include <QWidget>
#include <QFrame> class CustWidget: public QFrame
{
public:
CustWidget(QWidget* parent = nullptr);
private:
void initUI();
};
#endif // 代码文件
#include "custWidget.h"
#include <QFormLayout>
#include <QSlider> CustWidget::CustWidget(QWidget *parent)
:QFrame::QFrame(parent)
{
this->initUI();
} void CustWidget::initUI()
{
// 创建布局
QFormLayout* layout = new QFormLayout(this);
// 创建三个滑条
QSlider* slider1 = new QSlider;
slider1->setRange(0,255); // 有效范围
QSlider* slider2 = new QSlider;
slider2->setRange(0,255);
QSlider* slider3 = new QSlider;
slider3->setRange(0,255);
// 设置滑条的方向是水平方向
slider1->setOrientation(Qt::Horizontal);
slider2->setOrientation(Qt::Horizontal);
slider3->setOrientation(Qt::Horizontal);
// 把它们添加到布局中
layout->addRow("Red:", slider1);
layout->addRow("Green:", slider2);
layout->addRow("Blue:", slider3);
// 设置边框为面板
this->setFrameShape(QFrame::Panel);
}

滑块条是 QSlider 组件,它默认的方向是垂直的,所以要将方向设定为水平。自定义组件还用到了 QFormLayout 类,它是布局类,类似 HTML Form 元素的布局方式,即表单。一般分为两列,左列是字段标题,右列是字段内容。

CustWidget 组件定义好了,接下来就是 MyWidgetAction 类,派生自 QWidgetAction。

// 头文件
#ifndef MYWIDGETACTION_H
#define MYWIDGETACTION_H #include <QWidgetAction>
#include "custWidget.h" class MyWidgetAction : public QWidgetAction
{
public:
MyWidgetAction(QObject *parent); protected:
QWidget *createWidget(QWidget *parent) override;
}; #endif // 代码文件
#include "myWidgetAction.h" MyWidgetAction::MyWidgetAction(QObject *parent)
:QWidgetAction::QWidgetAction(parent)
{
} QWidget *MyWidgetAction::createWidget(QWidget *parent)
{
CustWidget* w = new CustWidget(parent);
return w;
}

整体逻辑很简单,就是返回 CustWidget 的实例。

然后咱们在前面 QWidgetAction 的示例上再添加一个菜单项,使用咱们刚定义的 MyWidgetAction。

MyWindow::MyWindow()
:QMainWindow((QWidget*)nullptr)
{
// 创建菜单栏
QMenuBar *menubar = this->menuBar();
// 创建菜单
QMenu *menu = menubar->addMenu("应用程序");
……
// 下面这个是自定义的
MyWidgetAction *custAct = new MyWidgetAction(menu);
menu->addAction(custAct);
}

最后,咱们来看看效果。

这效果不错吧。

好了,今天就水到这里了,有空咱们继续聊。

【Qt6】QWidgetAction 的使用的更多相关文章

  1. QT6 源码杂记

    菜鸡一个,随便写写,勿喷.好记性不如烂笔头. 了解qt,第一个绕不过的坎便是qt的元对象系统 QMetaObject. 1 class Object : public QObject 2 { 3 Q_ ...

  2. 用VS Code搞Qt6:编译源代码与基本配置

    先说明一下,本水文老周仅讨论新版的 Qt 6,旧版的 Qt 不讨论. 尽管 Qt 有自己的开发环境,但老周必须说句不装逼的话:真的不好用.说起写代码,当然了,用记事本也能写.但是,有个高逼格的工具,写 ...

  3. 用VS Code搞Qt6:编译附加模块

    上一次水文中,老周所介绍的是编译 Qt 的基础模块-- qtbase.一次性编译所有代码可以一劳永逸,但体积相当大,编译时间较长,CPU负载大发热大,风扇转得猛,电费交得多.因此老周更喜欢分开来编译. ...

  4. 用VS Code搞Qt6:至简窗口部件——QWidget

    在正题开始之前,老周照例扯点别的.嗯,咱们扯一下在 VS 2022 下结合 CMake 开发 Qt6 时的环境变量设置问题.在VS Code 中,通够通过 CMake Tools 扩展的配置来设置环境 ...

  5. 用 VS Code 搞 Qt6:信号、槽,以及QObject

    Qt 里面的信号(Signal)和槽(Slot)虽然看着像事件,但它实际上是用来在两个对象之间进行通信的.既然是通信,就会有发送者和接收者. 1.信号是发送者,触发时通过特有的关键字"emi ...

  6. 用 VS Code 搞 Qt6:让信号和槽自动建立连接

    Qt 具备让某个对象的信号与符合要求的槽函数自动建立连接.弄起来也很简单,只要调用这个静态方法即可: QMetaObject::connectSlotsByName(...); connectSlot ...

  7. Qt6.2 在Ubuntu20下提示 C++ 和 CMake 错误

    Qt6.2 在Ubuntu20下提示 CMake No CMake configuration found apt install libgl-dev 即可! 先是C++提示没有找到C++编译器,需要 ...

  8. 【原创】2022年linux环境下QT6不支持中文输入法解决方案

    1.配置环境 export PATH="~/目录/Qt/6.x.x/gcc_64/bin":$PATH export PATH="~/目录/Qt/Tools/Cmake/ ...

  9. vs2019 配置 qt6

    1.下载qt6 我的目录C:\Qt\6.3.1\msvc2019_64\bin C:\Qt\6.3.1\msvc2019_64\include C:\Qt\6.3.1\msvc2019_64\lib ...

  10. 【Qt6】QWindow类可以做什么

    原来的水文标题是"用 VS Code 搞 Qt6",想想还是直接改为"Qt6",反正这个用不用 VS Code 也能搞.虽然我知道大伙伴们都很讨厌 CMake, ...

随机推荐

  1. ssh终端工具推荐-WindTerm

    什么是WindTerm 官方github https://github.com/kingToolbox/WindTerm A Quicker and better SSH/Telnet/Serial/ ...

  2. 2022-09-05:作为国王的统治者,你有一支巫师军队听你指挥。 :给你一个下标从 0 开始的整数数组 strength , 其中 strength[i] 表示第 i 位巫师的力量值。 对于连续的一

    2022-09-05:作为国王的统治者,你有一支巫师军队听你指挥. :给你一个下标从 0 开始的整数数组 strength , 其中 strength[i] 表示第 i 位巫师的力量值. 对于连续的一 ...

  3. 2020-10-19:golang里defer为什么要逆序执行?顺序执行不是更符合人的习惯吗?

    福哥答案2020-10-19: 后面定义的函数可能会依赖前面的资源,所以要先执行.如果前面先执行,释放掉这个依赖,那后面的函数就不能找到它的依赖了.***[评论](https://user.qzone ...

  4. 2021-10-02:单词搜索。给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。单词必须按照字母

    2021-10-02:单词搜索.给定一个 m x n 二维字符网格 board 和一个字符串单词 word .如果 word 存在于网格中,返回 true :否则,返回 false .单词必须按照字母 ...

  5. vue全家桶进阶之路25:Vue2的停维通知

    Vue 2 的技术支持会持续多久?从官方发文来看,Vue 2.7 是当前.同时也是最后一个 Vue 2.x 的次级版本更新.Vue 2.7 会以其发布日期,即 2022 年 7 月 1 日开始计算,提 ...

  6. Linux 目录 查看 压缩 编辑 命令

    目录 一.文件目录 二.查看文件 三.压缩与解压 四.vim编辑 一.文件目录结构 文件类型: /root 管理员的家目录 /home 用户家目录 /bin   命令文件目录,存放命令,管理员和用户可 ...

  7. HTML转为PDF,图片导出失败的终极解决方案

    如题项目有需求将一个页面导出为pdf,然而页面中的图片却始终无法导出成功 文章目录 一.导出的方法 二.初步测试的结果 三.使用f12查找原油 四.方案一 五.方案二 六.方案三 七.完整代码 1.使 ...

  8. qq飞车端游最全按键指法教学

    目录 起步篇 超级起步 弹射起步 段位起步 基础篇 点飘 撞墙漂移 撞墙点喷 进阶篇 双喷 叠喷 断位漂移 段位双喷 侧身漂移 快速出弯 CW WCW CWW 牵引 甩尾点飘 甩尾漂移 右侧卡 左侧卡 ...

  9. 《数据结构》之栈和堆结构及JVM简析

    导言: 在数据结构中,我们第一了解到了栈或堆栈,它的结构特点是什么呢?先进后出,它的特点有什么用呢?我们在哪里可以使用到栈结构,栈结构那么简单,使用这么久了为什么不用其它结构替代? 一.程序在内存中的 ...

  10. Linux 线程传递参数

    1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 #include <u ...