【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.2 完成整体布局

2023-09-18 08:58:35

本章要实现的整体效果如下:

demo

在讲解实际的事件之前,本节先把整体布局搭建好。布局整体包括左侧的导航和右侧的主窗体

1. 新建工程

新建一个窗口类 MainWidget,继承自 QWidget,并且取消 “Generate form” 复选框

image-20230910083432017

也就是不使用 UI设计师界面拖拽控件,而是纯代码来实现界面。最终新建工程如下:

image-20230910083901573

此时,直接运行是一个空白窗体,如下:

运行-空白窗体


2. 整体布局规划

首先,来到 mainwidget.h 添加两个成员变量 navWidgetmainWidget,如下:

#include <QStackedWidget>

class MainWidget : public QWidget
{
private:
    QWidget* navWidget;
    
    QWidget* mainWidget;
    QStackedWidget* stackedWidget;
};

然后,来到 MainWidget 构造,添加左侧导航、右侧主体的整体框架

#include <QHBoxLayout>
#include <QVBoxLayout>

MainWidget::MainWidget(QWidget* parent) : QWidget(parent)
{
    // 1. 整体采用水平布局
    QHBoxLayout* horizontalLayout = new QHBoxLayout(this);
    horizontalLayout->setSpacing(0);
    horizontalLayout->setContentsMargins(0, 0, 0, 0);

    // 2. 导航窗体
    navWidget = new QWidget(this);
    QVBoxLayout* leftLayout = new QVBoxLayout(navWidget);
    leftLayout->setSpacing(0);
    leftLayout->setContentsMargins(0, 0, 0, 0);

    horizontalLayout->addWidget(navWidget);

    // 3. 主窗体
    mainWidget = new QWidget(this);
    mainWidget->setMinimumWidth(400);
    QVBoxLayout* rightLayout = new QVBoxLayout(mainWidget);
    rightLayout->setSpacing(0);
    rightLayout->setContentsMargins(5, 5, 5, 5);
    stackedWidget = new QStackedWidget(mainWidget);

    rightLayout->addWidget(stackedWidget);

    horizontalLayout->addWidget(mainWidget);
}

左侧导航采用垂直布局,添加多个 QPushButton;右侧主体采用 QStackedWidget,可以同时填充多个子页面,方便在多个子页面之间切换。

最后,运行效果如下:

运行-空白窗体


3. 实现左侧导航

首先,来到 mainwidget.h 添加 一个初始化左侧导航的成员函数,如下:

// 为了把多个 QPushButton 统一管理并实现互斥,放到一个 QButtonGroup 中
#include <QButtonGroup> 

class Widget : public QWidget
{
private:
    void initNav();
    
private:
    QButtonGroup* btnGroup;
};

然后,来到 mainwidget.cpp 实现 initNav() 成员函数,如下:

#include <QPushButton>

void MainWidget::initNav()
{
    // 按钮文字集合
    QStringList names;
    names << "鼠标进入/离开"
          << "鼠标按下/移动/释放"
          << "键盘事件"
          << "定时器事件"
          << "拖动事件"
          << "绘图事件"
          << "右键菜单"
          << "总结:事件的传递流程";

    btnGroup = new QButtonGroup(this);

    //自动生成按钮
    for ( int i = 0; i < names.count(); i++ ) {
        QPushButton* btn = new QPushButton;

        //设置按钮固定高度
        btn->setMinimumHeight(60);

        //设置按钮的文字
        btn->setText(QString("%1. %2").arg(i + 1, 2, 10, QChar('0')).arg(names.at(i)));

        //设置按钮可选中按下类似复选框的功能
        btn->setCheckable(true);

        // 设置按钮的样式
        navWidget->setStyleSheet(R"(
            QPushButton {
                    font: 25px;
                    text-align : left;
            }
        )");

        // 将按钮添加到 btnGroup
        btnGroup->addButton(btn, i);

        //将按钮加入到布局
        navWidget->layout()->addWidget(btn);
    }
}

最后,在 mainwidget.cpp 构造中调用 initNav() 即可

MainWidget::MainWidget(QWidget* parent) : QWidget(parent)
{
    // ...
    
    initNav();
}

