6. diel - Jednoduchá kalkulačka v Qt a C ++ - Model
V minulej lekcii, Jednoduchá kalkulačka v Qt a C ++ - Layout , sme si sľúbili, že si k našej kalkulačke vytvoríme business logiku - model a pridáme nejakú funkčnosť, aby sa z návrhu formulára kalkulačky naozaj stala funkčné kalkulačkou.
Model
V modeli sa bude nachádzať veľmi "jednoduchá" logika pre základné funkcie kalkulačky. V podstate potrebujeme len odovzdať nejaké dáta - v tomto prípade dva textové reťazce, typ operácie a druh aktuálnej číselnej sústavy. Reťazca je potrebné ďalej previesť na celé čísla a testovať, či používateľ nezadal nejaký nezmysel - teda urobiť program "relatívne blbuvzdorný". Aj keď podľa zákonov pána Murphyho to je asi nemožné:) Nakoniec to chce nejakú metódu, ktorá nám vráti výsledok danej operácie.
Naše okno, teda "controller", len odovzdá dáta a model sa postará o všetky kontroly a výsledok odovzdá späť, ak o to bude požiadaný.
Do projektu si pridáme novú triedu, napr. Model
, odvodenú od
QObject
.
Zdedením QObject
získame možnosť používať sloty a
signály. Tvorcovia Qt tento spôsob komunikácie odporúčajú a
používajú vo svojich modeloch, zvyčajne odvodených od triedy QAbstractItemModem
.
Myslím, že to už by bol kanón na vrabce a ako povedal A. Einstein - Urob
to tak jednoducho, ako to len ide. Nie viac ani menej. (Parafráza)
Teda pre odovzdanie dát použijeme Setter, pri chybách budeme posielať signály a výsledok odovzdáme späť "Getter".
Pridanie triedy do projektu
Pravým tlačidlom myši klikneme do Qt prieskumníkovi súborov projektu az
kontextového menu zvolíme pridať nový. Vyberieme súbor
C++
a typ C++ Class
:
Súbor pomenujeme Model
, nastavíme Base class na
QObject
a zaškrtneme možnosť Include QObject:
Model.h
Hlavičkový súbor nového modelu bude mať nasledujúci kód, ktorý si hneď 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
Čo sme to teda vlastne stvorili v hlavičkovom súbore?
- Úplne na začiatok bolo treba vložiť hlavičkový súbor knižnice
QString
- Pridali sme niekoľko statických konštánt, ktoré zastupujú jednotlivé
operácie. Alternatívne by šlo tiež použiť vymenovaného typu.
Keby sme použili konštanty (vlastne makrá) druhu
#define C_NAME (VALUE)
, tak tie potom nemajú priamu kontrolu nad typom. - Ďalšie sada konštánt určuje číselnú sústavu.
- Následne sme deklarovali niekoľko privátnych atribútov, ktoré
zastupujú operandmi (vstupné čísla) a operáciu:
int leftOperand...
. Tiež sme definovali aktuálne používanú sústavu. Veľmi dôležité sú logické atribútybool isLeftOk
..., ktoré nám budú detekovať chyby pri zadaní. Síce vyvolajú len neurčitú chybu, ale budeme vedieť, že niečo bolo zle. - Potom bolo treba uverejniť niekoľko sto / get metód k nastavenie
atribútov tried:
void setLeft(QString value)
...,int getLeft()
... - Deklarujeme metódu, ktorá dokáže vrátiť výsledok operácie:
int calculate()
. - Aby sme zbytočne nepísali rovnaký kód na test správnosti vstupe,
napíšeme si jednu funkciu navyše:
int numberTest(QString number, bool *isOk);
, Kde prvý parameter odovzdá užívateľský vstup a druhý mám umožní vrátiť prípadnú chybu zadania.
Užívateľské signály
Napriek tomu, že táto časť článku patrí k hlavičke modelu, je jej dôležitosť natoľko významná, že si zasluhuje svoj nadpis.
Zatiaľ som používali len signály, ktoré už niekto nanovo vymedzil. Ovšem je dosť dobré vedieť, že si ich môžeme napísať sami a dokonca v nich odovzdávať argumenty rôznych typov.
V hlavičkovom súbore nájdeme signály v bloku začínajúce kľúčovým
slovom signals
, čo je teraz synonymum pre public
(predtým bolo definované ako #define signals protected
).
Inak ich deklarácia má rovnaký tvar ako bežná členská funkcia, ale
nemusí byť v súbore *.cpp
definovaná.
My budeme posielať signál pri chybnom vstupe
void operandFalse(int operand)
, kde argument určí pomocou
konštanty, ktoré číslo bolo zle. Signál void zeroDivide()
bude
varovať pri delení nulou, čo je v matematike nedefinovaný výsledok. Proste
sa to nerobí:-)
Model.cpp
Prejdime do implementačného súboru. Tu máme teda výkonnú časť modelu našej kalkulačky.
Konštruktor
Konštruktor by nám mal preventívne nastaviť atribúty na predvolené hodnoty, aby nedochádzalo k nečakaným chybám. Východisková situácia po vytvorení modelu bude:
- Žiadny operand (číslo) nebol zadaný.
- Operácia je defaultne sčítanie.
- Číselná sústava je dekadické.
- Nedošlo k žiadnej chybe.
Kód konstruktoru je nasledujúci:
Model::Model(QObject *parent) : QObject(parent) { opLeft = 0; opRight = 0; operation = Model::OP_PLUS; numBasis = Model::SYS_DEC; isLeftOk = false; isRightOk = false; }
Setter
Setter operandov využijú členských funkcií triedy QString
a
prevedú reťazec na celé číslo. Zároveň prebehne test správnosti vstupu
metódou numberTest()
, ktorú dodáme neskôr. Výsledok testu si
uložíme na neskoršie použitie:
void Model::setLeft(QString value) { opLeft = numberTest(value, &isLeftOk); } void Model::setRight(QString value) { opRight = numberTest(value, &isRightOk); }
Následne si nastavíme typ operácie a základ číselnej sústavy. Argumentom bude jedna z konštánt modelu. Metódy sú veľmi triviálne a podtržnítko u názvu argumentu je len jednoduchá finta, ako používať rovnaké názvy a vyhnúť sa ukazovateľom (pointerům):
void Model::setOperator(int _operation) { operation = _operation; } void Model::setNumBasis(int _numBasis) { numBasis = _numBasis; }
Test vstupov
Pozrime sa na funkciu pre test vstupu. Odovzdáme jej reťazec, ktorý by mal
predstavovať číslo, a referenciu na atribút výsledku testu. Podľa
aktuálnej číselnej sústavy zabezpečíme prevod textu na číslo. Napr. pre
šestnástkovom sústavu riadok retVal = number.toInt(isOk, 16)
znamená: text preveď na celé číslo o základe 16
a ak všetko
je v poriadku, do isOk
ulož true
, inak
false
.
Hodnotu potom metóda vráti a to aj v prípade, že došlo k chybe (vtedy
vracia 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
Pre samotný výpočet použijeme jednoduchú funkciu obsahujúce jeden
prepínač switch
, ktorý podľa druhu operácie vykoná
požadovanú akciu:
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; } }
Najskôr vykonáme niekoľko testov. Predovšetkým ak je chybný niektorý z
operandov, odošleme signál pomocou príkazu emit
, ktorý z nich
to bol. Pre prípad, že dôjde k pokusu delenie nulou, opäť pošleme signál.
Pri chybe automaticky vraciame výsledok 0
:
V budúcej lekcii, Jednoduchá kalkulačka v Qt a C ++ - Dokončenie , vykonáme úpravy hlavného okna a tým kalkulačku sprevádzkujeme.