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;
}
}
*/

后续还会对简易计算器进行改进。。。(未完待续)

       

posted @   Alpha205  阅读(473)  评论(0编辑  收藏  举报
编辑推荐:
· 从 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)
点击右上角即可分享
微信分享提示