【Qt6】QWidgetAction 的使用
在开始主题前,先看一个 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 的使用的更多相关文章
- QT6 源码杂记
菜鸡一个,随便写写,勿喷.好记性不如烂笔头. 了解qt,第一个绕不过的坎便是qt的元对象系统 QMetaObject. 1 class Object : public QObject 2 { 3 Q_ ...
- 用VS Code搞Qt6:编译源代码与基本配置
先说明一下,本水文老周仅讨论新版的 Qt 6,旧版的 Qt 不讨论. 尽管 Qt 有自己的开发环境,但老周必须说句不装逼的话:真的不好用.说起写代码,当然了,用记事本也能写.但是,有个高逼格的工具,写 ...
- 用VS Code搞Qt6:编译附加模块
上一次水文中,老周所介绍的是编译 Qt 的基础模块-- qtbase.一次性编译所有代码可以一劳永逸,但体积相当大,编译时间较长,CPU负载大发热大,风扇转得猛,电费交得多.因此老周更喜欢分开来编译. ...
- 用VS Code搞Qt6:至简窗口部件——QWidget
在正题开始之前,老周照例扯点别的.嗯,咱们扯一下在 VS 2022 下结合 CMake 开发 Qt6 时的环境变量设置问题.在VS Code 中,通够通过 CMake Tools 扩展的配置来设置环境 ...
- 用 VS Code 搞 Qt6:信号、槽,以及QObject
Qt 里面的信号(Signal)和槽(Slot)虽然看着像事件,但它实际上是用来在两个对象之间进行通信的.既然是通信,就会有发送者和接收者. 1.信号是发送者,触发时通过特有的关键字"emi ...
- 用 VS Code 搞 Qt6:让信号和槽自动建立连接
Qt 具备让某个对象的信号与符合要求的槽函数自动建立连接.弄起来也很简单,只要调用这个静态方法即可: QMetaObject::connectSlotsByName(...); connectSlot ...
- Qt6.2 在Ubuntu20下提示 C++ 和 CMake 错误
Qt6.2 在Ubuntu20下提示 CMake No CMake configuration found apt install libgl-dev 即可! 先是C++提示没有找到C++编译器,需要 ...
- 【原创】2022年linux环境下QT6不支持中文输入法解决方案
1.配置环境 export PATH="~/目录/Qt/6.x.x/gcc_64/bin":$PATH export PATH="~/目录/Qt/Tools/Cmake/ ...
- 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 ...
- 【Qt6】QWindow类可以做什么
原来的水文标题是"用 VS Code 搞 Qt6",想想还是直接改为"Qt6",反正这个用不用 VS Code 也能搞.虽然我知道大伙伴们都很讨厌 CMake, ...
随机推荐
- 第一个c语言项目
怎么写代码呢 工具:编译器 市面上编译器主要有:clang,gcc,win-tc,msvc,turbo c等 怎么写呢 1.创建一个项目(项目名字不能以中文文字命名) 2.创建一个文件(项目名字不能以 ...
- 2022-05-12:小歪每次会给你两个字符串: 笔记s1和关键词s2,请你写一个函数, 判断s2的排列之一是否是s1的子串。 如果是,返回true; 否则,返回false。 来自字节飞书团队。
2022-05-12:小歪每次会给你两个字符串: 笔记s1和关键词s2,请你写一个函数, 判断s2的排列之一是否是s1的子串. 如果是,返回true: 否则,返回false. 来自字节飞书团队. 答案 ...
- mac系统下,docker安装kibana报错,manifest for kibana:latest not found: manifest unknown: manifest unknown
1.问题描述:mac系统下,docker安装kibana报错,manifest for kibana:latest not found: manifest unknown: manifest unkn ...
- 2021-09-20:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O
2021-09-20:给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度.不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O ...
- drf——5个视图扩展类、9个视图子类、视图集、drf之路由
5个视图扩展类 # 写5个类(不叫视图类 视图拓展类 需要配合GenericAPIView一起用) 每个类有一个方法 以后想写哪个接口 就继承哪个类即可 from rest_framework.res ...
- 多线程合集(三)---异步的那些事之自定义AsyncTaskMethodBuilder
引言 之前在上一篇文章中多线程合集(二)---异步的那些事,async和await原理抛析,我们从源码去分析了async和await如何运行,以及将编译后的IL代码写成了c#代码,以及实现自定义的Aw ...
- cv学习总结(SVM,softmax)10.24-10.30
本周完成了SVM课程笔记的阅读,包括SVM的基本原理以及SVM的优化过程,以及实现了SVM的两种损失函数(svm以及softmax)的线性分类器,以及学习了反向传播以及神经网络的初步.其中:svm在测 ...
- .Net NPOI Excel 导出
NPOI 导出 Excel 最终效果图 环境:Revit,WPF,NPOI 2.5.6,.Net Framework 4.7.2 一.引用 NPOI 右击项目引用,选择 "管理NuGet程序 ...
- java(方法定义、调用、重载)
1.方法 Java方法是语句的集合,它们在一起执行一个功能 方法是解决一类问题的步骤的有序组合 方法包含于类或对象中 方法在程序中被创建,在其他地方被引用 设计方法的原则:就是一个方法只完成一个功能, ...
- CompTIA Pentest+
关于学习后CompTIA Pentest+笔记 渗透测试工具 讲述了nmap,burp Suite,Metasploit,Nessus,hydra的入门使用 nmap:https://www.cnbl ...