Qt: пишем калькулятор
Установив и настроив QT, реализуем что-нибудь поинтереснее HelloWorld'а. Для этого после запуска QT Creator кликнем меню Файл, "Новый файл или проект", шаблон "Приложение Qt Widgets". Назовём проект Calculator и выберем папку для его хранения. Нам не понадобятся файлы mainwindow.cpp
и mainwindow.h
, их можно исключить из проекта, щёлкнув правой кнопкой мыши на имени файла в окне проектов и выбрав "Удалить". В этом же окне щёлкнем правой кнопкой мыши на имени проекта и скажем "Добавить новый...":
Добавление класса в проект Qt
Далее выбираем "Класс C++", жмём кнопку Выбрать..., вводим следующие данные:
Имя класса: Calculator
Базовый класс: QWidget
Тип класса: производный от QWidget
Путь: проверяем, что указана папка, где лежат остальные исходники.
Жмём "Далее, в следующем окне можно просто нажать "Завершить".
Текст модуля main.cpp
немного изменится по отношению к сгенерированному системой:
#include "calculator.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication app(argc, argv); Calculator calculator; calculator.setWindowTitle("Calculator"); calculator.resize(230,200); calculator.setFixedSize(300,250); calculator.show(); return app.exec(); }
Обратите внимание на установку и фиксацию размеров окна приложения.
В заголовочном файле класса калькулятора calculator.h
опишем свойства и прототипы функций калькулятора:
#ifndef _Calculator_h_ #define _Calculator_h_ #include <QWidget> #include <QStack> #include <QLabel> #include <QPushButton> #include <QGridLayout> class Calculator : public QWidget { Q_OBJECT //макрос, нужен в начале всех наших классов private: QLabel *displaystring; QStack <QString> stack; public: Calculator (QWidget* pwgt = 0); QPushButton* createButton (const QString& str); void calculate (); public slots: //Общедоступные обработчики событий void slotButtonClicked (); }; #endif
В метку displaystring
будем выводить результаты вычислений (и показывать там числа/операции в процессе их набора), для хранения двух чисел и выбранной над ними операции используем реализованный в QT стек (QStack <QString> stack;
). Единственный конструктор класса может быть вызван с параметром-указателем на родительский виджет или без него. Метод createButton
будет создавать одну кнопку с указанной параметром подписью, метод calculate
займётся вычислением, а единственный обработчик событий (слот) калькулятора с именем slotButtonClicked
будет выполнять всю основную работу по реагированию на нажатия кнопок.
Осталось написать файл calculator.cpp
. Конструктор создаст в окне виджета метку displaystring
и набор кнопок, заодно продемонстрировав современный подход к логическому проектированию интерфейсов "как в Java", то есть, на основе относительного позиционирования в компоненте типа QGridLayout
.
Нам понадобится "сетка" из ячеек для метки и кнопок размерностью 6 строк на 4 столбца, например, такое размещение кнопок ничем не хуже других:
План размещения кнопок с помощью QGridLayout
Вот текст конструктора калькулятора, реализующего данную конфигурацию интерфейса (здесь и далее пишем файл Calculator.cpp
):
#include "calculator.h" Calculator::Calculator (QWidget *parent) : QWidget(parent) { displaystring = new QLabel(""); displaystring->setMinimumSize (150, 50); QChar aButtons[4][4] = { {'7', '8', '9', '/'}, {'4', '5', '6', '*'}, {'1', '2', '3', '-'}, {'0', '.', '=', '+'} }; QGridLayout *myLayout = new QGridLayout; myLayout->addWidget(displaystring, 0, 0, 1, 4); myLayout->addWidget(createButton("CE"), 1, 3); for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { myLayout->addWidget(createButton(aButtons[i][j]), i + 2, j); } } setLayout(myLayout); }
Служебный метод createButton
создаёт одну кнопку и назначает ей обработчиком нажатия (стандартного сигнала "clicked()
") метод (слот) с именем slotButtonClicked(). Если пока не знакомы с сигналами и слотами, считайте для простоты, что это события и их обработчики, как в любом другом ООП:
QPushButton* Calculator::createButton (const QString& str) { QPushButton* pcmd = new QPushButton(str); pcmd->setMinimumSize(40, 40); connect(pcmd, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); return pcmd; }
Метод calculate
будет очень прост, ведь мы собираемся ограничиться стеком из 3 элементов, а вычисление выполнять либо по нажатию "=", либо когда выбрана следующая операция, например, при наборе 5+3*6, как только нажата "*" вычислим "5+3" и вернём в стек "8" и "*" для последующего умножения на 6 или другое число. Это избавит нас от необходимости возиться со скобками и учитывать старшинство операций. Также в методе не проверяется деление на ноль и т.п., в этом плане даже новичок легко улучшит его самостоятельно:
void Calculator::calculate() { double dOperand2 = stack.pop().toDouble(); QString strOperation = stack.pop(); double dOperand1 = stack.pop().toDouble(); double dResult = 0; if (strOperation == "+") { dResult = dOperand1 + dOperand2; } else if (strOperation == "-") { dResult = dOperand1 - dOperand2; } else if (strOperation == "/") { dResult = dOperand1 / dOperand2; } else if (strOperation == "*") { dResult = dOperand1 * dOperand2; } displaystring->setText(QString("%1").arg(dResult, 0, 'f', 3)); }
В последнем операторе результат выводится как вещественное число с 3 знаками после запятой, при необходимости измените.
Наконец, реализуем метод slotButtonClicked
, реагирующий на нажатия кнопок и управляющий стеком:
void Calculator::slotButtonClicked() { QString str = ((QPushButton*)sender())->text(); //Получаем текст с нажатой кнопки if (str == "CE") { //Кнопка Очистить stack.clear(); displaystring->setText(""); return; } QString text = displaystring->text(); //отображаемый текст int len = text.length(); QString last = ""; if (len>0) last = text.right(1); //самый правый символ ввода if (((len==0 && stack.count()==0) || ((stack.count()==2 && len>1 && (last=="+"||last=="-"||last=="*"||last=="/")))) && (str.contains(QRegExp("[0-9]")) || str=="-")) { //На экране пусто и стек пуст или введен 1-й операнд и операция //и при этом нажата цифра или "-" text=str; //Стереть то, что было отображено, и отобразить нажатый символ } else if ((text+str).contains(QRegExp("^-?[0-9]+\\.?[0-9]*$"))) { text+=str; //Пока вводим число - добавлять символ } else if (text.contains(QRegExp("^-?[0-9]+\\.?[0-9]*$"))) { //Уже набрано число if (str=="*"||str=="/"||str=="+"||str=="-"||str=="=") { //Вычислить if (stack.count()==2) { //Есть 1-й операнд и число stack.push(text); //Положить в стек 2-й операнд calculate(); //Вычислить text=displaystring->text(); //Показать результат } if (str!="=") { //Для вычисления "по цепочке" stack.push(text); //Положить в стек 1-й операнд text+=str; //Отобразить операцию до след.нажатия кнопки stack.push(str); //Положить в стек операцию } } } displaystring->setText(text); }
Здесь мы не делаем никакого "лишнего" контроля, так что если нажать "2", "+", "3", "=", получим результат "5.000" и либо сможем дальше вычислять по цепочке ("+", "1"), либо вводимые цифры будут дописываться к числу :) Это легко исправить, введя в класс калькулятора, например, дополнительный флажок, показывающий нужно ли очищать поле ввода после нажатия следующей кнопки.
Для проверки того, вводится ли допустимое число, использован самый простой и естественный путь - регулярное выражение, полученное с помощью встроенного класса QRegExp
.
Вот что у нас вышло:
Вид полученного приложения
Попробуйте воспроизвести этот пример - и минимальный опыт программирования в Qt у Вас появится :) Исходники калькулятора помощнее и с более стандартным видом - здесь.
Попробуй сделать сам
1. Добавить под кнопкой ”C” 4 кнопки для вычисления функций, например, синуса, косинуса, степени и натурального логарифма с помощью методов qSin
, qCos
, qPow
, qLn
:
#include <QTCore/qmath.h>
и реализовать их функционал. Как вариант, можно использовать стандартные функции Си из библиотеки math.h
.
2. Добавить обработку нажатий клавиш с отображением введённых символов в метке QLabel
. Для этого включить в класс Calculator.h
прототип метода для обработки нажатий клавиш:
protected: virtual void keyPressEvent(QKeyEvent *event);
и подключить библиотеки
#include <QKeyEvent> #include <Qt>
Реализовать в Calculator.cpp
код метода:
void Calculator::keyPressEvent(QKeyEvent *event) { int key=event->key();//event->key() - целочисленный код клавиши if (key>=Qt::Key_0 && key<=Qt::Key_9) { //Цифровые клавиши 0..9 QString str = QString(QChar(key)); displaystring->setText(displaystring->text()+str); } }
P.S. Здесь только набросок, а не решение!
Версия с дополнительной меткой, куда выводятся операнды, и обработкой нажатий клавиш
Код тоже учебный :) Вот изменённый calculator.h
:
#ifndef _Calculator_h_ #define _Calculator_h_ #include <QWidget> #include <QStack> #include <QLabel> #include <QPushButton> #include <QGridLayout> #include <QTCore/qmath.h> #include <QKeyEvent> #include <Qt> class Calculator : public QWidget { Q_OBJECT private: QLabel *firststring; //метка для вывода предыдущего операнда или операции QLabel *displaystring; QStack <QString> stack; protected: virtual void keyPressEvent(QKeyEvent *event); //Класс будет обрабатывать нажатия клавиш! public: Calculator (QWidget* pwgt = 0); QPushButton* createButton (const QString& str); void clearAll (); //Очистить всё void input(QString); //Обработка нажатия кнопки или клавиши void calculate (); //Вычисление public slots: void slotButtonClicked (); }; #endif
Изменённый calculator.cpp
:
#include "calculator.h" Calculator::Calculator (QWidget *parent) : QWidget(parent) { firststring = new QLabel(""); firststring->setMinimumSize (150, 50); displaystring = new QLabel(""); displaystring->setMinimumSize (150, 50); displaystring->setFont(QFont("sans", 14, QFont::Bold)); QChar aButtons[4][4] = { {'7', '8', '9', '/'}, {'4', '5', '6', '*'}, {'1', '2', '3', '-'}, {'0', '.', '=', '+'} }; QGridLayout *myLayout = new QGridLayout; myLayout->addWidget(firststring, 0, 0, 1, 4); myLayout->addWidget(displaystring, 1, 0, 1, 4); myLayout->addWidget(createButton("CE"), 2, 3); for (int i = 0; i < 4; ++i) { for (int j = 0; j < 4; ++j) { myLayout->addWidget(createButton(aButtons[i][j]), i + 3, j); } } setLayout(myLayout); } QPushButton* Calculator::createButton (const QString& str) { QPushButton* pcmd = new QPushButton(str); pcmd->setMinimumSize(40, 30); connect(pcmd, SIGNAL(clicked()), this, SLOT(slotButtonClicked())); return pcmd; } void Calculator::calculate() { double dOperand2 = stack.pop().toDouble(); QString strOperation = stack.pop(); double dOperand1 = stack.pop().toDouble(); double dResult = 0; if (strOperation == "+") { dResult = dOperand1 + dOperand2; } else if (strOperation == "-") { dResult = dOperand1 - dOperand2; } else if (strOperation == "/") { dResult = dOperand1 / dOperand2; } else if (strOperation == "*") { dResult = dOperand1 * dOperand2; } displaystring->setText(QString("%1").arg(dResult, 0, 'f', 3)); } void Calculator::input(QString str) { QString text = displaystring->text(); //отображаемый текст int len = text.length(); QString last = ""; if (len>0) last = text.right(1); //самый правый символ ввода if (((len==0 && stack.count()==0) || ((stack.count()==2 && len>1 && (last=="+"||last=="-"||last=="*"||last=="/")))) && (str.contains(QRegExp("[0-9]")) || str=="-")) { //На экране пусто и стек пуст или введен 1-й операнд и операция //и при этом нажата цифра или "-" firststring->setText(text); text=str; //Стереть то, что было отображено, и отобразить нажатый символ } else if ((text+str).contains(QRegExp("^-?[0-9]+\\.?[0-9]*$"))) { text+=str; //Пока вводим число - добавлять символ } else if (text.contains(QRegExp("^-?[0-9]+\\.?[0-9]*$"))) { //Уже набрано число if (str=="*"||str=="/"||str=="+"||str=="-"||str=="=") { //Вычислить if (stack.count()==2) { //Есть 1-й операнд и число stack.push(text); //Положить в стек 2-й операнд firststring->setText(firststring->text()+text+"="); //отобразить вычисляемое выражение calculate(); //Вычислить text=displaystring->text(); //Показать результат } if (str!="=") { //Для вычисления "по цепочке" stack.push(text); //Положить в стек 1-й операнд text+=str; //Отобразить операцию до след.нажатия кнопки stack.push(str); //Положить в стек операцию firststring->setText(text); } } } displaystring->setText(text); } void Calculator::clearAll() { stack.clear(); displaystring->setText(""); firststring->setText(""); } void Calculator::slotButtonClicked() { QString str = ((QPushButton*)sender())->text(); //Получаем текст с нажатой кнопки if (str == "CE") clearAll(); else input(str); } void Calculator::keyPressEvent(QKeyEvent *event) { int key=event->key();//event->key() - целочисленный код клавиши QString str = QString(QChar(key)); if (key==Qt::Key_Delete) clearAll(); //Так проверять коды клавиш else input(str); }
P.S. Замена QRegExp на QRegularExpression в Qt6.
Qt6: пишем калькулятор (файл .pdf) (263 Кб)
Скачать проект Qt6 из этой статьи в архиве .zip, развернуть в новую папку проекта (3 Кб)
25.04.2014, 19:19 [49769 просмотров]