3. Qt基础控件(一):QWidget、QLabel、QPushButton、QLineEdit、QTextEdit,信号与槽机制入门
好,咱们正式开始接触Qt的“积木块”——基础控件。这一章我打算把最常用的五个控件讲透,再带大家入门Qt的灵魂机制:信号与槽。
说实话,我早年刚学Qt时,觉得控件不就是拖拖拽拽嘛,没啥技术含量。后来做工业级项目才发现,控件用得好不好,直接决定了软件的稳定性和用户体验。你想想看,一个串口调试工具,如果文本框刷新卡顿、按钮响应迟钝,用户第一反应就是“这软件写崩了”。
所以,咱们从最底层开始,把每个控件的脾气摸清楚。
3.1 控件的祖宗:QWidget
QWidget是所有可视控件的基类。说白了,你在界面上看到的任何一个窗口、按钮、文本框,本质上都是一个QWidget。
我个人习惯把QWidget理解成“一张白纸”。你可以往上面画东西,也可以把它当作容器,往里面放其他控件。它有几个核心属性,我列个表,大家记一下:
| 属性 | 类型 | 说明 | 我常用的场景 |
|---|---|---|---|
geometry() |
QRect | 控件的位置和大小 | 动态调整布局时,直接setGeometry |
setVisible() |
bool | 显示/隐藏 | 做多页切换时,隐藏不用的页面 |
setEnabled() |
bool | 启用/禁用 | 串口未打开时,禁用发送按钮 |
setStyleSheet() |
QString | 样式表 | 美化界面,后面会专门讲 |
setWindowTitle() |
QString | 窗口标题 | 每个主窗口必备 |
3.2 显示文本的利器:QLabel
QLabel是显示文本或图片的控件。它不能交互,只能看。但别小看它,很多细节需要注意。
我遇到过一个问题:在串口调试工具里,用QLabel显示接收到的数据,结果数据一多,界面就卡死了。后来发现是QLabel默认开启了自动换行和富文本解析,每次更新都要重新渲染HTML。
所以,如果你只是显示纯文本,记得关掉富文本:
// 高性能纯文本显示
QLabel *label = new QLabel("接收数据", this);
label->setTextFormat(Qt::PlainText); // 关掉富文本
label->setWordWrap(false); // 关掉自动换行
label->setFixedWidth(200); // 固定宽度,避免布局反复计算
3.3 按钮的王者:QPushButton
QPushButton是最常用的交互控件。它有三种状态:正常、悬停、按下。我建议你至少给按钮设置三种不同的样式,让用户有明确的反馈。
来看一个典型的按钮用法:
// 创建一个发送按钮
QPushButton *btnSend = new QPushButton("发送", this);
btnSend->setFixedSize(80, 30);
// 设置快捷键
btnSend->setShortcut(QKeySequence("Ctrl+Enter"));
// 设置样式(后面会详细讲)
btnSend->setStyleSheet(
"QPushButton { background-color: #3498db; color: white; border-radius: 4px; }"
"QPushButton:hover { background-color: #2980b9; }"
"QPushButton:pressed { background-color: #1c6ea4; }"
);
这里有个细节:setFixedSize 和 setMinimumSize 的区别。前者是固定大小,后者是最小大小。在布局里,我建议用 setMinimumSize,这样布局可以自适应调整。
3.4 单行输入:QLineEdit
QLineEdit用于输入单行文本。它有几个模式,我整理了一下:
| 模式 | 设置方法 | 用途 |
|---|---|---|
| 普通 | 默认 | 输入任意文本 |
| 密码 | setEchoMode(QLineEdit::Password) |
输入密码,显示为圆点 |
| 数字 | setValidator(new QIntValidator()) |
只允许输入数字 |
| 只读 | setReadOnly(true) |
显示信息,禁止修改 |
我在做串口参数配置时,经常用QLineEdit来输入波特率、IP地址等。这里有个坑:textChanged 信号会在每次输入变化时触发,如果你在里面做校验,要注意性能。我一般用 editingFinished 信号,它只在用户按回车或焦点离开时触发。
// 推荐的做法:用editingFinished代替textChanged
QLineEdit *editBaud = new QLineEdit("9600", this);
editBaud->setValidator(new QIntValidator(1200, 115200, this));
connect(editBaud, &QLineEdit::editingFinished, [=]() {
int baud = editBaud->text().toInt();
// 在这里更新串口配置
qDebug() << "波特率已更新:" << baud;
});
3.5 多行文本:QTextEdit
QTextEdit是显示和编辑多行富文本的控件。在串口调试工具里,它常被用来显示接收到的数据。
但要注意,QTextEdit默认支持富文本,如果你只是显示纯文本,一定要关掉:
// 串口接收数据显示专用配置
QTextEdit *textReceive = new QTextEdit(this);
textReceive->setReadOnly(true); // 只读
textReceive->setAcceptRichText(false); // 不接受富文本
textReceive->setMaximumBlockCount(1000); // 限制最大行数,防止内存溢出
3.6 信号与槽:Qt的灵魂
信号与槽是Qt最核心的机制。它解决了“对象A发生了某件事,需要通知对象B”的问题。说白了,就是一种松耦合的通信方式。
我画了一张图,帮大家理解信号与槽的工作流程:
信号与槽的语法,我推荐使用Qt5引入的新式写法,编译期就能检查类型安全:
// 旧式写法(不推荐,类型不安全)
connect(btnSend, SIGNAL(clicked()), this, SLOT(onSendClicked()));
// 新式写法(推荐,编译期检查)
connect(btnSend, &QPushButton::clicked, this, &MainWindow::onSendClicked);
// Lambda表达式写法(灵活,适合简单逻辑)
connect(btnSend, &QPushButton::clicked, [=]() {
QString data = editInput->text();
// 发送数据...
});
[=] 按值捕获,[this] 捕获当前对象指针,别把整个界面都捕获进去了,容易造成内存泄漏。
3.7 综合示例:一个简单的串口发送界面
把上面学的知识串起来,写一个简单的串口发送界面:
// MainWindow.h
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
private slots:
void onSendClicked();
private:
QLineEdit *m_editInput;
QPushButton *m_btnSend;
QTextEdit *m_textDisplay;
};
// MainWindow.cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
// 创建中心控件
QWidget *central = new QWidget(this);
setCentralWidget(central);
// 创建控件
m_editInput = new QLineEdit("请输入发送数据", this);
m_btnSend = new QPushButton("发送", this);
m_textDisplay = new QTextEdit(this);
m_textDisplay->setReadOnly(true);
// 布局
QVBoxLayout *layout = new QVBoxLayout(central);
QHBoxLayout *topLayout = new QHBoxLayout();
topLayout->addWidget(m_editInput);
topLayout->addWidget(m_btnSend);
layout->addLayout(topLayout);
layout->addWidget(m_textDisplay);
// 连接信号与槽
connect(m_btnSend, &QPushButton::clicked, this, &MainWindow::onSendClicked);
connect(m_editInput, &QLineEdit::returnPressed, this, &MainWindow::onSendClicked);
}
void MainWindow::onSendClicked()
{
QString data = m_editInput->text();
if (data.isEmpty()) {
m_textDisplay->append("警告:发送数据为空!");
return;
}
// 这里将来会调用串口发送
m_textDisplay->append("发送:" + data);
m_editInput->clear();
}
connect 连接了同一个信号两次,结果槽函数被执行了两次。排查了半天才发现。所以,如果你在构造函数里连接信号,一定要确保只连接一次。如果需要在运行时动态连接/断开,可以用 disconnect。