Qt实现TabWidget通过addTab函数添加的页,页内控件自适应窗口大小

**前言:**因为项目的要求,需要把几个不同类型功能的界面集成在同一个窗口中,方便用户不切换窗口,也能快捷的操作不同类型的功能。我首先想到的是通过选项卡方式,让几个类别的功能界面通过不同选项卡进行切换,这在windows系统中也是很常见的一种方式。为了将不同类功能进行模块化,方便维护,通过自定义QWidget的子类,然后在主界面通过tabWidget.addTab()方法,将不同类功能添加到主界面。由于不同tab页内控件大小不一,为了让界面尽可能的美观,希望新增的tab页也能跟随窗口的大小随时改变尺寸。但是我使用通过遍历tab页内的控件,并在窗口改变事件(resizeEvent)中添加控件缩放操作,发现控件并没有达到我想要的缩放效果。经过不断尝试,发现问题出现在:新增的tab页中的布局控件无法轻易地获取真实大小,而且也无法触发tab页resizeEvent事件。知道了问题所在,经过不懈努力,终于找到了解决方法,于是有了本文。

一、主界面类

主界面中包含一个TabWidget控件,并包含一个tab页。TabWidget通过addTab()方法将其他类型的功能模块加进来,既实现模块化,又能将多个功能集成到一个窗口。

以下为主界面内的主要代码

mainwindow.h

cpp 复制代码
#include <QPlainTextEdit>
#include <QTextBrowser>
#include <QResizeEvent>
#include "tab2widght.h"
class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private:
    /** 初始化添加的TabWidget的尺寸 */
    void initialAddedTabWidgetSize(int tabIndex);
    // 需要添加的Tab2Widget模块
    Tab2Widget *tab2Widget = NULL;
	// 上次点击的tab页序号
	int preTabWidgetIndex = 0;
    // 控件根据窗口大小自动调整控件大小 @{
protected:
    void resizeEvent(QResizeEvent* event) override;
private:
    QMap<QWidget*, QRect> allWidgetMap;
    QMap<QLayout*, QRect> allLayoutMap;
    /** 窗口默认尺寸 */
    QSize windowOriginalSize;
    /** 查找所有子布局和子控件 */
    void findAllLayoutAndWidget(QObject *object);
    /** 检查文本是否超出了容器宽或高,方便修改文本大小 */
    bool checkTextOverflow(QWidget *widget);
// 控件根据窗口大小自动调整控件大小 @}
private slots:
    /** 当tabwidget切换tab时 **/
    void onTabClicked(int index);
};

mainwindow.cpp

cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
	// 实例化tab2模块类
	if(tab2Widget == NULL) {
        tab2Widget = new Tab2Widget();
    }
	// tab2模块添加到主界面的tabwidget中
    if (tab2Widget != NULL) {
        ui->tabWidget->addTab(tab2Widget, "Tab2");
    }
	// 注册tabwidget的页点击事件
	connect(ui->tabWidget, &QTabWidget::tabBarClicked, this, &MainWindow::onTabClicked);
	// 延时为了获取的控件初始大小是正确的
    QTimer::singleShot(200, this, [=] {
        findAllLayoutAndWidget(this);
        windowOriginalSize = this->size();
    });
    // 控件根据窗口大小自动调整控件大小 @}
}

MainWindow::~MainWindow()
{
	if (tab2Widget != NULL) {
		tab2Widget->deleteLater();
	}
}

/** 当tabview切换tab时 **/
void MainWindow::onTabClicked(int index) {
    // 根据tab的索引判断是否需要授权
    if (index == ui->tabWidget->indexOf(tab2Widget)) {
        // 首次设置新添加的tab的尺寸,只有切换到对应tab,才能拿到真实的layout尺寸数据,关键代码。
        initialAddedTabWidgetSize(index);
    }
	// 切换tab
    ui->tabWidget->setCurrentIndex(index);
	
    preTabWidgetIndex = index;
}