最终效果,如下:

添加了左侧导航按钮


4. 实现右侧主体

右侧主体采用 QStackedWidget,可以同时填充多个子页面,方便在多个子页面之间切换。因此,要创建 8 个子窗口。

首先,添加第一个窗口:鼠标进入/离开窗口,对应的文件为 enter_leave_widget.cpp/.h

在左侧项目文件名上右键,然后选择 “添加新文件”,选择 “C++ Class”,如下:

添加类

新建类文件信息如下:

类信息一览

此时就可以看到新建的两个文件,如下:

添加完成

为了便于区分,在该页面添加一个标签,并修改 text 为 “鼠标进入/离开”,如下:

#include <QLabel>
#include <QVBoxLayout>

EnterLeaveWidget::EnterLeaveWidget(QWidget* parent) : QWidget{parent}
{
    QVBoxLayout* verticalLayout = new QVBoxLayout(this);
    verticalLayout->setSpacing(0);
    verticalLayout->setContentsMargins(0, 0, 0, 0);

    QLabel* lbl = new QLabel(this);
    lbl->setText("鼠标进入/离开");
    lbl->setFrameShape(QFrame::Box);
    lbl->setFixedHeight(50);
    lbl->setAlignment(Qt::AlignCenter);
    lbl->setStyleSheet("background-color: blue;color: white;font-size: 25px");
    verticalLayout->addWidget(lbl);
}

然后,根据上述方法,创建剩余的 7 个子窗口,最终效果如下:

项目目录


5. 左右联动

5.1 添加 8 个窗体

实现点击左侧导航栏的按钮,切换右侧的子界面

首先,在 mainwidget.h 中,添加一个 initMain() 的成员函数:

class MainWidget : public QWidget
{
private:
    void initMain();
};

mainwidget.cpp 中,实现 initmain() 函数:

#include "enter_leave_widget.h"
#include "press_move_release_widget.h"
#include "key_widget.h"
#include "timer_widget.h"
#include "drag_widget.h"
#include "paint_widget.h"
#include "context_widget.h"
#include "propagate_widget.h"

void MainWidget::initMain()
{
    // 逐个添加子窗体
    stackedWidget->addWidget(new EnterLeaveWidget());
    stackedWidget->addWidget(new PressMoveReleaseWidget());
    stackedWidget->addWidget(new KeyWidget());
    stackedWidget->addWidget(new TimerWidget());
    stackedWidget->addWidget(new DragWidget());
    stackedWidget->addWidget(new PaintWidget());
    stackedWidget->addWidget(new ContextWidget());
    stackedWidget->addWidget(new PropagateWidget());
}

别忘了,在 mainwidget.cpp 构造中调用一下 initMain(),如下:

MainWidget::MainWidget(QWidget* parent) : QWidget(parent)
{
    initNav();
    initMain();
}

5.2 关联槽函数

首先,在 mainwidget.h 中,声明左侧导航按钮的槽函数:

class MainWidget : public QWidget
{
private slots:
    void buttonClicked();  //导航按钮单击事件
};

并在 mainwidget.cpp 中 实现该槽函数,如下:

void MainWidget::buttonClicked()
{
    // 识别按下了哪个按钮
    int index = btnGroup->checkedId();
    stackedWidget->setCurrentIndex(index);
}

然后,需要在 initNav() 函数中,关联一下信号槽,如下:

void MainWidget::initNav()
{
    for ( int i = 0; i < names.count(); i++ ) {
        // 关联信号槽
        connect(btn, SIGNAL(clicked(bool)), this, SLOT(buttonClicked()));
    }
}

最后,为了默认选中第一项,在 initNav() 最后添加如下一行:

void MainWidget::initNav()
{
 	// ...
    
    // 默认选中第一项
    btnGroup->button(0)->click();
}

此时,运行,就可以左右联动了,如下:

最终效果

更多推荐

Java版分布式微服务云开发架构 Spring Cloud+Spring Boot+Mybatis 电子招标采购系统功能清单

