Qt C++ GUI编程进阶:多窗口交互与事件机制深度解析

文章目录

  • 前言
  • 一、QWidget
  • [二、QMessageBox 消息对话框](#二、QMessageBox 消息对话框)
    • [2.1 基本特性](#2.1 基本特性)
    • 2.2核心功能
    • [2.3 代码示例](#2.3 代码示例)
  • [三、QMainWindow 主窗口类](#三、QMainWindow 主窗口类)
    • [3.1 QMenuBar 菜单栏](#3.1 QMenuBar 菜单栏)
    • [3.2QToolBar 工具栏](#3.2QToolBar 工具栏)
    • [3.3QStatusBar 状态栏](#3.3QStatusBar 状态栏)
  • [四、Qt 对象树](#四、Qt 对象树)
  • 五、传参
  • [5.1 创建自定义窗口类 (Creating a Custom Window Class)](#5.1 创建自定义窗口类 (Creating a Custom Window Class))
    • [5.2 父对象→子对象:通过成员函数调用传参](#5.2 父对象→子对象:通过成员函数调用传参)
    • [5.3 子对象→父对象:通过信号槽传参](#5.3 子对象→父对象:通过信号槽传参)
  • 六、事件机制
  • 总结

前言

很高兴你能看到这篇文章,在上一篇文章中,我们讨论了关于QTimer定时器的一些细节和内容。今天我们将会继续进行c++版本Qt的学习。在今天的内容里,我们将会探讨有关于多窗口编辑和文件i/o的相关知识,祝小伙伴们学习愉快!


一、QWidget

QWidget 是所有 Qt 窗口的基类

既可以作为独立窗口,也可以作为内嵌组件

通常不直接使用 QWidget 作为独立窗口

1.1构造函数

cpp 复制代码
QWidget::QWidget(QWidget *parent = nullptr)
不传参:创建独立窗口
传参:创建作为父窗口子组件的内嵌窗口

1.2窗口特性

窗口标记 (windowFlags)

可以通过设置窗口标记来改变窗口行为,常见标记包括:

Qt::Window - 普通窗口(默认)

Qt::Dialog - 对话框窗口

Qt::Tool - 工具窗口(通常没有任务栏条目)

Qt::FramelessWindowHint - 无边框窗口

Qt::WindowStaysOnTopHint - 窗口始终置顶

Qt::WindowMinimizeButtonHint - 显示最小化按钮

Qt::WindowMaximizeButtonHint - 显示最大化按钮

Qt::WindowCloseButtonHint - 显示关闭按钮

设置多个标记时使用 | 操作符:

cpp 复制代码
widget->setWindowFlags(Qt::Window | Qt::WindowStaysOnTopHint | Qt::FramelessWindowHint);

1.3窗口状态 (WindowState)

cpp 复制代码
void QWidget::setWindowState(Qt::WindowStates windowState)

常用状态:

Qt::WindowNoState - 正常状态

Qt::WindowMinimized - 最小化

Qt::WindowMaximized - 最大化

Qt::WindowFullScreen - 全屏


1.4 代码示例

代码包,运行解压


二、QMessageBox 消息对话框

2.1 基本特性

继承关系:QMessageBox 继承自 QDialog,属于模态对话框(modal: true)。

模态特性:必须优先处理,阻塞用户与其他窗口的交互,直到关闭。


2.2核心功能

提供四种静态函数快速创建标准弹窗,通过返回值获取用户操作:

函数类型 作用描述 函数签名
question 询问用户(带是/否等选项) StandardButton question(QWidget* parent, const QString& title, const QString& text)
information 提示信息(通常为确认按钮) StandardButton information(...) (参数同上)
warning 警告信息 StandardButton warning(...)
critical 严重错误信息 StandardButton critical(...)

参数说明

parent:父窗口指针(可省略,设为 nullptr 时独立显示)。

title:窗口标题(windowTitle 属性)。

text:弹窗主内容(支持富文本格式)。

返回值:用户点击的按钮类型(StandardButton 枚举,如 QMessageBox::Ok、QMessageBox::Yes)。


2.3 代码示例

代码包


三、QMainWindow 主窗口类

QMainWindow 是 Qt 中用于创建主窗口的核心类,它提供了应用程序主窗口所需的标准结构和功能。


3.1 QMenuBar 菜单栏

信号:


3.2QToolBar 工具栏

工具栏与菜单栏的关联

工具栏中的工具按钮通常是菜单栏中已有的 QAction 对象,通过共享 QAction 实现功能复用。

工具栏按钮需要为 QAction 设置图标(setIcon()),而菜单项可以仅显示文本。

QAction 的多重用途

同一个 QAction 可以同时出现在菜单栏、工具栏和快捷键触发中,保持操作一致性。

QAction的属性可以在以下两个位置配置:


3.3QStatusBar 状态栏

显示消息

cpp 复制代码
// 在状态栏显示消息
// 参数:
//   message - 要显示的信息内容
//   timeout - 显示时长(毫秒),默认0表示持久显示直到被清除
void QStatusBar::showMessage(const QString &message, int timeout = 0) [slot]

清除消息

cpp 复制代码
// 清除当前显示的状态栏消息
void QStatusBar::clearMessage() [slot]

使用示例

cpp 复制代码
// 显示持久性消息
statusBar()->showMessage("Ready");
// 显示临时消息(3秒后自动消失)
statusBar()->showMessage("File saved successfully", 3000);
// 清除当前消息
statusBar()->clearMessage();

代码示例
代码包


四、Qt 对象树

  1. 核心概念
    对象树(Object Tree):Qt 通过 parent 参数建立父子对象的层级关系,形成树状结构。
    内存管理:父对象销毁时,会自动递归销毁其所有子对象,避免手动释放内存的繁琐和潜在泄漏。
  2. parent 参数的作用
    构造函数中的 parent:
    在创建 QObject 派生类对象时,通过传递 parent 参数指定父对象。
cpp 复制代码
QPushButton *button = new QPushButton("Click", this);  // this 作为 parent
  • 自动销毁:
    当父对象被销毁时,Qt 会遍历其子对象列表并调用它们的析构函数,释放内存。
  1. 不传递 parent 的后果
    需手动管理内存:
    若对象未指定 parent,则必须显式调用 delete,否则会导致内存泄漏。
cpp 复制代码
QPushButton *button = new QPushButton("Click");  // 无 parent
// 必须手动释放
delete button;
  • 适用场景:
    非 Qt 对象(如纯 C++ 类)或需要独立生命周期的对象。
  1. 主窗口与对象树
    根节点:主窗口(如 QMainWindow)通常是对象树的根。关闭主窗口时,所有子对象(按钮、布局等)自动销毁。
    示例:
cpp 复制代码
QMainWindow *window = new QMainWindow();
QWidget *widget = new QWidget(window);  // widget 的 parent 是 window
// 关闭 window 时,widget 也会被销毁

五、传参

5.1 创建自定义窗口类 (Creating a Custom Window Class)

在Qt Creator中为一个项目添加自定义窗口类的操作步骤如下:
步骤 1: 启动"添加新文件"向导

在Qt Creator的"项目"视图中,选中您的项目名称(通常位于左侧的项目树中)。

右键点击项目名称。

在弹出的上下文菜单中,选择"添加新文件..."(或使用快捷键 Ctrl+N,或通过菜单 文件(File) -> 新建文件或项目(New File or Project...))。

步骤 2: 选择文件类型

在弹出的"新建文件或项目"窗口中,您会看到多种文件类型选项。

展开"Qt"分类。

推荐选择:Qt Designer Form Class

说明: 这是创建带有可视化界面的自定义窗口或控件的首选方式。它会自动生成一个 .ui 文件,您可以使用Qt Designer进行拖放式界面设计。

点击右下角的"选择(Choose)..."按钮。

步骤 3: 配置类信息

在弹出的配置窗口中,您需要定义新类的基本属性:

选择基类 (Base Class):

QWidget: 这是所有Qt UI元素的基类。如果您要创建一个可以嵌入到其他窗口中的自定义控件,或者一个简单的独立窗口,通常选择 QWidget。它是最通用的选择。

QMainWindow: 适用于创建带有菜单栏、工具栏、状态栏等复杂主窗口的应用。如果您的自定义类是应用程序的主界面,请选择此项。

QDialog: 适用于创建对话框,通常用于用户输入或显示特定信息。对话框可以设置为模态(阻塞父窗口操作)或非模态。

提示: 您也可以选择继承自您项目中已有的自定义类,以实现代码复用和层次化设计。

编写类名 (Class Name):

请遵循大驼峰命名法 (PascalCase),即每个单词的首字母大写,不含空格。

示例: MyCustomWidget、LoginDialog、SettingsWindow。

Qt Creator会根据您输入的类名,自动生成对应的头文件 (.h)、源文件 (.cpp) 和 UI 文件 (.ui) 的名称。

勾选选项 (Options):

通常保持默认即可。对于"Qt Designer Form Class",Generate form 选项通常是默认勾选的,这确保了 .ui 文件的生成。

点击"下一步(Next)"按钮。

步骤 4: 项目管理与完成

在项目管理界面,您会看到新文件将被添加到您的项目中。通常,您不需要在此处进行额外修改。

直接点击"完成(Finish)"按钮。


5.2 父对象→子对象:通过成员函数调用传参

核心思想:

这种方式是最直接的父子通信方法。父对象持有子对象的引用或指针,并在需要向子对象传递信息时,直接调用子对象公开(public)的成员函数,将数据作为参数传递进去。

工作原理:

子对象提供接口: 子对象类定义一个或多个 public 的成员函数,这些函数接收父对象需要传递的数据作为参数。

父对象持有引用: 父对象在创建子对象时,或者通过其他方式获取到子对象的指针或引用。

父对象直接调用: 当父对象中的某个事件发生(例如按钮点击、滑块值改变等),需要通知子对象时,父对象直接通过持有的子对象指针/引用调用子对象提供的公共成员函数,并将数据作为参数传递。

优点:

简单直观: 代码逻辑清晰,易于理解。

直接高效: 数据直接通过函数调用传递,没有额外的中间层。

缺点:

紧耦合: 父对象必须知道子对象的具体类型以及它提供的成员函数名称和签名。如果子对象的接口发生变化,父对象也需要修改。

单向性强: 主要用于父对象"命令"或"更新"子对象的状态。子对象通常不会通过这种方式直接"回复"父对象(虽然子对象也可以调用父对象的公共方法,但这不如信号/槽灵活)。

适用场景:

父对象需要直接控制或设置子对象的状态。

父对象与子对象之间是明确的"拥有"关系,且父对象需要对子对象进行特定的操作。

通信需求简单,不需要高度解耦。

与信号/槽的区别:

成员函数调用: 是直接的函数调用,调用者(父)必须知道被调用者(子)及其方法。是同步的。

信号/槽: 是一种事件机制,发射者(父)不需要知道接收者(子)是谁,只需要发射信号。接收者连接信号到自己的槽函数。是解耦的,可以是同步或异步的。

【例子】在主窗口中转动QDial,把value值同步到子窗口的滚动条上。

这个例子完美展示了父对象(主窗口)如何通过调用子对象(包含滚动条的自定义窗口)的公共方法来传递数据。
代码包


5.3 子对象→父对象:通过信号槽传参

核心思想:

子对象需要通知父对象某个事件发生或某个状态改变时,最 Qt 风格且解耦的方式是使用信号槽机制。子对象发射(emit)一个信号,父对象连接(connect)一个槽函数来接收这个信号并处理。

工作原理:

子对象定义信号: 子对象类定义一个或多个 signals,这些信号可以携带参数(即需要传递给父对象的数据)。

子对象发射信号: 当子对象内部发生某个事件(例如用户操作、状态改变等),需要通知外部(包括父对象)时,子对象发射相应的信号,并将数据作为信号的参数发出。

父对象定义槽: 父对象类定义一个或多个 public slots 或 private slots 成员函数,这些函数用于处理接收到的信号。槽函数的参数列表需要与连接的信号的参数列表兼容。

父对象连接信号与槽: 父对象在创建子对象后,或者在适当的时候,使用 connect 函数将子对象的特定信号连接到父对象的特定槽函数上。

优点:

解耦: 子对象发射信号时,不需要知道是哪个对象(父对象或其他对象)在接收它,也不需要知道接收对象如何处理这个信号。它只负责"广播"发生了什么。这使得子对象更具通用性。

灵活: 一个信号可以连接到多个槽,多个信号可以连接到同一个槽。

Qt 标准机制: 这是 Qt 中对象间通信的核心和推荐方式。

缺点:

稍微多一些代码: 需要定义信号、定义槽、建立连接,相比直接函数调用稍微繁琐一点点。

连接必须存在: 如果没有建立连接,信号发射后不会有任何效果。

适用场景:

子对象的状态或事件需要通知父对象或其他对象。

需要保持对象间的低耦合度。

处理用户交互、后台任务完成、数据更新等事件。

与父对象→子对象直接调用的区别:

方向: 信号槽主要用于子对象"报告"或"通知"父对象,而直接调用是父对象"命令"或"设置"子对象。

耦合度: 信号槽是解耦的,子对象不依赖于父对象的具体接口;直接调用是紧耦合的,父对象必须知道子对象的具体接口。

主动方: 信号槽由子对象主动发起(发射信号);直接调用由父对象主动发起。

【例子】在子窗口中滑动滚动条,把value值同步到父窗口的QDial上。

这个例子展示了子对象(包含滚动条的自定义窗口)如何通过发射信号来通知父对象(主窗口)其内部状态(滚动条值)的变化。
代码包


六、事件机制

本次学习的重点是在 窗口类中通过覆盖虚函数来处理事件。当特定事件发生并传递到窗口类对象时,相应的事件处理函数会自动被触发。

核心概念

事件函数 (Event Handlers):这些是 QWidget 类中定义的 virtual protected 成员函数。它们是虚函数,允许子类(如您的自定义窗口类)通过覆盖它们来定义自己的事件处理逻辑。

自动触发:当系统检测到某个事件(例如鼠标点击、窗口大小改变等)发生在您的 QWidget 对象上时,Qt 框架会自动调用相应的事件处理函数。

参数携带事件信息:每个事件函数都带有一个指向特定事件类型对象的指针作为参数(例如 QMouseEvent *event)。这个 event 对象包含了关于该事件的详细信息(例如鼠标点击的位置、按下的键码等)。

保护权限 (protected):这些函数是 protected 的,意味着它们只能在类的内部或其派生类中被访问和覆盖,不能直接从外部调用。

常见的事件函数

以下是 QWidget 类中一些常见的事件处理虚函数:

事件类型 函数签名 描述
绘制 void QWidget::paintEvent(QPaintEvent *event) 当窗口需要重绘时触发。
大小改变 void QWidget::resizeEvent(QResizeEvent *event) 当窗口大小改变时触发。
鼠标点击 void QWidget::mousePressEvent(QMouseEvent *event) 鼠标按钮按下时触发。
鼠标释放 void QWidget::mouseReleaseEvent(QMouseEvent *event) 鼠标按钮释放时触发。
双击鼠标 void QWidget::mouseDoubleClickEvent(QMouseEvent *event) 鼠标双击时触发。
鼠标移动 void QWidget::mouseMoveEvent(QMouseEvent *event) 鼠标在窗口内移动时触发。
位置改变 void QWidget::moveEvent(QMoveEvent *event) 当窗口位置改变时触发。
按键按下 void QWidget::keyPressEvent(QKeyEvent *event) 键盘按键按下时触发。
按键释放 void QWidget::keyReleaseEvent(QKeyEvent *event) 键盘按键释放时触发。
获取焦点 void QWidget::focusInEvent(QFocusEvent *event) 窗口或控件获得焦点时触发。
失去焦点 void QWidget::focusOutEvent(QFocusEvent *event) 窗口或控件失去焦点时触发。
关闭 void QWidget::closeEvent(QCloseEvent *event) 当窗口即将关闭时触发。
指针进入 void QWidget::enterEvent(QEvent *event) 鼠标指针进入窗口或控件区域时触发。
指针离开 void QWidget::leaveEvent(QEvent *event) 鼠标指针离开窗口或控件区域时触发。

本节相关函数 (QPainter)

在处理事件(特别是 paintEvent)时,经常会用到 QPainter 类来进行绘制操作。

cpp 复制代码
QPainter::QPainter(QPaintDevice *device)

描述:QPainter 类的构造函数。

参数:device - 表示要在其上进行绘制的对象。这个 device 必须是继承自 QPaintDevice 的类(例如 QWidget、QPixmap、QImage 等),因为只有 QPaintDevice 才能被绘制。

cpp 复制代码
void QPainter::drawPixmap(int x, int y, int width, int height, const QPixmap &pixmap)

描述:在绘制设备上绘制一个 QPixmap 对象。

参数:

x:绘制区域的左上角横坐标。

y:绘制区域的左上角纵坐标。

width:绘制区域的宽度。

height:绘制区域的高度。

pixmap:要绘制的 QPixmap 对象。

代码示例
代码包


总结

本文深入探讨了Qt C++ GUI编程中的多窗口管理、消息对话框、主窗口结构以及核心的事件机制和对象树概念。首先介绍了QWidget作为所有窗口组件基类的特性,包括构造函数、窗口标记和状态设置。接着详细讲解了QMessageBox消息对话框的模态特性和四种常用静态函数。随后,文章重点阐述了QMainWindow作为主窗口的核心作用,并分别介绍了其组成部分:QMenuBar(菜单栏)、QToolBar(工具栏)和QStatusBar(状态栏),强调了QAction在菜单和工具栏间的功能复用。

在内存管理方面,文章详细解释了Qt对象树的概念,强调了parent参数在自动内存管理中的重要性,并对比了有无parent参数的后果。最后,文章着重讲解了多窗口间的数据传递机制,通过父对象调用子对象成员函数和子对象通过信号槽通知父对象两种方式,结合具体代码示例,清晰地展示了不同场景下的通信策略。特别地,事件机制部分深入讲解了如何通过覆盖QWidget的虚函数来处理各种用户交互和系统事件,并介绍了QPainter在绘图事件中的应用。

相关推荐
珊瑚里的鱼2 分钟前
第十讲 | 继承
开发语言·c++·笔记·visualstudio·学习方法·visual studio
有点。13 分钟前
C++031(变量的存储类型-auto变量)
c++
mmz120738 分钟前
单调栈(c++)
c语言·c++
菠萝011 小时前
分布式不同数据的一致性模型
数据库·c++·分布式·后端
Code哈哈笑1 小时前
【基于SpringBoot的图书购买系统】操作Jedis对图书图书的增-删-改:从设计到实战的全栈开发指南
java·spring boot·后端·spring·交互·jedis
笨鸟起飞1 小时前
CODEFORCES----1999A - A+B Again?
数据结构·c++·算法
1白天的黑夜12 小时前
动态规划-931.下降路径最小和-力扣(LeetCode)
c++·算法·leetcode·动态规划
h汉堡2 小时前
Codeforces Round 1027 (Div. 3)
数据结构·c++·算法
小葡萄20252 小时前
黑马程序员C++核心编程笔记--4 类和对象--封装
java·c++·笔记
一条叫做nemo的鱼3 小时前
从汇编的角度揭秘C++函数重载,原来这么简单
汇编·c++·函数重载·原理解密