Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 6 - Jednoduchá kalkulačka v Qt a C++ - Model

V minulé lekci, Jednoduchá kalkulačka v Qt a C++ - Layout, jsme si slíbili, že si k naší kalkulačce vytvoříme business logiku - model a přidáme nějakou funkčnost, aby se z návrhu formuláře kalkulačky opravdu stala funkční kalkulačkou.

Model

V modelu se bude nacházet velmi "jednoduchá" logika pro základní funkce kalkulačky. V podstatě potřebujeme jen předat nějaká data - v tomto případě dva textové řetězce, typ operace a druh aktuální číselné soustavy. Řetězce je třeba dále převést na celá čísla a testovat, zda uživatel nezadal nějaký nesmysl - tedy učinit program "relativně blbuvzdorný". I když podle zákonů pana Murphyho to je asi nemožné :) Nakonec to chce nějakou metodu, která nám vrátí výsledek dané operace.

Naše okno, tedy "controller", jen předá data a model se postará o všechny kontroly a výsledek předá zpět, pokud o to bude požádán.

Do projektu si přidáme novou třídu, např. Model, odvozenou od QObject. Zděděním QObject získáme možnost používat sloty a signály. Tvůrci Qt tento způsob komunikace doporučují a používají ve svých modelech, obvykle odvozených od třídy QAbstractItemModem. Myslím, že to už by byl kanón na vrabce a jak pravil A. Einstein - Udělej to tak jednoduše, jak to jen jde. Ne víc ani míň. (parafráze)

Tedy pro předání dat použijeme settery, při chybách budeme posílat signály a výsledek předáme zpět "getterem".

Přidání třídy do projektu

Pravým tlačítkem myši klikneme do Qt průzkumníku souborů projektu a z kontextového menu zvolíme přidat nový. Vybereme soubor C++ a typ C++ Class:

Přidání nové C++ třídy do Qt projektu - Qt - Okenní/formulářové aplikace v C++

Soubor pojmenujeme Model, nastavíme Base class na QObject a zaškrtneme možnost Include QObject:

Vytvoření modelu v Qt aplikaci v C++ - Qt - Okenní/formulářové aplikace v C++

model.h

Hlavičkový soubor nového modelu bude mít následující kód, který si hned popíšeme:

#ifndef MODEL_H
#define MODEL_H

#include <QObject>
#include <QString>

class Model : public QObject
{
    Q_OBJECT
public:
    explicit Model(QObject *parent = nullptr);

    static const int OP_PLUS    = 0x00;
    static const int OP_MINUS   = 0x01;
    static const int OP_TIMES   = 0x02;
    static const int OP_DIVIDE  = 0x03;

    static const int SYS_HEX    = 0x04;
    static const int SYS_DEC    = 0x05;
    static const int SYS_OCT    = 0x06;
    static const int SYS_BIN    = 0x07;

    static const int OP_LEFT    = 0x08;
    static const int OP_RIGHT   = 0x09;


private:
    int opLeft;
    int opRight;

    int operation;
    int numBasis;

    bool isLeftOk;
    bool isRightOk;

public:
    void setLeft(QString value);
    void setRight(QString value);
    void setOperator(int operation);
    void setNumBasis(int numBasis);

    int calculate();

private:
    int numberTest(QString number, bool *isOk);

signals:
    void operandFalse(int operand);
    void zeroDivide();

public slots:
};

#endif // MODEL_H

Co jsme to tedy vlastně stvořili v hlavičkovém souboru?

  • Zcela na začátek bylo třeba vložit hlavičkový soubor knihovny QString
  • Přidali jsme několik statických konstant, které zastupují jednotlivé operace. Alternativně by šlo také použít výčtového typu. Kdybychom použili konstanty (vlastně makra) druhu `#define C_NAME (VALUE) `, tak ty pak nemají přímou kontrolu nad typem.
  • Další sada konstant určuje číselnou soustavu.
  • Následně jsme deklarovali několik privátních atributů, které zastupují operandy (vstupní čísla) a operaci: int leftOperand.... Též jsme definovali aktuálně používanou soustavu. Velmi důležité jsou logické atributy bool isLeftOk..., které nám budou detekovat chyby při zadání. Sice vyvolají jen neurčitou chybu, ale budeme vědět, že něco bylo špatně.
  • Poté bylo třeba uveřejnit několik set/get metod k nastavení atributů tříd: void setLeft(QString value)..., int getLeft()...
  • Deklarujeme metodu, která dovede vrátit výsledek operace: int calculate().
  • Abychom zbytečně nepsali stejný kód na test správnosti vstupu, napíšeme si jednu funkci navíc: int numberTest(QString number, bool *isOk);, kde první parametr předá uživatelský vstup a druhý mám umožní vrátit případnou chybu zadání.

Uživatelské signály

Přesto, že tato část článku patří k hlavičce modelu, je její důležitost natolik významná, že si zasluhuje svůj nadpis.

Zatím jsem používali jen signály, které již někdo předefinoval. Ovšem je dost dobré vědět, že si je můžeme napsat sami a dokonce v nich předávat argumenty různých typů.

