Шаг 32.
Библиотека Qt.
Пример создания калькулятора

    На этом шаге рассмотрим на примере создание калькулятора.

    Приложение, выполнение которого показано на рис. 1, демонстрирует применение табличного размещения на примере калькулятора.


Рис.1. Вид окна приложения

Структура файла main.cpp приложения:

#include <QApplication>
#include "Calculator.h"

int main(int argc, char** argv)
{
    QApplication app(argc, argv);
    Calculator   calculator;

    calculator.setWindowTitle("Калькулятор");
    calculator.resize(230, 200);

    calculator.show();

    return app.exec();
}

    В файле main.cpp создается виджет калькулятора calculator (класс Calculator описан в файле Calculator.h). После изменения размера методом resize() вызов метода show() отображает калькулятор на экране.

    Приведем описание файла Calculator.h, в котором определен класс Calculator.

#ifndef _Calculator_h_
#define _Calculator_h_

#include <QWidget>
#include <QStack>

class QLCDNumber;
class QPushButton;

class Calculator : public QWidget {
    Q_OBJECT
private:
    QLCDNumber*     m_plcd;
    QStack<QString> m_stk;
    QString         m_strDisplay;

public:
    Calculator(QWidget* pwgt = 0);

    QPushButton* createButton(const QString& str);
    void         calculate   (                  );

public slots:
    void slotButtonClicked();
};

#endif  //_Calculator_h_

    В определении класса Calculator, описываются атрибуты:

  1. m_plcd — указатель на виджет электронного индикатора,
  2. bm_stk — стек для проведения операций вычисления,
  3. m_strDisplay — строка, в которую мы будем записывать символы нажатых пользователем кнопок,
  4. метод createButton() предназначен для создания кнопок калькулятора,
  5. метод calculate() — для вычисления выражений, находящихся в стеке m_stk,
  6. слот slotButtonClicked() вызывается при нажатии на любую из кнопок калькулятора.
Стек QStack<T>

    Стек реализует структуру данных, работающую по принципу LIFO (Last In First Out, последним пришел — первым ушел), т. е. из стека первым удаляется элемент, который был вставлен позже всех остальных.

    Класс QStack<T> представляет собой реализацию стековой структуры. Этот класс унаследован от класса QVector<T>. Процесс помещения элементов в стек обычно называется проталкиванием (pushing), а извлечение из него верхнего объекта — выталкиванием (poping).

    Каждая операция проталкивания увеличивает размер стека на 1, а каждая операция выталкивания — уменьшает на 1. Для этих операций в классе QStack<T> определены методы push() и pop(). Метод top() возвращает ссылку на элемент вершины стека.

    Электронный индикатор

    Класс QLCDNumber виджета электронного индикатора определен в заголовочном файле QLCDNumber. По внешнему виду этот виджет представляет собой набор сегментных указателей как, например, на электронных часах. С помощью этого виджета отображаются целые числа. Допускается использование точки, которую можно отображать между позициями сегментов или как отдельный символ, вызывая метод setSmallDecimalPoint() и передавая в него true или false соответственно. Количество отображаемых сегментов можно задать в конструкторе или с помощью метода setDigitCount(). В том случае, когда для отображения числа не хватает сегментов индикатора, отсылается сигнал overflow().

    По умолчанию стиль электронного индикатора соответствует стилю Outline, но его можно изменить, передав методу setSegmentStyle() одно из следующих значений: QLCDNumber::Outline, QLCDNumber::Filled или QLCDNumber::Flat. В табл. 1 приводится внешний вид виджета для каждого из перечисленных стилей.

Таблица 1. Стили электронного индикатора
Константа
Внешний вид
Outline
Flat
Filled

    Электронный индикатор можно включать в режиме отображения двоичной, восьмеричной, десятеричной или шестнадцатеричной систем счисления. Режим отображения изменяется с помощью метода setMode(), в который передается одно из следующих значений: QLCDNumber::Bin (двоичная), QLCDNumber::Oct (восьмеричная), QLCDNumber::Dec (десятичная) или QLCDNumber::Hex (шестнадцатеричная). Также для смены режима отображения можно воспользоваться слотами setBinMode(), setOctMode(), setDecMode() и setHexMode() соответственно.

    Рассмотрим файл calculator.cpp, в котором приведено описание всех определенных в классе функций.

    Сначала приведем описание конструктора:

