C++ Qt学习笔记 (1) 简易计算器设计
最近开始学习c++ qt, 按照教材上的例程设计一个简易的桌面计算器:
Qt是一个基于C++语言的跨平台应用程序和UI开发框架,主要包含一个类库,和跨平台开发及国际化的工具,最初由挪威的Trolltech公司开发,后来被诺基亚收购,现在属于Digia公司。qt最大的特点是其跨平台的属性,同一份代码可以在不同平台上编译运行。qt中使用信号/槽机制来代替传统UI设计中的回调函数。
信号和槽是Qt中对象通信的一种方式,它代替了传统的GUI编程中利用回调函数传递消息的机制。回调函数的原理是:将处理函数传递到按钮控件中去,在用户点击按钮对象之后,按钮控件对象调用传进去的处理函数,这种将一个函数传进去,在将来某个时刻调用的方式称为回调。信号与槽机制:同样需要编写处理函数(称为槽函数),但是不需要将处理函数传递给控件,只需要将按钮的单击信号(如clicked(),这是Qt种已经比那些好的函数)和槽函数连接起来即可。使用更加方便,但是也带来了效率上的损耗。
QWidget是所有图像用户将界面的基类,QWidget包含所有界面共有的特征:1. 接受鼠标点击事件 2. 接受键盘事件 3. 区域渲染
同时,QWidget也可以包含其他的界面控件。
(由于目前没有找到好的Qt教材,所以只对Qt有一个简单的了解,后续会进行更加全面的学习)
1. 计算器界面设计:
这里界面设计采用了Qt designer, 在Qt Creator种新建项目,进入.ui文件。进行界面设计,如下图所示:
对界面中的控件进行命名,以及属性设置:
将计算器的按钮与相应的槽函数进行连接:
这里使用Qt中的connect方法,将按钮和相应的槽函数连接在一起:
connect(btn, SIGNAL(clicked()), this, SLOT(onDigitClicked()));
connect函数的形式如上所示:
在计算器设计中,采用的方式为,将数字按钮0~9连接到同一个槽函数中,将加减乘除连接到同一个槽函数中,将其它的按钮连接到对应的槽函数中,首先定义在widget.h文件中定义相应的槽函数:
private slots: void onDigitClicked(); // 数字键按下对应的槽函数 void onOperatorClicked(); // 运算符键按下对应的槽函数 void onEqualBtnClicked(); // 按下运算键对应的槽函数 void onDotBtnClicked(); // 按下小数点对应的槽函数 void onClearBtnClicked(); // 清除按钮对应的槽函数 void onClearAllBtnClicked(); // 清除所有按钮对应的槽函数 void onSignBtnClicked(); // 正负号按键所对应的槽函数
定义函数connectSlots(),将连接槽函数的过程进行封装:
private: void connectSolts(); // 将数字键以及符号键连接到槽函数
实现connectSlots()函数:
void Widget::connectSolts() { // 链接槽函数 // 将十个数字键连接到槽函数onDigitClicked(); QPushButton *digit_btns[10] = { ui->btn_0, ui->btn_1, ui->btn_2, ui->btn_3, ui->btn_4, ui->btn_5, ui->btn_6, ui->btn_7, ui->btn_8, ui->btn_9 }; for(auto btn : digit_btns) //for(int i=0; i<10; i++) { // 将按键连接到槽函数 connect(btn, SIGNAL(clicked()), this, SLOT(onDigitClicked())); } // 对应的按钮为指针 QPushButton *operatorBtn[4] = { ui->btn_add, ui->btn_subtract, ui->btn_multiply, ui->btn_divide }; for(auto btn : operatorBtn) // for(int i=0; i<4; i++) { connect(btn, SIGNAL(clicked()), this, SLOT(onOperatorClicked())); } connect(ui->btn_equal, SIGNAL(clicked()), this, SLOT(onEqualBtnClicked())); // 等号键按下 connect(ui->btn_dot, SIGNAL(clicked()), this, SLOT(onDotBtnClicked())); // 小数点键安按下 connect(ui->btn_clear, SIGNAL(clicked()), this, SLOT(onClearBtnClicked())); // 清除键按下 connect(ui->btn_clear_all, SIGNAL(clicked()), this, SLOT(onClearAllBtnClicked())); // 清除所有 按钮 connect(ui->btn_neg_pos, SIGNAL(clicked()),this, SLOT(onSignBtnClicked())); // 符号按键 }
上面通过循环的方式,将相应的按钮与槽函数连接。相应槽函数的实现如下:
1. 数字键按下对应的槽函数:
void Widget::onDigitClicked() { // std::cout << "数字键按下" << std::endl; qDebug() << "digit key pressed" << endl; QPushButton *digitBtn = static_cast<QPushButton*>(sender()); // sender()表示信号的发送者 QString value = digitBtn->text(); // 获取按钮的text属性 // 判断按键 if(ui->result->text() == "0" && value == "0") // 按键为0 return; if(waitForOperator) // 等在操作数 状态为真 { ui->result->setText(value); waitForOperator = false; // 此时不再需要等待操作数 } else { ui->result->setText(ui->result->text() + value); } }
2. 运算符键按下对应的槽函数实现:
void Widget::onOperatorClicked() { // qDebug() << "operator key pressed" << endl; // 判断按下的运算符键 QPushButton *clickedBtn = static_cast<QPushButton*>(sender()); // 将信号源转化为QpusuBytton指针 QString value = clickedBtn->text(); // 获取运算符 // 此时的状态是按下了运算符,所以需要获取第一个运算数 double operand = ui->result->text().toDouble(); // 获取运算数 if(pendingOperator.isEmpty()) // 运算符为空 { result = operand; } else { if(!calculate(operand, value)) { abortOperation(); return; } ui->result->setText(QString::number(result)); } // 更新运算符 pendingOperator = value; waitForOperator = true; // 等待新的输入数字 }
函数calculate(operand, value)用于获取运算符和操作数之后进行计算,具体的实现如下所示:
bool Widget::calculate(double operand, QString pendingOperator) { if(pendingOperator == "+") { result += operand; // 加法运算 } else if(pendingOperator == "-") { result -= operand; } else if(pendingOperator == "*") { result *= operand; } else { if(operand == 0) { return false; } result /= operand; } return true; }
Qt中的键盘事件:
Qt中使用QKeyEvent来描述键盘事件,当键盘按下胡哦这松开的时候,键盘事件便会传递给拥有键盘输入焦点的控件,key()函数可以用来获取具体的按键值。在简易计算器设计中中加入键盘事件,使得在键盘上输入数据和在计算器上输入具有相同的效果,给按钮添加快捷键的方式主要有两种方法:
1. 重写键盘事件函数:
首先,在widget.h文件中,键盘事件函数必须定义为protected类型:
#include <QKeyEvent>
protected: void keyPressEvent(QKeyEvent* event);
键盘事件函数的定义:
(未解决的bug, emit ui->btn_0->clicked报错问题)
void Widget::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_0: emit ui->btn_0->clicked(); break; case Qt::Key_1: emit ui->btn_1->clicked(); break; case Qt::Key_2: emit ui->btn_2->clicked(); break; case Qt::Key_3: emit ui->btn_3->clicked(); break; case Qt::Key_4: emit ui->btn_4->clicked(); break; case Qt::Key_5: emit ui->btn_5->clicked(); break; case Qt::Key_6: emit ui->btn_6->clicked(); break; case Qt::Key_7: emit ui->btn_7->clicked(); break; case Qt::Key_8: emit ui->btn_8->clicked(); break; case Qt::Key_9: emit ui->btn_9->clicked(); break; case Qt::Key_Plus: emit ui->btn_add->clicked(); break; case Qt::Key_Minus: emit ui->btn_subtract->clicked(); break; case Qt::Key_Asterisk: emit ui->btn_multiply->clicked(); break; case Qt::Key_Slash: emit ui->btn_divide->clicked(); break; case Qt::Key_Enter: case Qt::Key_Equal: emit ui->btn_equal->clicked(); break; case Qt::Key_Period: emit ui->btn_dot->clicked(); break; case Qt::Key_M: emit ui->btn_neg_pos->clicked(); break; case Qt::Key_Backspace: emit ui->btn_clear->clicked(); break; default: break; } }
2. 通过按钮控件的setShortCut()函数
在widget.h中定义快捷键设置的函数setShortCut()
public: void setShortCut(); // 设计快捷键
void Widget::setShortCut() { Qt::Key key[18] = { Qt::Key_0, Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_Plus, Qt::Key_Minus, Qt::Key_Asterisk, Qt::Key_Slash, Qt::Key_Enter, Qt::Key_Period, Qt::Key_Backspace, Qt::Key_M }; QPushButton *btn[18] = { ui->btn_0, ui->btn_1, ui->btn_2, ui->btn_3, ui->btn_4, ui->btn_5, ui->btn_6, ui->btn_7, ui->btn_8, ui->btn_9, ui->btn_add, ui->btn_subtract, ui->btn_multiply, ui->btn_divide, ui->btn_equal, ui->btn_dot, ui->btn_clear, ui->btn_neg_pos }; for(int i=0; i<18; i++) { btn[i]->setShortcut(QKeySequence(key[i])); } ui->btn_clear_all->setShortcut(QKeySequence("Ctrl+Backspace")); }
Qt中的鼠标事件:
Qt中用一个对象表示一个事件(event), 继承自QEvent。QMouseEvent事件用来表示鼠标事件,它可以检测到当前哪个键被按下了,或者鼠标的当前位置。QWheelEvent用来表示鼠标滚轮事件,用来获取滚轮的滑动方向和距离,共有5个鼠标事件处理函数,在.h文件中这些事件处理函数必须为protected类型,同时需要包含头文件<QMouseEvent><QwheelEvent>
mousePressEvent()
mouseReleaseEvent()
mouseDoubleClickedEvent()
mouseMoveEvent()
wheelEvent()
---------------------------------------------------------------------------------------------------------------
简易计算器完整代码:
widget.h文件
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QString> #include <QKeyEvent> namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private: Ui::Widget *ui; private: bool calculate(double operand, QString pendingOperator); // 做算数运算 void abortOperation(); // 结束运算 void connectSolts(); // 将数字键以及符号键连接到槽函数 QString pendingOperator; // 存储运算符 double result; // 存储运算结果 bool waitForOperator; // 标志位,是否等待操作数 private slots: void onDigitClicked(); // 数字键按下对应的槽函数 void onOperatorClicked(); // 运算符键按下对应的槽函数 void onEqualBtnClicked(); // 按下运算键对应的槽函数 void onDotBtnClicked(); // 按下小数点对应的槽函数 void onClearBtnClicked(); // 清除按钮对应的槽函数 void onClearAllBtnClicked(); // 清除所有按钮对应的槽函数 void onSignBtnClicked(); // 正负号按键所对应的槽函数 public: void setShortCut(); // 设计快捷键 //protected: // void keyPressEvent(QKeyEvent* event); }; #endif // WIDGET_H
widget.cpp文件:
#include "widget.h" #include "ui_widget.h" #include <QString> #include <QLayout> #include <QMessageBox> #include <iostream> #include <QDebug> Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); waitForOperator = true; // 初始状态等待操作数 result = 0.0; // 初始状态结果为0 ui->result->setText("0"); connectSolts(); // 连接所有的按键与对应的槽函数 setShortCut(); } Widget::~Widget() { delete ui; } bool Widget::calculate(double operand, QString pendingOperator) { if(pendingOperator == "+") { result += operand; // 加法运算 } else if(pendingOperator == "-") { result -= operand; } else if(pendingOperator == "*") { result *= operand; } else { if(operand == 0) { return false; } result /= operand; } return true; } void Widget::abortOperation() { // 终止运算,清除数据,输出错误信息 result = 0; pendingOperator.clear(); // 清除运算符 ui->result->setText("0"); // 清除显示结果 waitForOperator = true; // 等待一个操作数输入 QMessageBox::warning(this, "运算错误", "除数不能为零"); } void Widget::connectSolts() { // 链接槽函数 // 将十个数字键连接到槽函数onDigitClicked(); QPushButton *digit_btns[10] = { ui->btn_0, ui->btn_1, ui->btn_2, ui->btn_3, ui->btn_4, ui->btn_5, ui->btn_6, ui->btn_7, ui->btn_8, ui->btn_9 }; for(auto btn : digit_btns) //for(int i=0; i<10; i++) { // 将按键连接到槽函数 connect(btn, SIGNAL(clicked()), this, SLOT(onDigitClicked())); } QPushButton *operatorBtn[4] = { ui->btn_add, ui->btn_subtract, ui->btn_multiply, ui->btn_divide }; for(auto btn : operatorBtn) // for(int i=0; i<4; i++) { connect(btn, SIGNAL(clicked()), this, SLOT(onOperatorClicked())); } connect(ui->btn_equal, SIGNAL(clicked()), this, SLOT(onEqualBtnClicked())); // 等号键按下 connect(ui->btn_dot, SIGNAL(clicked()), this, SLOT(onDotBtnClicked())); // 小数点键安按下 connect(ui->btn_clear, SIGNAL(clicked()), this, SLOT(onClearBtnClicked())); // 清除键按下 connect(ui->btn_clear_all, SIGNAL(clicked()), this, SLOT(onClearAllBtnClicked())); // 清除所有 按钮 connect(ui->btn_neg_pos, SIGNAL(clicked()),this, SLOT(onSignBtnClicked())); // 符号按键 } void Widget::onDigitClicked() { // std::cout << "数字键按下" << std::endl; qDebug() << "digit key pressed" << endl; QPushButton *digitBtn = static_cast<QPushButton*>(sender()); // sender()表示信号的发送者 QString value = digitBtn->text(); // 获取按钮的text属性 // 判断按键 if(ui->result->text() == "0" && value == "0") // 按键为0 return; if(waitForOperator) // 等在操作数 状态为真 { ui->result->setText(value); waitForOperator = false; // 此时不再需要等待操作数 } else { ui->result->setText(ui->result->text() + value); } } void Widget::onOperatorClicked() { // qDebug() << "operator key pressed" << endl; // 判断按下的运算符键 QPushButton *clickedBtn = static_cast<QPushButton*>(sender()); // 将信号源转化为QpusuBytton指针 QString value = clickedBtn->text(); // 获取运算符 // 此时的状态是按下了运算符,所以需要获取第一个运算数 double operand = ui->result->text().toDouble(); // 获取运算数 if(pendingOperator.isEmpty()) // 运算符为空 { result = operand; } else { if(!calculate(operand, value)) { abortOperation(); return; } ui->result->setText(QString::number(result)); } // 更新运算符 pendingOperator = value; waitForOperator = true; // 等待新的输入数字 } void Widget::onEqualBtnClicked() { // 等号键按下,需要计算最终的结果 double operand = ui->result->text().toDouble(); // 获取运算数 if(pendingOperator.isEmpty()) // 没有输入加减乘除运算符,直接按了等号 { return; } if(!calculate(operand, pendingOperator)) { abortOperation(); return; } ui->result->setText(QString::number(result)); waitForOperator = true; result = 0.0; pendingOperator.clear(); } void Widget::onDotBtnClicked() { if(waitForOperator) { ui->result->setText("0"); } if(ui->result->text().contains(".")) { // no operation } else { ui->result->setText(ui->result->text() + "."); } waitForOperator = false; // 当前数字的输入还未结束 } void Widget::onClearBtnClicked() { ui->result->setText("0"); // 输入的数字清零 waitForOperator = true; // 重新输入 } void Widget::onClearAllBtnClicked() { ui->result->setText("0"); waitForOperator = true; result = 0.0; pendingOperator.clear(); } void Widget::onSignBtnClicked() { QString text = ui->result->text(); double value = text.toDouble(); if(value > 0) { text.prepend("-"); } else { text.remove(0, 1); } ui->result->setText(text); } void Widget::setShortCut() { Qt::Key key[18] = { Qt::Key_0, Qt::Key_1, Qt::Key_2, Qt::Key_3, Qt::Key_4, Qt::Key_5, Qt::Key_6, Qt::Key_7, Qt::Key_8, Qt::Key_9, Qt::Key_Plus, Qt::Key_Minus, Qt::Key_Asterisk, Qt::Key_Slash, Qt::Key_Enter, Qt::Key_Period, Qt::Key_Backspace, Qt::Key_M }; QPushButton *btn[18] = { ui->btn_0, ui->btn_1, ui->btn_2, ui->btn_3, ui->btn_4, ui->btn_5, ui->btn_6, ui->btn_7, ui->btn_8, ui->btn_9, ui->btn_add, ui->btn_subtract, ui->btn_multiply, ui->btn_divide, ui->btn_equal, ui->btn_dot, ui->btn_clear, ui->btn_neg_pos }; for(int i=0; i<18; i++) { btn[i]->setShortcut(QKeySequence(key[i])); } ui->btn_clear_all->setShortcut(QKeySequence("Ctrl+Backspace")); } /* void Widget::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_0: emit ui->btn_0->clicked(); break; case Qt::Key_1: emit ui->btn_1->clicked(); break; case Qt::Key_2: emit ui->btn_2->clicked(); break; case Qt::Key_3: emit ui->btn_3->clicked(); break; case Qt::Key_4: emit ui->btn_4->clicked(); break; case Qt::Key_5: emit ui->btn_5->clicked(); break; case Qt::Key_6: emit ui->btn_6->clicked(); break; case Qt::Key_7: emit ui->btn_7->clicked(); break; case Qt::Key_8: emit ui->btn_8->clicked(); break; case Qt::Key_9: emit ui->btn_9->clicked(); break; case Qt::Key_Plus: emit ui->btn_add->clicked(); break; case Qt::Key_Minus: emit ui->btn_subtract->clicked(); break; case Qt::Key_Asterisk: emit ui->btn_multiply->clicked(); break; case Qt::Key_Slash: emit ui->btn_divide->clicked(); break; case Qt::Key_Enter: case Qt::Key_Equal: emit ui->btn_equal->clicked(); break; case Qt::Key_Period: emit ui->btn_dot->clicked(); break; case Qt::Key_M: emit ui->btn_neg_pos->clicked(); break; case Qt::Key_Backspace: emit ui->btn_clear->clicked(); break; default: break; } } */
后续还会对简易计算器进行改进。。。(未完待续)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)