「Qt Widget中文示例指南」如何创建一个计算器?(二)

Qt 是目前最先进、最完整的跨平台C++开发工具。它不仅完全实现了一次编写,所有平台无差别运行,更提供了几乎所有开发过程中需要用到的工具。如今,Qt已被运用于超过70个行业、数千家企业,支持数百万设备及应用。

本文将展示如何使用信号和槽来实现计算器小部件的功能,以及如何使用QGridLayout在网格中放置子小部件。在上文中(点击这里回顾>>)为大家介绍了实现计算器的Calculator类定义,本文将主要介绍Calculator类是如何实现的,持续关注我们哦~

点击获取Qt Widget组件下载(Q技术交流:166830288)

Qt Widget中文示例指南图集

计算器示例的屏幕截图

这个例子由两个类组成:

  • Calculator是计算器小部件,具有计算器的所有功能。
  • Button是用于每个计算器按钮的小部件,它派生自QToolButton。

我们将从回顾计算器开始,然后再看看按钮。

Calculator类实现
Calculator::Calculator(QWidget *parent)
: QWidget(parent), sumInMemory(0.0), sumSoFar(0.0)
, factorSoFar(0.0), waitingForOperand(true)
{

在构造函数中,初始化计算器的状态。pendingAdditiveOperator和pendingMultiplicativeOperator变量不需要显式初始化,因为QString构造函数将它们初始化为空字符串。也可以直接在header文件中初始化这些变量,这称为成员初始化,避免了长初始化列表。

display = new QLineEdit("0");
display->setReadOnly(true);
display->setAlignment(Qt::AlignRight);
display->setMaxLength(15);

QFont font = display->font();
font.setPointSize(font.pointSize() + 8);
display->setFont(font);

我们创建了表示计算器显示的QLineEdit ,并设置了它的一些属性,特别地我们将其设置为只读。

我们还将显示器的字体放大了8个点。

for (int i = 0; i < NumDigitButtons; ++i)
digitButtons[i] = createButton(QString::number(i), &Calculator::digitClicked);

Button *pointButton = createButton(tr("."), &Calculator::pointClicked);
Button *changeSignButton = createButton(tr("\302\261"), &Calculator::changeSignClicked);

Button *backspaceButton = createButton(tr("Backspace"), &Calculator::backspaceClicked);
Button *clearButton = createButton(tr("Clear"), &Calculator::clear);
Button *clearAllButton = createButton(tr("Clear All"), &Calculator::clearAll);

Button *clearMemoryButton = createButton(tr("MC"), &Calculator::clearMemory);
Button *readMemoryButton = createButton(tr("MR"), &Calculator::readMemory);
Button *setMemoryButton = createButton(tr("MS"), &Calculator::setMemory);
Button *addToMemoryButton = createButton(tr("M+"), &Calculator::addToMemory);

Button *divisionButton = createButton(tr("\303\267"), &Calculator::multiplicativeOperatorClicked);
Button *timesButton = createButton(tr("\303\227"), &Calculator::multiplicativeOperatorClicked);
Button *minusButton = createButton(tr("-"), &Calculator::additiveOperatorClicked);
Button *plusButton = createButton(tr("+"), &Calculator::additiveOperatorClicked);

Button *squareRootButton = createButton(tr("Sqrt"), &Calculator::unaryOperatorClicked);
Button *powerButton = createButton(tr("x\302\262"), &Calculator::unaryOperatorClicked);
Button *reciprocalButton = createButton(tr("1/x"), &Calculator::unaryOperatorClicked);
Button *equalButton = createButton(tr("="), &Calculator::equalClicked);

对于每个按钮,我们使用适当的文本标签和连接到按钮的插槽调用私有createButton()函数。

QGridLayout *mainLayout = new QGridLayout;
mainLayout->setSizeConstraint(QLayout::SetFixedSize);
mainLayout->addWidget(display, 0, 0, 1, 6);
mainLayout->addWidget(backspaceButton, 1, 0, 1, 2);
mainLayout->addWidget(clearButton, 1, 2, 1, 2);
mainLayout->addWidget(clearAllButton, 1, 4, 1, 2);

mainLayout->addWidget(clearMemoryButton, 2, 0);
mainLayout->addWidget(readMemoryButton, 3, 0);
mainLayout->addWidget(setMemoryButton, 4, 0);
mainLayout->addWidget(addToMemoryButton, 5, 0);

for (int i = 1; i < NumDigitButtons; ++i) {
int row = ((9 - i) / 3) + 2;
int column = ((i - 1) % 3) + 1;
mainLayout->addWidget(digitButtons[i], row, column);
}

mainLayout->addWidget(digitButtons[0], 5, 1);
mainLayout->addWidget(pointButton, 5, 2);
mainLayout->addWidget(changeSignButton, 5, 3);

mainLayout->addWidget(divisionButton, 2, 4);
mainLayout->addWidget(timesButton, 3, 4);
mainLayout->addWidget(minusButton, 4, 4);
mainLayout->addWidget(plusButton, 5, 4);

mainLayout->addWidget(squareRootButton, 2, 5);
mainLayout->addWidget(powerButton, 3, 5);
mainLayout->addWidget(reciprocalButton, 4, 5);
mainLayout->addWidget(equalButton, 5, 5);
setLayout(mainLayout);

setWindowTitle(tr("Calculator"));
}

