走进Qt--常用控件QTabWidget&QListWidget &QScrollArea

589 阅读11分钟

1. 前言

在开发 Qt 应用程序时,合理选择和组合控件可以极大提升用户体验与开发效率。本章将介绍三个在实际项目中极为常用的控件:

  • QTabWidget:多标签页控件,常用于构建设置页面、功能分类区域;
  • QListWidget:条目列表控件,支持选择、展示、交互操作,适合用于导航栏、数据列表等场景;
  • QScrollArea:滚动区域控件,用于在空间有限的窗口中展示超出可视范围的内容。

这些控件本身功能丰富,但使用方式并不复杂。本文将通过简洁示例演示它们的常用功能,并在最后用一个综合案例展示如何将它们结合使用,构建一个既实用又美观的界面。


2. QTabWidget(标签页控件)

2.1 简介

QTabWidget 是 Qt 中用于管理多个标签页的控件,它允许用户在同一窗口中切换不同的界面内容,常用于设置界面、工具界面等需要分类展示的场景。其核心优势包括:

  • 空间效率:在有限区域内组织大量内容
  • 交互友好:符合用户对浏览器/设置面板的操作直觉
  • 高度可定制:支持文字/图标标签、可关闭标签、拖拽排序等

2.2 常用API

方法说明
addTab(QWidget *page, const QString &label)添加一个新标签页
insertTab(int index, QWidget *page, const QString &label)在指定位置插入标签页
setCurrentIndex(int index)设置当前显示的标签页
currentIndex()获取当前索引
setTabPosition(QTabWidget::TabPosition)设置标签位置(如顶部、左侧等)
setTabsClosable(bool)设置是否允许关闭标签页
removeTab(int index)移除指定标签页

2.3 示例:创建标签页

setWindowTitle("QTabWidget");
resize(400, 300);
QVBoxLayout* vlay = new QVBoxLayout(this);

QTabWidget* tabWidget = new QTabWidget(this);
tabWidget->addTab(new QLabel("这是第一页内容"), "第一页");
tabWidget->addTab(new QLabel("这是第二页内容"), "第二页");
tabWidget->addTab(new QLabel("这是第三页内容"), QIcon(":/ControlDemo/resources/env.png"), "第三页");

vlay->addWidget(tabWidget);
setLayout(vlay); 

image.png

常用属性设置

// 标签位置(可设为North/South/West/East)
tabWidget->setTabPosition(QTabWidget::South);

// 标签形状(Rounded/Triangular)
tabWidget->setTabShape(QTabWidget::Triangular);

// 关闭按钮显示策略
tabWidget->setTabsClosable(true);

image.png

2.4 动态标签管理(添加与关闭)

//添加关闭按钮功能
connect(tabWidget, &QTabWidget::tabCloseRequested, [=](int index) {
    if (tabWidget->count() > 1) tabWidget->removeTab(index);
    else QMessageBox::warning(this, "警告", "至少保留一个标签页");
    });

//动态添加新项
QPushButton* addBtn = new QPushButton("+");
vlay->addWidget(addBtn);
connect(addBtn, &QPushButton::clicked, [=]() {
    int newIdx = tabWidget->addTab(new QWidget, QString("页%1").arg(tabWidget->count() + 1));
    });

image.png

当删除最后一个标签页时:

image.png

当点击【+】按钮时,会添加新的标签页:

image.png

2.5 自定义标签样式(QSS)

通过样式表看自定义外观,例如:

tabWidget->setStyleSheet(R"(
    QTabBar::tab {
        background: lightgray;
        padding: 8px;
        border: 1px solid gray;
    }
    QTabBar::tab:selected {
        background: white;
        font-weight: bold;
    }
)");

image.png

2.6 拖拽排序支持

QTabWidget 本身不支持标签拖拽排序,但可通过继承 QTabWidget 并使用 QTabBar::setMovable(true) 实现:

tabWidget->tabBar()->QTabBar::setMovable(true); // 允许拖动标签重新排序

image.png

如需更进一步控制拖放行为,可重写 QTabBar 并实现自定义拖拽逻辑。


3. QListWidget(列表控件)

3.1简介