void MainWindow::initialAddedTabWidgetSize(int tabIndex)
{
    // 刷新切换后的tab的布局大小
    if (tabIndex < ui->tabWidget->count() && preTabWidgetIndex != tabIndex && ui->tabWidget->count() > 1) {
        // 调整通用参数选项卡页的尺寸
        if (tabIndex == ui->tabWidget->indexOf(tab2Widget) && tab2Widget != NULL) {
            if (tab2Widget->allWidgetMap.size() == 0 && tab2Widget->allLayoutMap.size() == 0) {
                tab2Widget->findAllLayoutAndWidget(tab2Widget);
            }
            // 设置通过addTab()函数新增的选项卡页内容的大小
            tab2Widget->setMinimumSize(ui->tabWidget->widget(0)->geometry().width(),
                                                     ui->tabWidget->widget(0)->geometry().height());
            // tabwidget需要单独调用内部控件的大小
            if (tab2Widget != NULL) {
                tab2Widget->resizeAllComponents(ui->tabWidget->widget(0)->geometry());
            }
        }
    }
}

// 控件根据窗口大小自动调整控件大小 @{
void MainWindow::resizeEvent(QResizeEvent* event)
{
    if (event == NULL) {
        return;
    }

    double scaleX = (double)event->size().width() / windowOriginalSize.width();
    double scaleY = (double)event->size().height() / windowOriginalSize.height();
	
    for (auto iter = allLayoutMap.begin(); iter != allLayoutMap.end(); ++iter) {
        QLayout* layout = iter.key();
        QRect originalGeometry = iter.value();
        QRect newGeometry(
            originalGeometry.x() * scaleX,
            originalGeometry.y() * scaleY,
            originalGeometry.width() * scaleX,
            originalGeometry.height() * scaleY
            );
        if (layout) {
            layout->setGeometry(newGeometry);
        }
    }

    for (auto iter = allWidgetMap.begin(); iter != allWidgetMap.end(); ++iter) {
        QWidget* widget = iter.key();
        if (widget) {
            QRect originalGeometry = iter.value();
            QRect newGeometry(
                originalGeometry.x() * scaleX,
                originalGeometry.y() * scaleY,
                originalGeometry.width() * scaleX,
                originalGeometry.height() * scaleY
                );
            widget->setGeometry(newGeometry);

            // 防止在多屏(选择扩展方式),移动到扩展屏时,tab页文字叠在一起的问题
            QTabWidget* tab = dynamic_cast<QTabWidget*>(widget);
            if (tab != NULL) {
                tab->setElideMode(Qt::ElideNone);
            }
        }

        // 调整带文本的控件的文本大小
        QPushButton* btn = dynamic_cast<QPushButton*>(widget);
        QLabel* label = dynamic_cast<QLabel*>(widget);
        QCheckBox* checkbox = dynamic_cast<QCheckBox*>(widget);
        QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget);
        QTextEdit* textEdit = dynamic_cast<QTextEdit*>(widget);
        QPlainTextEdit* plainTextEdit = dynamic_cast<QPlainTextEdit*>(widget);
        QTextBrowser* textBrowser = dynamic_cast<QTextBrowser*>(widget);
        // 条件为了防止在随意调节界面大小时,界面上出现黑块问题
        if (textBrowser == NULL && (btn != NULL || label != NULL || checkbox != NULL
            || lineEdit != NULL || textEdit != NULL || plainTextEdit != NULL)) {
            int initialFontSize = widget->height() / 3;
            if (btn != NULL) {
                initialFontSize = widget->height() / 4;
            } else if (checkbox != NULL) {
                initialFontSize = widget->height() / 2;
            }
            // 使用Do...While语句是为了始终会设置字体大小一次,防止界面缩小后又放大后,字体还一直保持很小的问题
            do {
                QFont font = widget->font();
                int size = initialFontSize;
                bool needBreak = false;
                // 最小9
                if (size < 9) {
                    size = 9;
                    needBreak = true;
                }
                // 根据按钮的宽度/高度调整字体大小,可以根据默认控件的高度和字体的大小比率进行适当调整
                font.setPointSize(size);
                widget->setFont(font);
                initialFontSize--;
                if (needBreak) {
                    break;
                }
            } while(checkTextOverflow(widget));
        }
    }

    if (windowOriginalSize.width() != -1) {
        // 设置通过addTab()函数新增的选项卡页内容的大小
        if (ui->tabWidget->count() > 1) {
			// 布局或控件拿到之后才能调整
            if (tab2Widget->allWidgetMap.size() > 0
                || tab2Widget->allLayoutMap.size() > 0) {
				// tab2
				tab2Widget->setMinimumSize(ui->tabWidget->widget(0)->geometry().width(),
														 ui->tabWidget->widget(0)->geometry().height());
				// tabwidget需要单独调用内部控件的大小
				if (tab2Widget != NULL) {
					tab2Widget->resizeAllComponents(ui->tabWidget->widget(0)->geometry());
				}
			}
        }
    }
}