布局由单个QGridLayout处理,QLayout::setSizeConstraint()调用确保Calculator小部件始终显示为其最佳大小(其大小提示),从而防止用户调整计算器的大小,大小提示由子小部件的大小和大小策略决定。

大多数子部件只占用网格布局中的一个单元格,对于这些我们只需要将一行和一列传递给QGridLayout::addWidget()。display、backspaceButton、clearButton和clearAllButton小部件占用多于一列,对于这些我们还必须船体一个行空间和一个列空间。

void Calculator::digitClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
int digitValue = clickedButton->text().toInt();
if (display->text() == "0" && digitValue == 0.0)
return;

if (waitingForOperand) {
display->clear();
waitingForOperand = false;
}
display->setText(display->text() + QString::number(digitValue));
}

按下计算器的数字按钮时将发出按钮的clicked()信号,该信号将触发digitClicked()插槽。

首先我们先使用QObject::sender()找出哪个按钮发送了信号,这个函数以QObject指针的形式返回发送方。因为我们知道发送方是一个Button对象,所以可以安全地强制转换QObject。本来可以使用C风格的强制转换或c++ static_cast<>(),但作为一种防御性编程技术,我们使用qobject_cast()。这样做的好处是,如果对象的类型错误,则返回空指针。空指针导致的崩溃比不安全强制转换导致的崩溃更容易诊断。有了按钮后,我们使用QToolButton::text()提取操作符。

插槽需要特别考虑两种情况,如果显示包含“0”,而用户点击了“0”按钮,那么显示“00”将是愚蠢的。如果计算器处于等待新操作数的状态,新数字就是新操作数的第一位;在这种情况下,必须首先清除先前计算的任何结果。

最后,我们将新数字附加到显示的值后面。

void Calculator::unaryOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();
double result = 0.0;

if (clickedOperator == tr("Sqrt")) {
if (operand < 0.0) {
abortOperation();
return;
}
result = std::sqrt(operand);
} else if (clickedOperator == tr("x\302\262")) {
result = std::pow(operand, 2.0);
} else if (clickedOperator == tr("1/x")) {
if (operand == 0.0) {
abortOperation();
return;
}
result = 1.0 / operand;
}
display->setText(QString::number(result));
waitingForOperand = true;
}

每当单击一个一元操作符按钮时,就调用unaryOperatorClicked()插槽,再次使用QObject::sender()获取指向被单击按钮的指针。操作符从按钮的文本中提取并存储在clickoperator中,操作数从display中获得。