QListWidget 是 Qt 提供的一个用于显示和操作项目列表的控件。它基于 QListView,但提供了更高层次的封装,适用于简单的列表使用场景,无需模型-视图编程。具有以下特点:

  • 数据-视图分离:继承自QListView,但内置了基于项的便捷接口
  • 多样化显示:支持图标、文字、复选框等多种元素
  • 交互灵活:提供单选/多选、拖拽、排序等交互模式

3.2 常用API

方法/信号说明
addItem(const QString &text)添加文本项
addItem(QListWidgetItem *item)添加自定义项
insertItem(int row, const QString &text)插入文本项
takeItem(int row)移除并返回某一项
currentItem() / currentRow()获取当前选中项或行号
item(int row)获取指定行的项
clear()清空列表
itemClicked(QListWidgetItem *item)单击信号
itemDoubleClicked(QListWidgetItem *item)双击信号

3.3 示例:创建列表

 QListWidget* listWidget = new QListWidget(this);
 // 添加简单文本项
 listWidget->addItem("项目一");
 listWidget->addItem("项目二");
 // 添加带图标项
 QListWidgetItem* item = new QListWidgetItem(QIcon(":/QListWidgetDemo/resources/kits.png"), "用户设置");
 listWidget->addItem(item); 
 //插入项到指定位置
 listWidget->insertItem(1, "新增中间项");
 connect(listWidget, &QListWidget::itemClicked, this, [](QListWidgetItem* item) {
    qDebug() << "您点击了:" << item->text();
    });

image.png

常用属性设置

// 选择模式设置(单选/多选等)
listWidget->setSelectionMode(QAbstractItemView::ExtendedSelection);

// 视图模式(列表/图标模式)
listWidget->setViewMode(QListView::IconMode);

// 启用排序
listWidget->setSortingEnabled(true);

(1) setSelectionMode(QAbstractItemView::ExtendedSelection)

这是 设置选择模式 的方法,决定用户可以选中多少项:

模式含义
SingleSelection只能选中一项(默认)
MultiSelection可以点击多个项,每次点击一个新增项
ExtendedSelection可使用 Ctrl/Shift 扩展选择(推荐)
NoSelection禁止选择任何项

(2) setViewMode(QListView::IconMode)

这设置的是 视图模式,控制项的显示风格:

模式含义
ListMode垂直列表显示(默认)
IconMode横向图标视图(类似桌面图标)

(3) setSortingEnabled(true)

开启 自动排序功能,根据项的文本进行字典序排序。

3.4 自定义列表样式

在默认情况下,QListWidgetItem 只支持简单的文字和图标展示。但我们可以通过 QListWidget::setItemWidget() 方法,将任意 QWidget 作为该项的显示内容,从而实现更复杂的界面效果。

QListWidget *listWidget = new QListWidget(this);

// 创建自定义项
QListWidgetItem *customItem = new QListWidgetItem(listWidget);
customItem->setSizeHint(QSize(300, 60));  // 设置该项大小

// 创建一个 QWidget 作为自定义项的显示控件
QWidget *itemWidget = new QWidget;
QLabel *icon = new QLabel;
icon->setPixmap(QPixmap(":/icon.png").scaled(40, 40)); // 设置图标
QLabel *text = new QLabel("这是一个自定义列表项");

// 创建布局并添加控件
QHBoxLayout *layout = new QHBoxLayout(itemWidget);
layout->addWidget(icon);
layout->addWidget(text);
layout->addStretch(); // 弹簧占位
layout->setContentsMargins(5, 5, 5, 5);  // 设置边距

// 将该控件设置为 customItem 的显示内容
listWidget->setItemWidget(customItem, itemWidget);

image.png

3.5 右键菜单与拖拽功能

 // 启用右键菜单
 listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
 
 connect(listWidget, &QListWidget::customContextMenuRequested, [=](const QPoint& pos) {
     QMenu menu;
     menu.addAction("删除", [=] {
         delete listWidget->currentItem();
         });
     menu.exec(listWidget->viewport()->mapToGlobal(pos));
     });

 // 启用拖拽排序
 listWidget->setDragDropMode(QAbstractItemView::InternalMove);

右键删除:

image.png

image.png

鼠标拖拽排序

image.png

3.6 删除列表项

// 清除所有项(自动释放内存)
listWidget->clear(); 

// 手动删除指定项
QList<QListWidgetItem*> items = listWidget->findItems("test", Qt::MatchContains);
foreach(QListWidgetItem* item, items){
    delete item; // 必须手动删除
}

