第七章 基础控件(二):QCheckBox、QRadioButton、QSpinBox、QSlider、QProgressBar、QDateTimeEdit

好,咱们接着聊基础控件。上一章我们把按钮、输入框这些“门面”控件讲透了,这一章要深入一些交互性更强、带状态反馈的控件。说白了,就是让用户能做选择、调数值、看进度、选时间。这些控件在项目中几乎天天见,我敢说你写的每个Qt程序里至少会用到其中两三个。

7.1 QCheckBox:多选开关,状态不止两种

QCheckBox,复选框,大家再熟悉不过了。但你真的用对了吗?

先看最基础的用法:

QCheckBox *checkBox = new QCheckBox("记住密码", this);
connect(checkBox, &QCheckBox::toggled, [](bool checked) {
    qDebug() << "选中状态:" << checked;
});

嗯,很简单。但我要说的是——复选框其实有三种状态。默认是两态(选中/未选中),但你还可以开启第三态:

checkBox->setTristate(true);  // 开启三态
checkBox->setCheckState(Qt::PartiallyChecked); // 部分选中

什么时候用第三态? 我在做权限管理模块时遇到过:一个父节点下有多个子权限,如果子权限部分选中、部分未选,父节点就应该显示“部分选中”状态。这个设计很符合直觉,用户一看就懂。

小技巧: 判断三态时别用 checked(),要用 checkState()。checked() 只返回 true/false,会把 PartiallyChecked 当成 true,容易出 bug。

7.2 QRadioButton:单选,天生排他

QRadioButton 和 QCheckBox 长得像,但逻辑完全不同。单选按钮是“排他”的——同一组里只能选一个。

关键点:必须放到 QButtonGroup 里。否则同个父窗口下的所有单选按钮会自动互斥,这往往不是你想要的效果。

QRadioButton *rbMale = new QRadioButton("男");
QRadioButton *rbFemale = new QRadioButton("女");
QRadioButton *rbOther = new QRadioButton("其他");

QButtonGroup *group = new QButtonGroup(this);
group->addButton(rbMale, 1);
group->addButton(rbFemale, 2);
group->addButton(rbOther, 3);

connect(group, QOverload<int>::of(&QButtonGroup::buttonClicked),
        [](int id) {
    qDebug() << "选中了ID:" << id;
});

我个人习惯给每个按钮分配一个枚举值,这样代码可读性高,后期维护也方便。比如:

enum Gender { Male = 1, Female, Other };
注意: QButtonGroup 不负责按钮的布局,只管理互斥逻辑。布局还是要靠 QHBoxLayout、QVBoxLayout 这些。

7.3 QSpinBox:数字调节,精准又灵活

QSpinBox 是整数调节框,QDoubleSpinBox 是浮点版。用法几乎一样,我就合在一起讲了。

QSpinBox *spinBox = new QSpinBox(this);
spinBox->setRange(0, 100);
spinBox->setValue(50);
spinBox->setSingleStep(5);
spinBox->setSuffix(" 分");  // 显示后缀

connect(spinBox, QOverload<int>::of(&QSpinBox::valueChanged),
        [](int value) {
    qDebug() << "当前值:" << value;
});

我曾经在做一个视频剪辑软件时,需要让用户设置帧率。帧率一般是 24、25、30、60 这些特殊值,不是连续的。这时候 QSpinBox 的 setSpecialValueText() 就派上用场了:

spinBox->setSpecialValueText("自动");  // 当值为最小值时显示"自动"

但更灵活的做法是用 QComboBox 配合自定义模型。嗯,这个后面再细讲。

7.4 QSlider:滑动条,手感很重要

QSlider 有两种方向:水平(Qt::Horizontal)和垂直(Qt::Vertical)。

QSlider *slider = new QSlider(Qt::Horizontal, this);
slider->setRange(0, 100);
slider->setValue(30);
slider->setTickPosition(QSlider::TicksBelow);  // 刻度线在下方
slider->setTickInterval(10);

滑动条最常用的场景是音量控制、亮度调节、进度跳转。但有个细节很多人忽略——滑块移动的粒度。默认是 1,如果你想让用户拖动时感觉更“顺滑”,可以把 singleStep 设小一点,pageStep 设大一点:

slider->setSingleStep(1);   // 键盘左右键步进
slider->setPageStep(10);    // 键盘 PageUp/PageDown 步进
核心经验: 滑动条和数值显示控件(比如 QLabel 或 QSpinBox)通常是成对出现的。用户拖动滑块时,旁边的数字实时更新,这样交互反馈最直观。

7.5 QProgressBar:进度条,别让用户干等

进度条是给用户“定心丸”的。哪怕进度不准,也比没有强。

QProgressBar *progressBar = new QProgressBar(this);
progressBar->setRange(0, 100);
progressBar->setValue(0);
progressBar->setTextVisible(true);  // 显示百分比文字

如果你不知道任务总量,可以用“忙碌指示”模式:

progressBar->setRange(0, 0);  // 范围设为 0-0,进入忙碌模式
// 此时进度条会显示一个来回滚动的动画条

我在做文件上传工具时,进度条是必须的。但有个坑——不要在 UI 线程里做耗时操作。如果你在主线程里直接循环更新进度,界面会卡死,进度条根本动不了。正确做法是用 QThread 或 QtConcurrent 把任务丢到后台线程,然后通过信号更新进度。