然后我们执行这个操作,如果Sqrt应用于负数或1/x到零,调用abortOperation()。如果一切顺利,我们将在行编辑中显示操作的结果,并将waitingForOperand设置为true。这确保了如果用户键入一个新数字,该数字将被视为一个新的操作数,而不是附加到当前值。

void Calculator::additiveOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
if (!clickedButton)
return;
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();

当用户单击+或-按钮时调用additiveOperatorClicked()槽。

在对单击的操作符进行实际操作之前,我们必须处理所有挂起的操作。从乘法运算符开始,因为它们的优先级高于加法运算符:

if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
display->setText(QString::number(factorSoFar));
operand = factorSoFar;
factorSoFar = 0.0;
pendingMultiplicativeOperator.clear();
}

如果之前已经单击了x或÷,而没有随后单击=,则显示中的当前值是x或÷操作符的右操作数,我们最终可以执行该操作并更新显示。

if (!pendingAdditiveOperator.isEmpty()) {
if (!calculate(operand, pendingAdditiveOperator)) {
abortOperation();
return;
}
display->setText(QString::number(sumSoFar));
} else {
sumSoFar = operand;
}

如果前面已经单击了+或-,则sumSoFar是左操作数,而显示的当前值是操作符的右操作数。如果没有挂起的加法运算符,则简单地将sumSoFar设置为显示中的文本。

pendingAdditiveOperator = clickedOperator;
waitingForOperand = true;
}

最后,我们可以处理刚刚点击的操作符。由于还没有右操作数,所以将单击的操作符存储在pendingAdditiveOperator变量中。稍后当有一个右操作数,而左操作数为sumSoFar时,将应用该操作。

void Calculator::multiplicativeOperatorClicked()
{
Button *clickedButton = qobject_cast<Button *>(sender());
if (!clickedButton)
return;
QString clickedOperator = clickedButton->text();
double operand = display->text().toDouble();

if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
display->setText(QString::number(factorSoFar));
} else {
factorSoFar = operand;
}

pendingMultiplicativeOperator = clickedOperator;
waitingForOperand = true;
}

multiplicativeOperatorClicked()插槽类似于additiveOperatorClicked(),不需要担心挂起的加法运算符,因为乘法运算符优先于加法运算符。

void Calculator::equalClicked()
{
double operand = display->text().toDouble();

if (!pendingMultiplicativeOperator.isEmpty()) {
if (!calculate(operand, pendingMultiplicativeOperator)) {
abortOperation();
return;
}
operand = factorSoFar;
factorSoFar = 0.0;
pendingMultiplicativeOperator.clear();
}
if (!pendingAdditiveOperator.isEmpty()) {
if (!calculate(operand, pendingAdditiveOperator)) {
abortOperation();
return;
}
pendingAdditiveOperator.clear();
} else {
sumSoFar = operand;
}

display->setText(QString::number(sumSoFar));
sumSoFar = 0.0;
waitingForOperand = true;
}

与additiveOperatorClicked()一样,我们首先处理任何挂起的乘法和加法操作符,然后显示sumSoFar并将变量重置为零。必须将变量重置为零,以避免对值进行两次计数。

void Calculator::pointClicked()
{
if (waitingForOperand)
display->setText("0");
if (!display->text().contains('.'))
display->setText(display->text() + tr("."));
waitingForOperand = false;
}

pointClicked()槽向显示的内容添加一个小数点。

void Calculator::changeSignClicked()
{
QString text = display->text();
double value = text.toDouble();

if (value > 0.0) {
text.prepend(tr("-"));
} else if (value < 0.0) {
text.remove(0, 1);
}
display->setText(text);
}

changeSignClicked()槽改变显示值的符号,如果当前值为正,则在前面加一个负号;如果当前值为负,则从值中删除第一个字符(负号)。

void Calculator::backspaceClicked()
{
if (waitingForOperand)
return;

QString text = display->text();
text.chop(1);
if (text.isEmpty()) {
text = "0";
waitingForOperand = true;
}
display->setText(text);
}