4. QScrollArea(滚动区域控件)

4.1 简介

QScrollArea 是一个可以在空间不足时提供滚动条的容器,适用于展示较大内容的子控件(如图片、表单等)。其工作原理如下:

  • 视口(Viewport):显示内容的可见区域
  • 滚动条(ScrollBar):水平和垂直方向的滚动控制器
  • 微件(Widget):实际承载内容的内部控件

4.2 基本使用步骤

// 1. 创建滚动区域
QScrollArea* scrollArea = new QScrollArea(this);
scrollArea->setMinimumWidth(120);
scrollArea->setWidgetResizable(true); // 关键设置!

// 2. 创建内容控件(必须设置布局)
QWidget* contentWidget = new QWidget;
contentWidget->setLayout(new QVBoxLayout); // 必须有布局

// 3. 向内容控件添加大量内容
for (int i = 0; i < 50; i++) {
    contentWidget->layout()->addWidget(new QPushButton(QString("按钮 %1").arg(i))); 
}

// 4. 设置内容控件
scrollArea->setWidget(contentWidget);

image.png

4.3 常用设置

方法说明
setWidget(QWidget *widget)设置滚动区域中要显示的主控件
setWidgetResizable(bool)设置是否自动调整内容控件大小
setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy)设置水平滚动条策略(如始终显示、自动、隐藏)
setVerticalScrollBarPolicy(Qt::ScrollBarPolicy)设置垂直滚动条策略
setViewportMargins(int l, int t, int r, int b)设置视口四个方向的边距,控制内容与边界的距离
viewport()返回显示内容的可视区域(即不含滚动条部分)
viewport()->size()获取可视区域的实际大小
widget()获取当前显示的内容控件
widget()->sizeHint()获取内容控件推荐大小(通常用于布局或滚动判断)

4.4 常见问题解决方案

现象:内部控件只显示部分内容 解决方案

  1. 确认内容控件设置了布局
  2. 检查是否调用了setWidgetResizable(true)
  3. 验证内容控件的sizeHint()是否正确

现象: 滚动条不出现问题

排查步骤

  1. 确认内容尺寸大于视口尺寸
  2. 检查滚动策略设置
  3. 确保没有覆盖sizeHint()minimumSizeHint()

5. 综合示例:信息管理系统界面

下面是一个使用QTabWidget、QListWidget和QScrollArea的综合案例,实现一个简单的信息管理系统界面。

案例功能说明

  1. 用户管理标签页
    • 左侧QListWidget显示用户列表
    • 右侧QScrollArea包含用户详细信息表单
    • 点击用户列表项自动填充表单
  2. 产品管理标签页
    • 顶部搜索栏
    • 中间QScrollArea展示产品列表
    • 底部操作按钮
  3. 订单管理标签页
    • 左侧QListWidget显示订单列表
    • 右侧QScrollArea展示订单详情
    • 可更新订单状态

MainWindow.h

#pragma once

#include <QtWidgets/QMainWindow>
#include <QTabWidget>
#include <QListWidget>
#include <QScrollArea>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPushButton>
#include <QLabel>
#include <QLineEdit>


class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    void initUI();
    void setupUserTab();
    void setupProductTab();
    void setupOrderTab();

    QTabWidget* tabWidget;
};

MainWindow.cpp

#include "MainWindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
  //设置窗口标题和大小
    setWindowTitle("信息管理系统");
    setMinimumSize(600, 400);

    //初始化
    initUI();
}

MainWindow::~MainWindow()
{}

//初始化主界面
void MainWindow::initUI()
{
    //中央部件
    QWidget* centralWidget = new QWidget(this);
    setCentralWidget(centralWidget);
    //主布局
    QVBoxLayout* mainLayout = new QVBoxLayout(centralWidget);
    //创建标签页控件
    tabWidget = new QTabWidget(this);
    tabWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    tabWidget->setTabPosition(QTabWidget::West);//标签在左侧

    //添加标签页
    setupUserTab();
    setupProductTab();
    setupOrderTab();

    mainLayout->addWidget(tabWidget);
   
}