V hlavičkovém souboru nalezneme signály v bloku uvozeném klíčovým slovem signals, což je nyní synonymum pro public (dříve bylo definováno jako #define signals protected).

Jinak jejich deklarace má stejný tvar jako běžná členská funkce, ale nemusí být v souboru *.cpp definována.

My budeme posílat signál při chybném vstupu void operandFalse(int operand), kde argument určí pomocí konstanty, které číslo bylo špatně. Signál void zeroDivide() bude varovat při dělení nulou, což je v matematice nedefinovaný výsledek. Prostě se to nedělá :-)

model.cpp

Přejděme do implementačního souboru. Zde máme tedy výkonnou část modelu naší kalkulačky.

Konstruktor

Konstruktor by nám měl preventivně nastavit atributy na výchozí hodnoty, aby nedocházelo k nečekaným chybám. Výchozí situace po vytvoření modelu bude:

  • Žádný operand (číslo) nebyl zadán.
  • Operace je defaultně sčítání.
  • Číselná soustava je dekadická.
  • Nedošlo k žádné chybě.

Kód konstruktoru je následující:

Model::Model(QObject *parent) : QObject(parent)
{
    opLeft = 0;
    opRight = 0;
    operation = Model::OP_PLUS;
    numBasis = Model::SYS_DEC;
    isLeftOk = false;
    isRightOk = false;
}

Settery

Settery operandů využijí členských funkcí třídy QString a převedou řetězec na celé číslo. Zároveň proběhne test správnosti vstupu metodou numberTest(), kterou dodáme později. Výsledek testu si uložíme pro pozdější použití:

void Model::setLeft(QString value)
{
    opLeft = numberTest(value, &isLeftOk);
}

void Model::setRight(QString value)
{
    opRight = numberTest(value, &isRightOk);
}

Následně si nastavíme typ operace a základ číselné soustavy. Argumentem bude jedna z konstant modelu. Metody jsou velmi triviální a podtržnítko u názvu argumentu je jen snadná finta, jak používat stejné názvy a vyhnout se ukazatelům (pointerům):

void Model::setOperator(int _operation)
{
    operation = _operation;
}

void Model::setNumBasis(int _numBasis)
{
    numBasis = _numBasis;
}

Test vstupů

Podívejme se na funkci pro test vstupu. Předáme jí řetězec, který by měl představovat číslo, a referenci na atribut výsledku testu. Podle aktuální číselné soustavy zajistíme převod textu na číslo. Např. pro šestnáctkovou soustavu řádek retVal = number.toInt(isOk, 16) znamená: text převeď na celé číslo o základu 16 a pokud vše je v pořádku, do isOk ulož true, jinak false.

Hodnotu potom metoda vrátí a to i v případě, že došlo k chybě (tehdy vrací 0):

int Model::numberTest(QString number, bool *isOk)
{
    int retVal = 0;
    switch (numBasis) {
    case SYS_HEX:
        retVal = number.toInt(isOk, 16);
        break;
    case SYS_DEC:
        retVal = number.toInt(isOk, 10);
        break;
    case SYS_OCT:
        retVal = number.toInt(isOk, 8);
        break;
    case SYS_BIN:
        retVal = number.toInt(isOk, 2);
        break;
    }
    return retVal;
}

Výpočet

Pro samotný výpočet použijeme jednoduchou funkci obsahující jeden přepínač switch, který podle typu operace provede požadovanou akci:

int Model::calculate()
{
   if (!isRightOk) {
        emit operandFalse(OP_RIGHT);
        return 0;
    }

    if (!isLeftOk) {
        emit operandFalse(OP_LEFT);
        return 0;
    }

    if (opRight == 0 && operation == OP_DIVIDE) {
        emit zeroDivide();
        return 0;
    }

    switch (operation) {
    case OP_PLUS:
        return opLeft + opRight;
        break;
    case OP_MINUS:
        return opLeft - opRight;
        break;
    case OP_TIMES:
        return opLeft * opRight;
        break;
    case OP_DIVIDE:
        return opLeft / opRight;
        break;
    default:
        return 0;
    }
}

Nejdříve provedeme několik testů. Předně pokud je chybný některý z operandů, odešleme signál pomocí příkazu emit, který z nich to byl. Pro případ, že dojde k pokusu dělení nulou, opět pošleme signál. Při chybě automaticky vracíme výsledek 0:

V příští lekci, Jednoduchá kalkulačka v Qt a C++ - Dokončení, provedeme úpravy hlavního okna a tím kalkulačku zprovozníme.


 

Předchozí článek
Jednoduchá kalkulačka v Qt a C++ - Layout
Všechny články v sekci
Qt - Okenní/formulářové aplikace v C++
Přeskočit článek
(nedoporučujeme)
Jednoduchá kalkulačka v Qt a C++ - Dokončení
Článek pro vás napsal Virlupus
Avatar
Uživatelské hodnocení:
9 hlasů
Autor se věnuje webovým aplikacím, skladově-účetnímu softwaru, 3D grafice, lexiální analýze a parserování. Studuje fyziku na MFF UK. Učil IT na střední škole.
Aktivity