4. Qt基础控件(下)
上一章我们把按钮、输入框这些基础控件聊得差不多了。今天继续往下走,聊聊剩下几个在数据采集和处理中高频使用的控件。说实话,这几个控件在实际项目里出现的频率,比前面那些还要高。我自己的经验是,一个数据采集软件,界面上一半以上的交互都离不开它们。
本章核心脉络:选择类控件(下拉框、复选框、单选按钮) + 列表/表格/树形控件(数据展示三剑客)。
4.1 QComboBox:下拉选择框
QComboBox 说白了就是一个下拉菜单。用户点一下,弹出一堆选项,选一个完事。在数据采集中,我经常用它来做「通道选择」、「采样率设置」、「量程切换」这类操作。
嗯,这里要注意:QComboBox 有两种模式——可编辑和不可编辑。默认是不可编辑的,用户只能从列表里选。但如果你把 setEditable(true) 打开,用户就能自己输入了。我在做串口调试工具时,波特率选择就用了可编辑模式——预设几个常用值,但允许用户手动输入特殊值。
个人习惯:如果选项超过15个,我会考虑用 QCompleter 做自动补全。不然用户翻列表翻到崩溃。
// 基本用法
QComboBox *combo = new QComboBox(this);
combo->addItem("通道1", QVariant(1)); // 显示文本 + 用户数据
combo->addItem("通道2", QVariant(2));
combo->addItem("通道3", QVariant(3));
combo->setCurrentIndex(0); // 默认选中第一个
// 获取当前选中
int index = combo->currentIndex();
QVariant data = combo->currentData();
QString text = combo->currentText();
// 信号连接
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged),
[=](int index) {
qDebug() << "选中了第" << index << "项";
});
我曾经踩过的坑:QComboBox 的 currentIndexChanged 信号有两个重载版本——一个传 int,一个传 QString。如果你用 connect 直接连,编译器会报歧义。必须用 QOverload 指定类型。这个坑我至少花了半小时才找到原因。
4.2 QCheckBox:复选框
复选框,就是那个小方框,打勾表示「是」,不打勾表示「否」。在数据采集中,我常用它来做「启用/禁用」某个功能,或者「选择/取消选择」某些数据通道。
QCheckBox 有三个状态:选中、未选中、部分选中。第三个状态比较特殊,需要调用 setTristate(true) 开启。什么时候用?比如你有一个「全选」复选框,下面挂了10个子项——如果10个全选了,父复选框就是选中状态;如果全没选,就是未选中;如果只选了部分,就是部分选中。这个设计在树形结构中特别常见。
// 基本用法
QCheckBox *chk = new QCheckBox("启用数据记录", this);
chk->setChecked(true); // 默认选中
// 三态模式
QCheckBox *chkAll = new QCheckBox("全选", this);
chkAll->setTristate(true);
// 信号
connect(chk, &QCheckBox::stateChanged, [=](int state) {
if (state == Qt::Checked) {
qDebug() << "已选中";
} else if (state == Qt::Unchecked) {
qDebug() << "未选中";
} else if (state == Qt::PartiallyChecked) {
qDebug() << "部分选中";
}
});
我建议:用 QButtonGroup 把一组复选框管理起来,方便做互斥或批量操作。虽然复选框本身不互斥,但有时候业务逻辑需要「只能选一个」,这时候用 QButtonGroup 设置 exclusive 就对了。
4.3 QRadioButton:单选按钮
单选按钮和复选框长得很像,但逻辑完全不同。单选按钮是圆的,一组里面只能选一个。你想想看,什么时候用单选?「性别选择」、「模式切换」、「协议选择」——这些场景天然就是互斥的。
QRadioButton 必须配合 QButtonGroup 使用,否则多个单选按钮之间不会自动互斥。我刚开始学 Qt 时犯过这个错——放了三个单选按钮,结果发现都能同时选中,还以为是 bug。
// 创建单选按钮
QRadioButton *rb1 = new QRadioButton("TCP模式");
QRadioButton *rb2 = new QRadioButton("UDP模式");
QRadioButton *rb3 = new QRadioButton("串口模式");
// 必须用 QButtonGroup 管理
QButtonGroup *group = new QButtonGroup(this);
group->addButton(rb1, 1); // 第二个参数是 id
group->addButton(rb2, 2);
group->addButton(rb3, 3);
rb1->setChecked(true); // 默认选中第一个
// 信号
connect(group, QOverload<int>::of(&QButtonGroup::buttonClicked),
[=](int id) {
qDebug() << "选中了模式" << id;
});
避坑指南:QButtonGroup 不负责控件的布局,只负责逻辑互斥。你仍然需要把单选按钮放到 QVBoxLayout 或 QHBoxLayout 里。我曾经见过有人只加了 QButtonGroup 没加布局,结果按钮叠在一起了。
4.4 QListWidget:列表控件
QListWidget 就是展示一列数据的控件。在数据采集中,我常用它来显示「设备列表」、「文件列表」、「日志条目」。它比 QComboBox 的优势在于——可以同时看到多个选项,而且支持多选。
QListWidget 的每个条目是一个 QListWidgetItem。你可以给每个条目设置文本、图标、用户数据,甚至自定义字体颜色。
// 创建列表
QListWidget *list = new QListWidget(this);
// 添加条目
for (int i = 0; i < 10; ++i) {
QListWidgetItem *item = new QListWidgetItem(
QString("设备 %1").arg(i + 1));
item->setData(Qt::UserRole, i + 100); // 存储设备ID
list->addItem(item);
}
// 设置选择模式
list->setSelectionMode(QAbstractItemView::ExtendedSelection);
// 信号
connect(list, &QListWidget::currentItemChanged,
[=](QListWidgetItem *current, QListWidgetItem *previous) {
if (current) {
qDebug() << "当前选中:" << current->text();
}
});
个人经验:如果列表数据量超过1000条,建议用 QListView + 自定义模型,而不是 QListWidget。QListWidget 把所有数据都存内存里,数据量大了会卡。我做过一个日志查看器,用 QListWidget 加载10万条日志,直接卡死。后来换成 QListView + QStringListModel,流畅多了。
4.5 QTableWidget:表格控件
表格控件,这是数据采集软件的核心。传感器数据、采样点、配置参数——这些天然就是表格结构。QTableWidget 是 QTableView 的便捷版本,适合中小规模数据(几百行以内)。
QTableWidget 的行和列从0开始编号。每个单元格是一个 QTableWidgetItem。你可以设置表头、合并单元格、设置单元格编辑属性。
// 创建表格
QTableWidget *table = new QTableWidget(5, 3, this); // 5行3列
table->setHorizontalHeaderLabels({"时间", "通道1", "通道2"});
// 填充数据
for (int row = 0; row < 5; ++row) {
for (int col = 0; col < 3; ++col) {
QTableWidgetItem *item = new QTableWidgetItem(
QString("%1").arg(row * 10 + col));
table->setItem(row, col, item);
}
}
// 设置属性
table->setEditTriggers(QAbstractItemView::DoubleClicked); // 双击编辑
table->setSelectionBehavior(QAbstractItemView::SelectRows); // 选中整行
table->horizontalHeader()->setStretchLastSection(true); // 最后一列拉伸
// 获取选中行
connect(table, &QTableWidget::cellClicked, [=](int row, int col) {
QTableWidgetItem *item = table->item(row, col);
if (item) {
qDebug() << "点击了单元格:" << item->text();
}
});
我曾经犯过的错:QTableWidget 的 setItem 如果传入 nullptr,会直接崩溃。所以在删除单元格数据时,记得先检查 item 是否存在。另外,表格的排序功能默认是关闭的,需要调用 setSortingEnabled(true) 开启。但注意——开启排序后,你插入数据时如果还在用 append 方式,排序会打乱你的插入顺序。我一般先关排序,插完数据再开。
4.6 QTreeWidget:树形控件
树形控件,用来展示层级关系。在数据采集中,我常用它来展示「设备-通道-参数」这种三层结构,或者「项目-组-数据点」这种树形数据。
QTreeWidget 的每个节点是一个 QTreeWidgetItem。父节点可以挂子节点,子节点还能继续挂。每个节点可以有多列——第一列是树形展开列,后面几列是附加信息。
// 创建树形控件
QTreeWidget *tree = new QTreeWidget(this);
tree->setColumnCount(3);
tree->setHeaderLabels({"名称", "数值", "单位"});
// 创建根节点
QTreeWidgetItem *root = new QTreeWidgetItem(tree);
root->setText(0, "传感器组1");
// 创建子节点
QTreeWidgetItem *child1 = new QTreeWidgetItem(root);
child1->setText(0, "温度");
child1->setText(1, "25.3");
child1->setText(2, "°C");
QTreeWidgetItem *child2 = new QTreeWidgetItem(root);
child2->setText(0, "湿度");
child2->setText(1, "60%");
child2->setText(2, "RH");
// 展开所有节点
tree->expandAll();
// 信号
connect(tree, &QTreeWidget::itemClicked,
[=](QTreeWidgetItem *item, int column) {
qDebug() << "点击了:" << item->text(0);
});
我建议:树形控件如果层级太深(超过5层),用户体验会很差。用户要点很多次才能找到目标。我一般控制在3层以内。如果数据天然就是深层嵌套的,考虑用 QTreeView + 自定义模型,配合懒加载——只展开当前节点时再加载子节点,而不是一次性加载所有数据。
4.7 综合示例:数据采集配置面板
最后,我把这几个控件组合起来,做一个简单的数据采集配置面板。这个例子我在实际项目中用过类似的。
// 伪代码示例,展示控件组合使用
// 1. 设备选择 - QComboBox
QComboBox *deviceCombo = new QComboBox;
deviceCombo->addItem("设备A", "COM1");
deviceCombo->addItem("设备B", "COM2");
// 2. 通道选择 - QCheckBox 列表
QListWidget *channelList = new QListWidget;
channelList->addItem("通道1");
channelList->addItem("通道2");
channelList->addItem("通道3");
channelList->setSelectionMode(QAbstractItemView::MultiSelection);
// 3. 采样模式 - QRadioButton
QRadioButton *modeContinuous = new QRadioButton("连续采样");
QRadioButton *modeSingle = new QRadioButton("单次采样");
QButtonGroup *modeGroup = new QButtonGroup;
modeGroup->addButton(modeContinuous, 0);
modeGroup->addButton(modeSingle, 1);
modeContinuous->setChecked(true);
// 4. 数据显示 - QTableWidget
QTableWidget *dataTable = new QTableWidget(0, 4);
dataTable->setHorizontalHeaderLabels({"时间", "通道", "数值", "状态"});
// 5. 配置树 - QTreeWidget
QTreeWidget *configTree = new QTreeWidget;
configTree->setHeaderLabel("配置项");
// ... 填充树形数据
好了,这六个控件就聊到这里。QComboBox 做选择,QCheckBox 做开关,QRadioButton 做互斥,QListWidget 做列表,QTableWidget 做表格,QTreeWidget 做树形。每个控件都有自己的适用场景,选对了能让你的界面既简洁又高效。
下一章我们聊聊布局管理——怎么把这些控件摆得整整齐齐,让强迫症看了都说舒服。
核心要点回顾:
- QComboBox 用 QOverload 解决信号重载歧义
- QCheckBox 支持三态模式,用 QButtonGroup 管理组
- QRadioButton 必须配合 QButtonGroup 实现互斥
- QListWidget 适合小数据量,大数据用 QListView
- QTableWidget 注意空指针检查和排序时机
- QTreeWidget 层级控制在3层以内,考虑懒加载
可可精品资料库