//用户列表与详情区域
void MainWindow::setupUserTab()
{
    QWidget* userTab = new QWidget;
    QHBoxLayout* tabLayout = new QHBoxLayout(userTab);

    //左侧用户列表
    QListWidget* userList = new QListWidget;
    userList->addItems({
        "张三",
        "李四",
        "王五",
        "赵六"
        });
    userList->setFixedWidth(150);

    //右侧详情区域
    QScrollArea* scrollArea = new QScrollArea;
    QWidget* detailWidget = new QWidget;
    QVBoxLayout* detailLayout = new QVBoxLayout(detailWidget);

    //详情内容
    QLabel* titleLabel = new QLabel("用户详细信息");
    titleLabel->setStyleSheet("font-size:16px;font-weight:bold");

    QLabel* nameLabel = new QLabel("姓名:");
    QLineEdit* nameEdit = new QLineEdit;

    QLabel* ageLabel = new QLabel("年龄:");
    QLineEdit* ageEdit = new QLineEdit;

    QPushButton* saveBtn = new QPushButton("保存");

    //添加到详情布局
    detailLayout->addWidget(titleLabel);
    detailLayout->addWidget(nameLabel);
    detailLayout->addWidget(nameEdit);
    detailLayout->addWidget(ageLabel);
    detailLayout->addWidget(ageEdit);
    detailLayout->addWidget(saveBtn);
    detailLayout->addStretch();

    //设置滚动区域
    scrollArea->setWidget(detailWidget);
    scrollArea->setWidgetResizable(true);

    //连接信号槽
    connect(userList, &QListWidget::itemClicked, [=](QListWidgetItem* item) {
        nameEdit->setText(item->text());
        ageEdit->setText("30"); //模拟数据
        });

    //添加到标签页布局
    tabLayout->addWidget(userList);
    tabLayout->addWidget(scrollArea);

    //添加到标签页
    tabWidget->addTab(userTab, "用户管理");
}

//产品管理标签页
void MainWindow::setupProductTab()
{
    QWidget* productTab = new QWidget;
    QVBoxLayout* mainLayout = new QVBoxLayout(productTab);
    
    //顶部搜索栏
    QHBoxLayout* searchLayout = new QHBoxLayout;
    QLineEdit* searchEdit = new QLineEdit;
    QPushButton* searchBtn =new QPushButton("搜索");
    searchLayout->addWidget(searchEdit);
    searchLayout->addWidget(searchBtn);

    //中部产品列表
    QScrollArea* scrollArea = new QScrollArea;
    QWidget* listWidget = new QWidget;
    QVBoxLayout* listLayout = new QVBoxLayout(listWidget);

    //模拟产品数据
    QStringList products = {
        "笔记本电脑",
        "智能手机",
        "平板电脑",
        "数码相机",
        "智能手表",
        "蓝牙耳机",
        "游戏主机",
        "显示器"
    };

    for (const QString& product : products) {
        QHBoxLayout* itemLayout = new QHBoxLayout;

        QLabel* nameLabel = new QLabel(product);
        QLabel* priceLabel = new QLabel("3999");
        QPushButton* detailBtn = new QPushButton("查看详情");

        itemLayout->addWidget(nameLabel);
        itemLayout->addStretch();
        itemLayout->addWidget(priceLabel);
        itemLayout->addWidget(detailBtn);
       
        listLayout->addLayout(itemLayout);
    }
    listLayout->addStretch();

    //设置滚动区域
    scrollArea->setWidget(listWidget);
    scrollArea->setWidgetResizable(true);

    //设置滚动区域
    scrollArea->setWidget(listWidget);
    scrollArea->setWidgetResizable(true);

    //底部操作按钮
    QHBoxLayout* btnLayout = new QHBoxLayout;
    QPushButton* addBtn = new QPushButton("添加产品");
    QPushButton* delBtn = new QPushButton("删除产品");
    btnLayout->addWidget(addBtn);
    btnLayout->addWidget(delBtn);

    //添加到主布局
    mainLayout->addLayout(searchLayout);
    mainLayout->addWidget(scrollArea);
    mainLayout->addLayout(btnLayout);

    //添加到标签页
    tabWidget->addTab(productTab, "产品管理");
}