// 后台线程发射信号
emit progressUpdated(current, total);

// 主线程槽函数更新
connect(worker, &Worker::progressUpdated,
        progressBar, &QProgressBar::setMaximum);  // 设置最大值
connect(worker, &Worker::progressUpdated,
        progressBar, &QProgressBar::setValue);    // 设置当前值

7.6 QDateTimeEdit:日期时间选择,格式要人性化

QDateTimeEdit 是日期时间选择器,支持年、月、日、时、分、秒的单独调节。

QDateTimeEdit *dateTimeEdit = new QDateTimeEdit(QDateTime::currentDateTime(), this);
dateTimeEdit->setDisplayFormat("yyyy-MM-dd HH:mm:ss");
dateTimeEdit->setCalendarPopup(true);  // 弹出日历选择

显示格式的占位符要记牢:

占位符含义示例
yyyy四位年份2025
MM两位月份03
dd两位日期15
HH24小时制14
mm分钟30
ss45

我曾经踩过一个坑:用户输入日期时,如果格式是 "yyyy/MM/dd",但系统区域设置用的是 "-" 分隔符,解析就会出错。所以建议统一用 "-" 或 ".",并且 setDisplayFormat 要和实际数据格式一致

实用技巧: 如果你只需要日期,可以用 QDateEdit;只需要时间,用 QTimeEdit。它们分别是 QDateTimeEdit 的子类,接口更简洁。

7.7 知识体系总览

这一章的内容比较多,我画了一张图帮你理清脉络:

第七章 基础控件(二) 知识体系 基础控件(二) QCheckBox 复选框 两态 / 三态 toggled / checkState QRadioButton 单选 QButtonGroup 分组 排他互斥 QSpinBox 数字调节 整数 / 浮点 范围 / 步进 / 后缀 QSlider 滑动条 水平 / 垂直 刻度 / 步进粒度 QProgressBar 进度条 确定模式 / 忙碌模式 后台线程更新 QDateTimeEdit 日期时间 显示格式 / 日历弹出 QDateEdit / QTimeEdit 核心:状态管理 + 数值交互 + 进度反馈 + 时间选择

7.8 综合示例:一个简单的设置面板

把今天学的控件串起来,写一个模拟的设置面板:

// 头文件略
QVBoxLayout *layout = new QVBoxLayout(this);

// 复选框:开启高级模式
QCheckBox *cbAdvanced = new QCheckBox("高级模式");
layout->addWidget(cbAdvanced);

// 单选按钮组:主题选择
QRadioButton *rbLight = new QRadioButton("浅色主题");
QRadioButton *rbDark = new QRadioButton("深色主题");
QButtonGroup *themeGroup = new QButtonGroup(this);
themeGroup->addButton(rbLight, 0);
themeGroup->addButton(rbDark, 1);
rbLight->setChecked(true);

QHBoxLayout *themeLayout = new QHBoxLayout;
themeLayout->addWidget(rbLight);
themeLayout->addWidget(rbDark);
layout->addLayout(themeLayout);

// 滑动条 + 数字框:音量调节
QHBoxLayout *volumeLayout = new QHBoxLayout;
QSlider *volumeSlider = new QSlider(Qt::Horizontal);
volumeSlider->setRange(0, 100);
QSpinBox *volumeSpin = new QSpinBox;
volumeSpin->setRange(0, 100);
volumeSpin->setSuffix("%");

connect(volumeSlider, &QSlider::valueChanged, volumeSpin, &QSpinBox::setValue);
connect(volumeSpin, QOverload<int>::of(&QSpinBox::valueChanged), volumeSlider, &QSlider::setValue);

volumeLayout->addWidget(new QLabel("音量:"));
volumeLayout->addWidget(volumeSlider);
volumeLayout->addWidget(volumeSpin);
layout->addLayout(volumeLayout);

// 进度条:模拟加载
QProgressBar *progressBar = new QProgressBar;
progressBar->setRange(0, 100);
layout->addWidget(progressBar);

QPushButton *btnStart = new QPushButton("开始模拟");
layout->addWidget(btnStart);

connect(btnStart, &QPushButton::clicked, [progressBar]() {
    for (int i = 0; i <= 100; ++i) {
        progressBar->setValue(i);
        QApplication::processEvents(); // 简单演示用,实际项目别这么写
        QThread::msleep(30);
    }
});

// 日期时间选择
QDateTimeEdit *dtEdit = new QDateTimeEdit(QDateTime::currentDateTime());
dtEdit->setDisplayFormat("yyyy-MM-dd HH:mm:ss");
dtEdit->setCalendarPopup(true);
layout->addWidget(new QLabel("计划执行时间:"));
layout->addWidget(dtEdit);

这个例子虽然简单,但涵盖了今天讲的大部分控件。你可以在项目里直接套用这个结构,稍作修改就能用。

总结一下: 复选框管“要不要”,单选按钮管“选哪个”,数字框管“调多少”,滑动条管“拖到哪”,进度条管“等多久”,日期时间管“定在何时”。每个控件都有自己的“脾气”,摸透了就好用。

好,这一章就到这里。代码多敲几遍,手感自然就来了。


公众号:蓝海资料掘金营,微信deep3321