backspaceclick()将删除显示中最右边的字符,如果得到一个空字符串,则显示“0”并将waitingForOperand设置为true。

void Calculator::clear()
{
if (waitingForOperand)
return;

display->setText("0");
waitingForOperand = true;
}

clear()槽将当前操作数重置为零,这相当于按退格键多次擦除整个操作数。

void Calculator::clearAll()
{
sumSoFar = 0.0;
factorSoFar = 0.0;
pendingAdditiveOperator.clear();
pendingMultiplicativeOperator.clear();
display->setText("0");
waitingForOperand = true;
}

clearAll()槽将计算器重置为初始状态。

void Calculator::clearMemory()
{
sumInMemory = 0.0;
}

void Calculator::readMemory()
{
display->setText(QString::number(sumInMemory));
waitingForOperand = true;
}

void Calculator::setMemory()
{
equalClicked();
sumInMemory = display->text().toDouble();
}

void Calculator::addToMemory()
{
equalClicked();
sumInMemory += display->text().toDouble();
}

clearMemory()插槽擦除保存在内存中的总和,readMemory()将总和显示为操作数,setMemory()将内存中的总和替换为当前的总和,addtommemory()将当前值添加到内存中的值。对于setMemory()和addtommemory(),我们首先调用equalClicked()来更新sumSoFar和显示中的值。

template<typename PointerToMemberFunction>
Button *Calculator::createButton(const QString &text, const PointerToMemberFunction &member)
{
Button *button = new Button(text);
connect(button, &Button::clicked, this, member);
return button;
}

私有的createButton()函数从构造函数中被调用来创建计算器按钮。

void Calculator::abortOperation()
{
clearAll();
display->setText(tr("####"));
}

私有的abortOperation()函数在计算失败时被调用,重置计算器状态,显示“####”。

bool Calculator::calculate(double rightOperand, const QString &pendingOperator)
{
if (pendingOperator == tr("+")) {
sumSoFar += rightOperand;
} else if (pendingOperator == tr("-")) {
sumSoFar -= rightOperand;
} else if (pendingOperator == tr("\303\227")) {
factorSoFar *= rightOperand;
} else if (pendingOperator == tr("\303\267")) {
if (rightOperand == 0.0)
return false;
factorSoFar /= rightOperand;
}
return true;
}

私有的calculate()函数执行一个二进制操作,右操作数由rightOperand给出。对于加法操作符,左操作数为sumSoFar;对于乘法运算符,左操作数是factorSoFar。如果发生除零,函数返回false。

Qt Widget组件推荐
  • QtitanRibbon - Ribbon UI组件:是一款遵循Microsoft Ribbon UI Paradigm for Qt技术的Ribbon UI组件,QtitanRibbon致力于为Windows、Linux和Mac OS X提供功能完整的Ribbon组件。
  • QtitanChart - Qt类图表组件:是一个C ++库,代表一组控件,这些控件使您可以快速地为应用程序提供漂亮而丰富的图表。
  • QtitanDataGrid - Qt网格组件:提供了一套完整的标准 QTableView 函数和传统组件无法实现的独特功能。使您能够将不同来源的各类数据加载到一个快速、灵活且功能强大的可编辑网格中,支持排序、分组、报告、创建带状列、拖放按钮和许多其他方便的功能。
  • QtitanDocking:允许您像 Visual Studio 一样为您的伟大应用程序配备可停靠面板和可停靠工具栏。黑色、白色、蓝色调色板完全支持 Visual Studio 2019 主题!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/207834.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

淘宝/天猫商品详情API接口丨京东商品详情丨1688商品详情丨接口key密钥获取方式

要获取淘宝/天猫商品详情API接口、京东商品详情API接口、1688商品详情API接口以及接口密钥&#xff08;Key&#xff09;&#xff0c;可以按照以下步骤进行操作&#xff1a; 注册并登录淘宝/天猫开发者中心或京东开放平台或1688开放平台&#xff0c;并创建应用。在创建应用的过…