void MainWindow::findAllLayoutAndWidget(QObject *object) {
    QLayout *layout = qobject_cast<QLayout*>(object);
    QWidget *widget = qobject_cast<QWidget*>(object);
    if (layout) {
        if (layout->objectName() != "" && !allLayoutMap.contains(layout)) {
            allLayoutMap.insert(layout, layout->geometry());
        }
        for (int i = 0; i < layout->count(); ++i) {
            findAllLayoutAndWidget(layout->itemAt(i)->widget());
        }
        // 嵌套的选项卡,自适应尺寸放到对应类中,不在此类中调整
    } else if (widget && widget != tab2Widget) {
        if (widget != this && widget->objectName() != "" && !allWidgetMap.contains(widget)) {
            allWidgetMap.insert(widget, widget->geometry());
        }
        for (int i = 0; i < widget->children().size(); ++i) {
            findAllLayoutAndWidget(widget->children().at(i));
        }
    }
}

bool MainWindow::checkTextOverflow(QWidget *widget) {
    if (widget == NULL) {
        return false;
    }
    QPushButton* btn = dynamic_cast<QPushButton*>(widget);
    QLabel* label = dynamic_cast<QLabel*>(widget);
    QCheckBox* checkbox = dynamic_cast<QCheckBox*>(widget);
    QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget);
    QTextEdit* textEdit = dynamic_cast<QTextEdit*>(widget);
    QPlainTextEdit* plainTextEdit = dynamic_cast<QPlainTextEdit*>(widget);
    // TODO 可增加其他可调节文本的控件,以下的text文本也要相应赋值控件文本

    // 获取按钮的字体和文本
    QString text = "";
    if (btn != NULL) {
        text = btn->text();
    } else if (label != NULL) {
        text = label->text();
    } else if (checkbox != NULL) {
        text = checkbox->text();
    } else if (lineEdit != NULL) {
        text = lineEdit->text();
    } else if (textEdit != NULL) {
        text = textEdit->toPlainText();
    } else if (plainTextEdit != NULL) {
        text = plainTextEdit->toPlainText();
    }

    if (text.isEmpty()) {
        return false;
    }

    QFont font = widget->font();
    QFontMetrics fm(font);

    // 计算文本的宽度和高度
    int textWidth = fm.horizontalAdvance(text);
    int textHeight = fm.height();

    // 获取按钮的尺寸(不包括边框)
    QSize buttonSize = widget->size();
    // 宽度多减去一些,为了防止有些时候按钮字体和左右边框切边
    buttonSize.rwidth() -= 8 * widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); // 减去边框宽度
    buttonSize.rheight() -= 2 * widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); // 减去边框高度

    // 检查是否溢出
    if (textWidth > buttonSize.width() || textHeight > buttonSize.height()) {
        return true;
    } else {
        return false;
    }
}

// 控件根据窗口大小自动调整控件大小 @}

二、通过addTab()方法加入到主界面的类

相当于另一个模块,只是界面嵌套到了主界面,既方便用户操作,又能够模块化,方便维护。

tab2widget.h

cpp 复制代码
class Tab2Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Tab2Widget(QWidget *parent = nullptr);
    ~Tab2Widget();
    // 控件根据窗口大小自动调整控件大小 @{
public:
    void resizeAllComponents(QRect parentRect);
    QMap<QWidget*, QRect> allWidgetMap;
    QMap<QLayout*, QRect> allLayoutMap;
    /** 查找所有子布局和子控件 **/
    void findAllLayoutAndWidget(QObject *object);