Calculator::Calculator(QWidget* pwgt/*= 0*/) : QWidget(pwgt)
{
    m_plcd = new QLCDNumber(12);
    m_plcd->setSegmentStyle(QLCDNumber::Flat);
    m_plcd->setMinimumSize(150, 50);

    QChar aButtons[4][4] = {{'7', '8', '9', '/'},
                            {'4', '5', '6', '*'},
                            {'1', '2', '3', '-'},
                            {'0', '.', '=', '+'}
                           };

    QGridLayout* ptopLayout = new QGridLayout;
    ptopLayout->addWidget(m_plcd, 0, 0, 1, 4);
    ptopLayout->addWidget(createButton("CE"), 1, 3);

    for (int i = 0; i < 4; ++i) {
        for (int j = 0; j < 4; ++j) {
           ptopLayout->addWidget(createButton(aButtons[i][j]), i + 2, j);
        }
    }
    setLayout(ptopLayout);
}

    При создании электронного индикатора в его конструктор передается количество сегментов, равное 12. Флаг QLCDNumber::Flat, переданный в метод setSegmentStyle(), задает сегментам индикатора плоский стиль. Метод setMinimumSize() переустанавливает минимально возможные размеры виджета индикатора. В массиве aButtons определяются надписи для кнопок калькулятора. Виджет электронного индикатора помещается в компоновку вызовом метода addWidget(), первые два параметра которого задают его расположение, а последние два — количество занимаемых им строк и столбцов табличной компоновки.

    Кнопка CE, после своего создания методом createButton(), помещается в компоновку методом addWidget() на позиции (1,3) (т. е. на пересечении второй строки и четвертого столбца — они нумеруются с нуля). Все остальные виджеты кнопок создаются и помещаются в компоновку в цикле с помощью методов createButton() и addWidget().

    Рассмотрим метод для создания кнопок:

QPushButton* Calculator::createButton(const QString& str)
{
    QPushButton* pcmd = new QPushButton(str);
    pcmd->setMinimumSize(40, 40);
    connect(pcmd, SIGNAL(clicked()), SLOT(slotButtonClicked()));
    return pcmd;
}

    Метод createButton() получает строку с надписью и создает нажимающуюся кнопку. После этого вызовом метода setMinimumSize() для кнопки устанавливаются минимально возможные размеры, а сигнал clicked() соединяется со слотом slotButtonClicked() вызовом connect().

    Рассмотрим метод для реализации вычисления выражения, содержащегося в стеке:

void Calculator::calculate()
{
   double dOperand2 = m_stk.pop().toDouble();
   QString strOperation = m_stk.pop();
   double dOperand1 = m_stk.pop().toDouble();
   double dResult = 0;
   if (strOperation == "+") {
      dResult = dOperand1 + dOperand2;
   }
   if (strOperation == "-") {
      dResult = dOperand1 — dOperand2;
   }
   if (strOperation == "/") {
      dResult = dOperand1 / dOperand2;
   }
   if (strOperation == "*") {
      dResult = dOperand1 * dOperand2;
   }
   m_plcd->display(dResult);
}

    Назначение метода calculate() состоит в вычислении выражения, содержащегося в стеке m_stk. Переменная dOperand2 получает снятое с вершины стека значение, преобразованное к типу double. Строковая переменная strOperation получает символ операции. Переменная dOperand1 из стека получает последнее значение, которое также преобразуется к типу double. В операторах if символ операции сравнивается с четырьмя допустимыми и, в случае совпадения, выполняется требуемая операция, результат которой сохраняется в переменной dResult. После этого вызовом метода dislay() значение переменной dResult отображается на электронном индикаторе (указатель m_plcd).

    Осталось описать метод slotButtonClicked():

void Calculator::slotButtonClicked()
{
    QString str = ((QPushButton*)sender())->text();

    if (str == "CE") {
        m_stk.clear();
        m_strDisplay = "";
        m_plcd->display("0");
        return;
    }
    if (str.contains(QRegExp("[0-9]"))) {
        m_strDisplay += str;
        m_plcd->display(m_strDisplay.toDouble());
    }
    else if (str == ".") {
        m_strDisplay += str;
        m_plcd->display(m_strDisplay);
    }
    else {
        if (m_stk.count() >= 2) {
            m_stk.push(QString().setNum(m_plcd->value()));
            calculate();
            m_stk.clear();
            m_stk.push(QString().setNum(m_plcd->value()));
            if (str != "=") {
                m_stk.push(str);
            }
        }
        else {
            m_stk.push(QString().setNum(m_plcd->value()));
            m_stk.push(str);

            m_strDisplay = "";
            m_plcd->display("0");
        }
    }
}

    В слоте slotButtonClicked() осуществляется преобразование виджета, выславшего сигнал, к типу QPushButton, после чего переменной str присваивается текст надписи на кнопке.

    Если надпись равна "CE", то выполняется операция сброса — очистка стека и установка значения индикатора в 0.

    Если была нажата цифра или точка, то выполняется ее добавление в конец строки m_strDisplay, она отображается индикатором с последующей актуализацией.

    При нажатии любой другой кнопки мы считаем, что была нажата кнопка операции. Если в стеке находится менее двух элементов, то отображаемое число и операция заносятся в стек. Иначе в стек заносится отображаемое значение и вызывается метод calculate() для вычисления находящегося в стеке выражения.

    После этого стек очищается с помощью метода clear() и в него записывается значение результата, отображаемое индикатором, и следующая операция.

    Если выполняется операция "=", то она не будет добавляться в стек.

    Файлы примера можно взять здесь.

    На следующем шаге рассмотрим порядок следования табуляции.




Предыдущий шаг Содержание Следующий шаг