Human3.6m数据处理(mhformer代码解读)

对于3d人体姿态估计任务中数据集human3.6m的处理 写在最前面&#xff1a;这是我自己的理解&#xff0c;说的不一定对。 human3.6m有很多格式的数据&#xff0c;包括视频、2d ground truth、3d ground truth&#xff0c;还分为xyz坐标的表示形式和旋转向量表示形式&#xff0c;这…

HarmonyOS应用开发者基础认证考试(98分答案)

基于最近大家都在考这个应用开发者基础认证考试&#xff0c;因此出了一期&#xff0c;一样复制word里面搜索做&#xff0c;很快&#xff0c;当然good luck 判断题 Ability是系统调度应用的最小单元,是能够完成一个独立功能的组件。一个应用可以包含一个或多个Ability。 正确(Tr…

Swift构造器继承链

类类型的构造器代理 Swift构造器需遵循以下三大规则&#xff1a; 指定构造器必须调用它直接父类的指定构造器方法便利构造器必须调用同一个类中定义的其他初始化方法便利构造器在最后必须调用一个指定构造器 两段式构造过程 Swift 中类的构造过程包含两个阶段。第一个阶段&a…

Redis主从与哨兵架构详解

目录 主从架构 主从环境搭建 主从复制流程 1. 全量复制 2. 部分复制 主从风暴 哨兵架构 概念 哨兵环境搭建 主从架构 主从环境搭建 1. 复制一份redis.conf文件, 修改下面几行配置 port 6380 pidfile /var/run/redis_6380.pid logfile "6380.log" dir /usr/…

PowerDesigner数据库建模软件的安装

解压&#xff1a; 解压好以后&#xff0c;点击PowerDesigner.exe安装 这个安装的版本是15 选择安装路径&#xff0c;可以默认可以自定义&#xff1a; 直接点next&#xff1a; 全选了 点击next&#xff1a; 点击next&#xff1a; 点finish 汉化&#xff1a; 先把pojie和汉化文件…

Unity中Shader编译目标级别

文章目录 前言一、Shader Model二、Shader编译目标级别法1&#xff1a; #pragma target 3.0法2&#xff1a;#pragma require integers geometry 三、测试代码 前言 针对不同平台的特性&#xff0c;所做的一些功能 一、Shader Model ShaderModel 由微软提出&#xff0c;要求显…

蔚碳科技联合中投会发布国内首个 ESG 尽职调查服务标准

11 月 26 日&#xff0c;蔚碳&#xff08;上海&#xff09;科技有限公司&#xff08;以下简称“蔚碳科技”&#xff09;受邀出席由深圳市人民政府主办&#xff0c;深圳市发展和改革委员会、深圳市生态环境局、龙岗区人民政府共同承办的 2023 碳达峰碳中和论坛暨深圳国际低碳城论…

使用 SDKMAN 管理多版本本地 Java 环境---Centos8 Windows

文章目录 windows 安装centos8 安装卸载sdkman使用 windows 安装 SDKMAN是一个 jdk 多版本管理工具&#xff0c;类似于 nodejs 中的 nvm。可以在本地存在多个 java 环境&#xff0c;快速切换功能&#xff0c;同时&#xff0c;他不止于 java sdk&#xff0c;还有maven、tomcat等…

WhatsApp群发消息脚本功能介绍及代码分享!

随着社交媒体的普及&#xff0c;通讯应用成为了人们日常沟通的主要工具之一&#xff0c;其中&#xff0c;WhatsApp凭借其简洁、易用的特点&#xff0c;成为了全球广受欢迎的通讯应用之一&#xff0c;除了基础的聊天功能&#xff0c;WhatsApp还提供了一系列辅助工具功能&#xf…

关于 ls -s 输出文件大小的单位问题的讨论