private:
	/** 窗口默认尺寸 **/
    QSize windowOriginalSize = QSize(0,0);
    /** 检查文本是否超出了容器宽或高,方便修改文本大小 **/
    bool checkTextOverflow(QWidget *widget);
    // 控件根据窗口大小自动调整控件大小 @}
};

tab2widget.cpp

cpp 复制代码
#include "tab2widget.h"
#include "ui_tab2widget.h"

Tab2Widget::Tab2Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Tab2Widget)
{
	// 控件根据窗口大小自动调整控件大小 @{
    // 延时为了获取的控件初始大小是正确的
    QTimer::singleShot(200, this, [=] {
        windowOriginalSize = this->size();
    });
    // 控件根据窗口大小自动调整控件大小 @}
}

Tab2Widget::~Tab2Widget() 
{
}

// 控件根据窗口大小自动调整控件大小 @{
void Tab2Widget::resizeAllComponents(QRect parentRect)
{
    if (parentRect.width() == 0 || parentRect.height() == 0
        || windowOriginalSize.width() == 0 || windowOriginalSize.height() == 0) {
        return;
    }
    double scaleX = (double)parentRect.width() / windowOriginalSize.width();
    double scaleY = (double)parentRect.height() / windowOriginalSize.height();
	
    for (auto iter = allLayoutMap.begin(); iter != allLayoutMap.end(); ++iter) {
        QLayout* layout = iter.key();
        QRect originalGeometry = iter.value();
        QRect newGeometry(
            originalGeometry.x() * scaleX,
            originalGeometry.y() * scaleY,
            originalGeometry.width() * scaleX,
            originalGeometry.height() * scaleY
            );
        if (layout) {
            layout->setGeometry(newGeometry);
        }
    }

    for (auto iter = allWidgetMap.begin(); iter != allWidgetMap.end(); ++iter) {
        QWidget* widget = iter.key();
        if (widget) {
            QRect originalGeometry = iter.value();
            QRect newGeometry(
                originalGeometry.x() * scaleX,
                originalGeometry.y() * scaleY,
                originalGeometry.width() * scaleX,
                originalGeometry.height() * scaleY
                );
            widget->setGeometry(newGeometry);
        }

        // 调整带文本的控件的文本大小
        QPushButton* btn = dynamic_cast<QPushButton*>(widget);
        QLabel* label = dynamic_cast<QLabel*>(widget);
        QCheckBox* checkbox = dynamic_cast<QCheckBox*>(widget);
        QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget);
        QTextEdit* textEdit = dynamic_cast<QTextEdit*>(widget);
        QPlainTextEdit* plainTextEdit = dynamic_cast<QPlainTextEdit*>(widget);
        QTextBrowser* textBrowser = dynamic_cast<QTextBrowser*>(widget);
        // 条件为了防止在随意调节界面大小时,界面上出现黑块问题
        if (textBrowser == NULL && (btn != NULL || label != NULL || checkbox != NULL
            || lineEdit != NULL || textEdit != NULL || plainTextEdit != NULL)) {
            int initialFontSize = widget->height() / 3;
            if (btn != NULL) {
                initialFontSize = widget->height() / 4;
            } else if (checkbox != NULL) {
                initialFontSize = widget->height() / 2;
            }
            // 使用Do...While语句是为了始终会设置字体大小一次,防止界面缩小后又放大后,字体还一直保持很小的问题
            do {
                QFont font = widget->font();
                int size = initialFontSize;
                bool needBreak = false;
                // 最小9
                if (size < 9) {
                    size = 9;
                    needBreak = true;
                }
                // 根据按钮的宽度/高度调整字体大小,可以根据默认控件的高度和字体的大小比率进行适当调整
                font.setPointSize(size);
                widget->setFont(font);
                initialFontSize--;
                if (needBreak) {
                    break;
                }
            } while(checkTextOverflow(widget));
        }
    }
}