//订单标签页
void MainWindow::setupOrderTab()
{
    QWidget* orderTab = new QWidget;
    QHBoxLayout* mainLayout = new QHBoxLayout(orderTab);

    //左侧订单列表
    QListWidget* orderList = new QListWidget;
    orderList->addItems({
        "订单 #1001",
        "订单 #1002",
        "订单 #1003",
        "订单 #1004",
        "订单 #1005"
        });
    orderList->setFixedWidth(200);

    //右侧详情区域
    QScrollArea* scrollArea = new QScrollArea;
    QWidget* detailWidget = new QWidget;
    QVBoxLayout* detailLayout = new QVBoxLayout(detailWidget);
    
    //订单详情内容
    QLabel* orderTitle = new QLabel("订单详情");
    orderTitle->setStyleSheet("font-size: 16px; font-weight: bold;");
   
    QLabel* orderIdLabel = new QLabel("订单编号:");
    QLineEdit* orderIdEdit = new QLineEdit;
    orderIdEdit->setReadOnly(true);

    QLabel* productLabel = new QLabel("产品:");
    QLineEdit* productEdit = new QLineEdit;
    productEdit->setReadOnly(true);

    QLabel* amountLabel = new QLabel("金额:");
    QLineEdit* amountEdit = new QLineEdit;
    amountEdit->setReadOnly(true);

    QLabel* statusLabel = new QLabel("状态:");
    QLineEdit* statusEdit = new QLineEdit;

    QPushButton* updateBtn = new QPushButton("更新状态");

    // 添加到详情布局
    detailLayout->addWidget(orderTitle);
    detailLayout->addWidget(orderIdLabel);
    detailLayout->addWidget(orderIdEdit);
    detailLayout->addWidget(productLabel);
    detailLayout->addWidget(productEdit);
    detailLayout->addWidget(amountLabel);
    detailLayout->addWidget(amountEdit);
    detailLayout->addWidget(statusLabel);
    detailLayout->addWidget(statusEdit);
    detailLayout->addWidget(updateBtn);
    detailLayout->addStretch();

    // 设置滚动区域
    scrollArea->setWidget(detailWidget);
    scrollArea->setWidgetResizable(true);

    // 连接信号槽
    connect(orderList, &QListWidget::itemClicked, [=](QListWidgetItem* item) {
        orderIdEdit->setText(item->text());
        productEdit->setText("笔记本电脑"); // 模拟数据
        amountEdit->setText("¥5999");
        statusEdit->setText("已发货");
        });

    // 添加到主布局
    mainLayout->addWidget(orderList);
    mainLayout->addWidget(scrollArea);

    // 添加到标签页
    tabWidget->addTab(orderTab, QIcon(":/icons/order.png"), "订单管理");
}

main.cpp

#include "MainWindow.h"
#include <QtWidgets/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // 设置全局样式
    a.setStyleSheet(R"(
        QTabWidget::pane {
            border: 1px solid #ccc;
            margin-top: -1px;
        }
        QTabBar::tab {
            padding: 8px 12px;
            background: #f0f0f0;
            border: 1px solid #999;
        }
        QTabBar::tab:selected {
            background: #fff;
            border-bottom-color: #fff;
        }
        QListWidget {
            border: 1px solid #ddd;
        }
        QScrollArea {
            border: 1px solid #ddd;
            background: #fff;
        }
    )");
    MainWindow w;
    w.show();
    return a.exec();
}

最终效果

image.png

image.png

image.png

注意事项

由于使用QMainWindow,需使用setCentralWidget()来设置主内容区域。

QWidget *centralWidget = new QWidget(this);
    setCentralWidget(centralWidget);

6. 总结与建议

1. 各控件的适用场景

  • QTabWidget:适用于需要将功能/视图模块进行分页管理的场景,常用于设置页、编辑器、管理面板等。
  • QListWidget:适用于展示条目列表并进行选择操作,如项目导航、聊天联系人、菜单项等。支持自定义项、自定义视图等。
  • QScrollArea:适用于内容尺寸可能超过可视区域时的情况,如文档查看器、设置面板、图片/表单展示等。

2. 使用建议

  • QTabWidget 默认即可使用,若要增强功能可考虑添加标签关闭按钮、自定义样式、支持拖拽排序等。
  • QListWidget 是基于 QListView 的高级封装,适合快速使用。若对列表项内容有更高要求,建议配合 setItemWidget 与自定义控件使用。
  • QScrollArea建议将内容控件使用布局包裹,同时调用 setWidgetResizable(true) 以确保控件尺寸随视口动态变化。若未设置布局,子控件可能不随窗口变化自动调整。