自己看书正好看到这里&#xff0c;正纳闷呢&#xff0c;上网查了下&#xff0c;发现不是我自己在为这个问题感到困惑。 有个大哥提出一个问题&#xff1a; 问题标题&#xff1a; ls -s的单位到底是什么&#xff1f; man ls -s, --size print the alloca…

web前端之css变量的妙用、通过JavaScrip改变css文件中的属性值、querySelector、setProperty

MENU 效果图htmlJavaScripstylequerySelectorsetProperty 效果图 html <div id"idBox" class"p_r w_680 h_160 b_1s_red"><div id"idItem" class"p_a l_0 t_30 w_100 h_100 bc_rgba_255_00_05 radius_50_"></div> …

matlab 基于卡尔曼滤波的GPS-INS的数据融合的导航

1、内容简介 略 25-可以交流、咨询、答疑 2、内容说明 基于卡尔曼滤波的GPS-INS的数据融合的导航 "基于卡尔曼滤波的GPS-INS的数据融合的导航 基于卡尔曼滤波实现GPS-INS组合导航系统" 卡尔曼滤波、GPS、INS、数据融合、导航 3、仿真分析 4、参考论文 略 …

YOLOv8-Seg改进:SENetV2,squeeze和excitation全面升级,效果优于SENet | 2023年11月最新成果

🚀🚀🚀本文改进: SENetV2,squeeze和excitation全面升级,作为注意力机制引入到YOLOv8,放入不同网络位置实现涨点 🚀🚀🚀YOLOv8-seg创新专栏:http://t.csdnimg.cn/KLSdv 学姐带你学习YOLOv8,从入门到创新,轻轻松松搞定科研; 1)手把手教你如何训练YOLOv8-s…

FTP协议详解

一&#xff1a;概述 FTP协议是一个用于在计算机网络上在客户端和服务器之间进行文件传输的应用层协议&#xff0c;其英文全称是File Transfer Protocol。是TCP/IP协议簇中的重要一员。客户端和服务器按照FTP的协议规范进行通讯来实现文件传输。 对于FTP服务器好理解&#xff0c…

后仿真 ERROR

后仿真 error ERROR (SFE-23): "input.scs" 252: The instance _57_D32_noxref is referencing an undefined model or subcircuit, parasitic_nwd. Either include the file containing the definition of parasitic_nwd, or define parasitic_nwd before running t…

Adobe ColdFusion文件读取漏洞(CVE-2010-2861)

任务一&#xff1a; 复现漏洞 任务二&#xff1a; 尝试利用漏洞读取目标系统中的“opt/coldfusion8/license.txt"文件 1.环境搭建&#xff08;网上写的密码是admin&#xff0c;就用admin&#xff09; 2.看答案就是一层一层进行路径穿越攻击&#xff0c;这里要注意如果…

Vue+Element-ui实例_在form中动态校验tag标签

1.开发需求 在日常开发中&#xff0c;我们会遇到form表单的动态添加和校验&#xff0c;当我们需要在动态添加的内容中再次动态使用输入框的时候&#xff0c;就会变得很繁琐&#xff0c;我在网上找了很多案例&#xff0c;没有符合自己需求的内容&#xff0c;只好闲暇时间自己搞…

实现优雅的自增枚举类:Python中的枚举与自增技巧

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 枚举类型在编程中扮演着重要的角色&#xff0c;它们为变量赋予了更加清晰的含义。然而&#xff0c;在Python中&#xff0c;实现自增的枚举类并非直接而简单的任务。本文将深入讨论如何通过不同的方式优雅地实现自…

matlab 混沌动力学行为-分岔图-李雅普指数等

1、内容简介 略 24-可以交流、咨询、答疑 2、内容说明 混沌动力学行为-分岔图-李雅普指数等 包含各种类型的混沌模型求解&#xff0c;包含其分叉图、李雅普指数等 混沌、分叉图、李雅普指数 3、仿真分析 略 4、参考论文 略 链接&#xff1a;https://pan.baidu.com/…