void Tab2Widget::findAllLayoutAndWidget(QObject *object) {
    QLayout *layout = qobject_cast<QLayout*>(object);
    QWidget *widget = qobject_cast<QWidget*>(object);
    if (layout) {
        if (layout->objectName() != "" && !allLayoutMap.contains(layout)) {
            allLayoutMap.insert(layout, layout->geometry());
        }
        for (int i = 0; i < layout->count(); ++i) {
            findAllLayoutAndWidget(layout->itemAt(i)->widget());
        }
    } else if (widget) {
        if (widget != this && widget->objectName() != "" && !allWidgetMap.contains(widget)) {
            allWidgetMap.insert(widget, widget->geometry());
        }
        for (int i = 0; i < widget->children().size(); ++i) {
            findAllLayoutAndWidget(widget->children().at(i));
        }
    }
}

bool Tab2Widget::checkTextOverflow(QWidget *widget) {
    if (widget == NULL) {
        return false;
    }
    QPushButton* btn = dynamic_cast<QPushButton*>(widget);
    QLabel* label = dynamic_cast<QLabel*>(widget);
    QCheckBox* checkbox = dynamic_cast<QCheckBox*>(widget);
    QLineEdit* lineEdit = dynamic_cast<QLineEdit*>(widget);
    QTextEdit* textEdit = dynamic_cast<QTextEdit*>(widget);
    QPlainTextEdit* plainTextEdit = dynamic_cast<QPlainTextEdit*>(widget);
    // TODO 可增加其他可调节文本的控件,以下的text文本也要相应赋值控件文本

    // 获取按钮的字体和文本
    QString text = "";
    if (btn != NULL) {
        text = btn->text();
    } else if (label != NULL) {
        text = label->text();
    } else if (checkbox != NULL) {
        text = checkbox->text();
    } else if (lineEdit != NULL) {
        text = lineEdit->text();
    } else if (textEdit != NULL) {
        text = textEdit->toPlainText();
    } else if (plainTextEdit != NULL) {
        text = plainTextEdit->toPlainText();
    }

    if (text.isEmpty()) {
        return false;
    }

    QFont font = widget->font();
    QFontMetrics fm(font);

    // 计算文本的宽度和高度
    int textWidth = fm.horizontalAdvance(text);
    int textHeight = fm.height();

    // 获取按钮的尺寸(不包括边框)
    QSize buttonSize = widget->size();
    // 宽度多减去一些,为了防止有些时候按钮字体和左右边框切边
    buttonSize.rwidth() -= 8 * widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); // 减去边框宽度
    buttonSize.rheight() -= 2 * widget->style()->pixelMetric(QStyle::PM_DefaultFrameWidth); // 减去边框高度

    // 检查是否溢出
    if (textWidth > buttonSize.width() || textHeight > buttonSize.height()) {
        return true;
    } else {
        return false;
    }
}

// 控件根据窗口大小自动调整控件大小 @}

注意:要实现界面自适应尺寸,控件的sizePolicy最好设置成Preferred或者Expanding,少用Fixed,否则会出现在缩小窗口尺寸的时候,由于控件最小尺寸设定死了,容易出现文字切边的问题。

相关推荐
C4程序员21 分钟前
北京JAVA基础面试30天打卡14
java·开发语言·面试
黑客影儿1 小时前
Go特有的安全漏洞及渗透测试利用方法(通俗易懂)
开发语言·后端·安全·web安全·网络安全·golang·系统安全
君鼎1 小时前
Effective C++ 条款55:熟悉Boost库
c++
你好,我叫C小白2 小时前
C语言 常量,数据类型
c语言·开发语言·数据类型·常量
小红帽2.02 小时前
从ioutil到os:Golang在线客服聊天系统文件读取的迁移实践
服务器·开发语言·golang
阿巴~阿巴~3 小时前
深入解析C++非类型模板参数
开发语言·c++
多吃蔬菜!!!3 小时前
vscode 搭建C/C++开发环境搭建(linux)
linux·c语言·c++
小指纹5 小时前
河南萌新联赛2025第(六)场:郑州大学
java·开发语言·数据结构·c++·算法
岁忧5 小时前
(nice!!!)(LeetCode 每日一题) 1277. 统计全为 1 的正方形子矩阵 (动态规划)
java·c++·算法·leetcode·矩阵·go·动态规划