项目说明随着公司的快速发展,企业人员和经营规模不断壮大,公司对内部招采管理的提升提出了更高的要求。在企业里建立一个公平、公开、公正的采购环境,最大限度控制采购成本至关重要。符合国家电子招投标法律法规及相关规范,以及审计监督要求;通过电子化平台提高招投标工作的公开性和透明性;通过电子化招投标,使得招标采购的质量更高、速度

Spring MVC 八 - 内置过滤器

SpringMVC内置如下过滤器:FormDataForwardedHeadersShallowETagCORSFormData浏览器可以通过HTTPGET或HTTPPOST提交formdata(表单数据),但是非浏览器客户端可以通过HTTPPUT、HTTPDELETE、HTTPPATCH提交表单数据。但是Servle

如何使用 Node.js和Express搭建服务器?

如何使用NodeJs搭建服务器1.准备工作1.1安装Node.js2.安装express2.1初始化package.json2.2安装express2.3Express应用程序生成器1.准备工作1.1安装Node.jsNode.js是一个开源、跨平台的JavaScript运行时环境。下载链接:Node.js官网下载建议

【数据分享】我国主要城市的商圈范围数据(免费获取\shp格式)

大家对于商圈这个概念肯定都不陌生!商圈,简单来说,就是商业聚集的区域。我国比较知名的商圈有比如北京的三里屯商圈、上海的南京东路商圈。商圈是城市商业发展的重要体现,一个城市的商圈越多、商圈范围越大、商圈能级越高,代表这个城市的商业越发达!商圈的概念也越来越深入人心,比如在大众点评上可以以商圈(商区)为规则进行店铺搜索,比

【Redis】Redis实现分布式锁

【Redis】Redis常见面试题(1)文章目录【Redis】Redis常见面试题(1)1.为什么要用分布式锁2.Redis如何实现分布式锁3.Redis接受多个请求模拟演示4.使用Redis实现分布式锁会存在什么问题4.1一个锁被长时间占用4.2锁误删【Redis】Redis常见面试题(1)1.为什么要用分布式锁之前

Pandas 数据变形和模型分析

数据概念数据比对在本练习中,我们使用灵活的比较技术对不同的DataFrame进行比较importpandasaspdimportrandomrandom.seed(123)list1=[['A']*3,['B']*5,['C']*7]charlist=[xforsublistinlist1forxinsublist]r

(vue2).sync修饰符、ref和$refs、$nextTick、自定义指令、插槽

.sync修饰符实现子组件和父组件数据的双向绑定,简化代码prop属性名,可以自定义,非固定value本质:属性名和@update:属性名的合写<BaseDialog:value="isShow"@update="isShow=$event">//等价于<BaseDialogv-model="isShow=$event

node.js

前端工程化:开发项目直到上线,过程中集成的所有工具和技术Node.js是独立执行JavaScript代码的环境Node.js环境比浏览器环境中的JS少了BOM和DOMfs模块-读写文件模块:类型插件,封装了方法/属性fs模块:封装了与本机文件系统进行交互的方法/属性语法:加载fs模块对象写入文件内容读取文件内容Comm

自定义指令

一,原生指令v-bind:属性名="变量名"v-on:事件名="函数名"缩写模式::属性名="变量名"@事件名="函数名"示例:<inputtype="text"v-bind:disabled="isDisabled"v-on:change="change"/><inputtype="text":disabled="i

「工具|数据接口」免费公开的REST API & 如何借助github搭建自己的fake API接口

本文主要介绍日常开发、测试、教学或者分享中,可能遇到的模拟数据问题。分享免费开发的测试数据接口,以及如何利用github快速搭建定制化的接口数据,避免使用真实数据的风险以及自己现编数据的麻烦。文章目录一、场景说明二、免费公开的FakeRESTAPI:jsonplaceholder三、借助GitHub和MyJSONSer

手机全自动无人直播系统,成为商家实景无人直播带货好帮手!

商家手机无人直播系统最近太火爆了,那么,这个产品究竟是什么呢?全自动无人直播系统是一款手机自动直播软件,目地在于帮助广大商家和企业实现无人直播卖货,从而解放双手、降低人工干预的需求。当然,无人直播系统除了个人可以使用,拿来直播卖货等,也适合创业者,创业模式一是可以oem贴牌,二是直接进行渠道代理。通过OEM贴牌,用户可

热文推荐