Exemple de calculatrice
Cet exemple montre comment utiliser des signaux et des emplacements pour mettre en œuvre la fonctionnalité d'un widget de calculatrice, et comment utiliser QGridLayout pour placer des widgets enfants dans une grille.

Capture d'écran de l'exemple de calculatrice
L'exemple se compose de deux classes :
Calculatorle widget de la calculatrice, avec toutes les fonctionnalités de la calculatrice.Buttonest le widget utilisé pour chaque bouton de la calculatrice. Il dérive de QToolButton.
Nous commencerons par examiner Calculator, puis nous nous pencherons sur Button.
Définition de la classe Calculatrice
class Calculator : public QWidget { Q_OBJECT public: Calculator(QWidget *parent = nullptr); private slots: void digitClicked(); void unaryOperatorClicked(); void additiveOperatorClicked(); void multiplicativeOperatorClicked(); void equalClicked(); void pointClicked(); void changeSignClicked(); void backspaceClicked(); void clear(); void clearAll(); void clearMemory(); void readMemory(); void setMemory(); void addToMemory();
La classe Calculator fournit un widget de calculatrice simple. Elle hérite de QDialog et possède plusieurs emplacements privés associés aux boutons de la calculatrice. QObject::eventFilter La fonction () est réimplémentée pour gérer les événements de souris sur l'écran de la calculatrice.
Les boutons sont regroupés en catégories en fonction de leur comportement. Par exemple, tous les boutons de chiffres (étiquetés 0 à 9) ajoutent un chiffre à l'opérande courant. Pour ces boutons, nous connectons plusieurs boutons au même emplacement (par exemple, digitClicked()). Les catégories sont les chiffres, les opérateurs unaires (Sqrt, x², 1/x), les opérateurs additifs (+, -) et les opérateurs multiplicatifs (×, ÷). Les autres boutons ont leurs propres emplacements.
private: template<typename PointerToMemberFunction> Button *createButton(const QString &text, const PointerToMemberFunction &member); void abortOperation(); bool calculate(double rightOperand, const QString &pendingOperator);
La fonction privée createButton() est utilisée dans le cadre de la construction du widget. abortOperation() est appelée chaque fois qu'une division par zéro se produit ou qu'une opération de racine carrée est appliquée à un nombre négatif. calculate() applique un opérateur binaire (+, -, ×, ou ÷).
double sumInMemory; double sumSoFar; double factorSoFar; QString pendingAdditiveOperator; QString pendingMultiplicativeOperator; bool waitingForOperand;
Ces variables, ainsi que le contenu de l'écran de la calculatrice (a QLineEdit), codent l'état de la calculatrice :
sumInMemorycontient la valeur stockée dans la mémoire de la calculatrice (en utilisant MS, M+, ou MC).sumSoFarstocke la valeur accumulée jusqu'à présent. Lorsque l'utilisateur clique sur =,sumSoFarest recalculé et affiché à l'écran. Clear All remetsumSoFarà zéro.factorSoFarstocke une valeur temporaire lors des multiplications et des divisions.pendingAdditiveOperatorstocke le dernier opérateur additif cliqué par l'utilisateur.pendingMultiplicativeOperatormémorise le dernier opérateur multiplicatif cliqué par l'utilisateur.waitingForOperandesttruelorsque la calculatrice attend que l'utilisateur commence à taper un opérande.
Les opérateurs additifs et multiplicatifs sont traités différemment parce qu'ils ont des priorités différentes. Par exemple, 1 + 2 ÷ 3 est interprété comme 1 + (2 ÷ 3) parce que ÷ a une priorité plus élevée que +.
Le tableau ci-dessous montre l'évolution de l'état de la calculatrice lorsque l'utilisateur saisit une expression mathématique.
| Entrée de l'utilisateur | Affichage | Somme jusqu'à présent | Ajouter. Op. | Facteur jusqu'à présent | Mult. Op. | En attente de l'opérande ? |
|---|---|---|---|---|---|---|
| 0 | 0 | true | ||||
| 1 | 1 | 0 | false | |||
| 1 + | 1 | 1 | + | true | ||
| 1 + 2 | 2 | 1 | + | false | ||
| 1 + 2 ÷ | 2 | 1 | + | 2 | ÷ | true |
| 1 + 2 ÷ 3 | 3 | 1 | + | 2 | ÷ | false |
| 1 + 2 ÷ 3 - | 1.66667 | 1.66667 | - | true | ||
| 1 + 2 ÷ 3 - 4 | 4 | 1.66667 | - | false | ||
| 1 + 2 ÷ 3 - 4 = | -2.33333 | 0 | true |
Les opérateurs unaires, tels que Sqrt, ne nécessitent aucune manipulation particulière ; ils peuvent être appliqués immédiatement puisque l'opérande est déjà connu lorsque l'on clique sur le bouton de l'opérateur.
QLineEdit *display; enum { NumDigitButtons = 10 }; Button *digitButtons[NumDigitButtons]; };
Enfin, nous déclarons les variables associées à l'affichage et aux boutons utilisés pour afficher les chiffres.
Mise en œuvre de la classe Calculatrice
Calculator::Calculator(QWidget *parent) : QWidget(parent), sumInMemory(0.0), sumSoFar(0.0) , factorSoFar(0.0), waitingForOperand(true) {
Dans le constructeur, nous initialisons l'état de la calculatrice. Les variables pendingAdditiveOperator et pendingMultiplicativeOperator n'ont pas besoin d'être initialisées explicitement, car le constructeur QString les initialise à des chaînes vides. Il est également possible d'initialiser ces variables directement dans l'en-tête. Cela s'appelle member-initializaton et permet d'éviter une longue liste d'initialisation.
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);
Nous créons le QLineEdit représentant l'affichage de la calculatrice et définissons certaines de ses propriétés. En particulier, nous le configurons pour qu'il soit en lecture seule.
Nous agrandissons également la police de display de 8 points.
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);
Pour chaque bouton, nous appelons la fonction privée createButton() avec l'étiquette de texte appropriée et un emplacement à connecter au bouton.
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")); }
La mise en page est gérée par un seul QGridLayout. L'appel à QLayout::setSizeConstraint() garantit que le widget Calculator est toujours affiché dans sa taille optimale (son size hint), ce qui empêche l'utilisateur de redimensionner la calculatrice. L'indice de taille est déterminé par la taille et size policy des widgets enfants.
La plupart des widgets enfants n'occupent qu'une seule cellule de la grille. Pour ceux-ci, il suffit de transmettre une ligne et une colonne à QGridLayout::addWidget(). Les widgets display, backspaceButton, clearButton et clearAllButton occupent plus d'une colonne ; pour ceux-ci, nous devons également transmettre un intervalle de ligne et un intervalle de colonne.
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)); }
L'appui sur l'un des boutons numériques de la calculatrice émettra le signal clicked() du bouton, ce qui déclenchera l'emplacement digitClicked().
Tout d'abord, nous déterminons quel bouton a envoyé le signal à l'aide de QObject::sender(). Cette fonction renvoie l'émetteur sous la forme d'un pointeur QObject. Comme nous savons que l'expéditeur est un objet Button, nous pouvons en toute sécurité lancer le QObject. Nous aurions pu utiliser un lancer de style C ou un static_cast<>() C++, mais comme technique de programmation défensive, nous utilisons un qobject_cast(). L'avantage est que si l'objet n'a pas le bon type, un pointeur nul est renvoyé. Les plantages dus à des pointeurs nuls sont beaucoup plus faciles à diagnostiquer que les plantages dus à des castings non sûrs. Une fois que nous avons le bouton, nous extrayons l'opérateur en utilisant QToolButton::text().
Le slot doit prendre en compte deux situations en particulier. Si display contient "0" et que l'utilisateur clique sur le bouton 0, il serait idiot d'afficher "00". Et si la calculatrice est dans un état où elle attend un nouvel opérande, le nouveau chiffre est le premier chiffre de ce nouvel opérande ; dans ce cas, tout résultat d'un calcul précédent doit d'abord être effacé.
À la fin, nous ajoutons le nouveau chiffre à la valeur affichée.
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; }
Le slot unaryOperatorClicked() est appelé chaque fois que l'on clique sur l'un des boutons de l'opérateur unaire. Un pointeur sur le bouton cliqué est à nouveau récupéré à l'aide de QObject::sender(). L'opérateur est extrait du texte du bouton et stocké dans clickedOperator. L'opérande est obtenu à partir de display.
Nous effectuons ensuite l'opération. Si Sqrt est appliqué à un nombre négatif ou 1/x à zéro, nous appelons abortOperation(). Si tout se passe bien, nous affichons le résultat de l'opération dans la ligne d'édition et nous attribuons à waitingForOperand la valeur true. Cela garantit que si l'utilisateur tape un nouveau chiffre, celui-ci sera considéré comme un nouvel opérande, au lieu d'être ajouté à la valeur actuelle.
void Calculator::additiveOperatorClicked() { Button *clickedButton = qobject_cast<Button *>(sender()); if (!clickedButton) return; QString clickedOperator = clickedButton->text(); double operand = display->text().toDouble();
Le slot additiveOperatorClicked() est appelé lorsque l'utilisateur clique sur le bouton + ou -.
Avant de pouvoir agir sur l'opérateur cliqué, nous devons traiter toutes les opérations en attente. Nous commençons par les opérateurs multiplicatifs, car ils ont une priorité plus élevée que les opérateurs additifs :
if (!pendingMultiplicativeOperator.isEmpty()) { if (!calculate(operand, pendingMultiplicativeOperator)) { abortOperation(); return; } display->setText(QString::number(factorSoFar)); operand = factorSoFar; factorSoFar = 0.0; pendingMultiplicativeOperator.clear(); }
Si × ou ÷ a été cliqué plus tôt, sans cliquer ensuite sur =, la valeur actuelle de l'affichage est l'opérande droit de l'opérateur × ou ÷ et nous pouvons enfin effectuer l'opération et mettre à jour l'affichage.
if (!pendingAdditiveOperator.isEmpty()) { if (!calculate(operand, pendingAdditiveOperator)) { abortOperation(); return; } display->setText(QString::number(sumSoFar)); } else { sumSoFar = operand; }
Si + ou - a été cliqué plus tôt, sumSoFar est l'opérande gauche et la valeur actuelle affichée est l'opérande droit de l'opérateur. S'il n'y a pas d'opérateur additif en attente, sumSoFar devient simplement le texte affiché.
pendingAdditiveOperator = clickedOperator; waitingForOperand = true; }
Enfin, nous pouvons nous occuper de l'opérateur qui vient d'être cliqué. Comme nous n'avons pas encore l'opérande de droite, nous stockons l'opérateur cliqué dans la variable pendingAdditiveOperator. Nous appliquerons l'opération plus tard, lorsque nous aurons un opérande droit, avec sumSoFar comme opérande gauche.
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; }
L'emplacement multiplicativeOperatorClicked() est similaire à additiveOperatorClicked(). Nous n'avons pas besoin de nous préoccuper des opérateurs additifs en attente ici, car les opérateurs multiplicatifs ont la priorité sur les opérateurs additifs.
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; }
Comme pour additiveOperatorClicked(), nous commençons par traiter les opérateurs multiplicatifs et additifs en attente. Ensuite, nous affichons sumSoFar et remettons la variable à zéro. La remise à zéro de la variable est nécessaire pour éviter de compter la valeur deux fois.
void Calculator::pointClicked() { if (waitingForOperand) display->setText("0"); if (!display->text().contains('.')) display->setText(display->text() + tr(".")); waitingForOperand = false; }
Le slot pointClicked() ajoute un point décimal au contenu de display.
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); }
Le slot changeSignClicked() change le signe de la valeur dans display. Si la valeur actuelle est positive, nous ajoutons un signe moins ; si la valeur actuelle est négative, nous enlevons le premier caractère de la valeur (le signe moins).
void Calculator::backspaceClicked() { if (waitingForOperand) return; QString text = display->text(); text.chop(1); if (text.isEmpty()) { text = "0"; waitingForOperand = true; } display->setText(text); }
Le site backspaceClicked() supprime le caractère le plus à droite de l'affichage. Si nous obtenons une chaîne vide, nous affichons "0" et nous remplaçons waitingForOperand par true.
void Calculator::clear() { if (waitingForOperand) return; display->setText("0"); waitingForOperand = true; }
L'emplacement clear() remet l'opérande actuel à zéro. Cela équivaut à cliquer sur Backspace suffisamment de fois pour effacer tout l'opérande.
void Calculator::clearAll() { sumSoFar = 0.0; factorSoFar = 0.0; pendingAdditiveOperator.clear(); pendingMultiplicativeOperator.clear(); display->setText("0"); waitingForOperand = true; }
L'emplacement clearAll() réinitialise la calculatrice à son état initial.
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(); }
L'emplacement clearMemory() efface la somme conservée en mémoire, readMemory() affiche la somme comme opérande, setMemory() remplace la somme en mémoire par la somme actuelle, et addToMemory() ajoute la valeur actuelle à la valeur en mémoire. Pour setMemory() et addToMemory(), nous commençons par appeler equalClicked() pour mettre à jour sumSoFar et la valeur affichée.
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; }
La fonction privée createButton() est appelée à partir du constructeur pour créer les boutons de la calculatrice.
void Calculator::abortOperation() { clearAll(); display->setText(tr("####")); }
La fonction privée abortOperation() est appelée chaque fois qu'un calcul échoue. Elle réinitialise l'état de la calculatrice et affiche "####".
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; }
La fonction privée calculate() effectue une opération binaire. L'opérande de droite est donné par rightOperand. Pour les opérateurs additifs, l'opérande gauche est sumSoFar; pour les opérateurs multiplicatifs, l'opérande gauche est factorSoFar. La fonction renvoie false en cas de division par zéro.
Définition de la classe de boutons
Examinons maintenant la classe Button:
class Button : public QToolButton { Q_OBJECT public: explicit Button(const QString &text, QWidget *parent = nullptr); QSize sizeHint() const override; };
La classe Button possède un constructeur de commodité qui prend une étiquette de texte et un widget parent, et elle réimplémente QWidget::sizeHint() pour fournir plus d'espace autour du texte que l'espace normalement fourni par QToolButton.
Mise en œuvre de la classe des boutons
Button::Button(const QString &text, QWidget *parent) : QToolButton(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); setText(text); }
L'apparence des boutons est déterminée par la disposition du widget de la calculatrice, par le biais de la taille et de size policy des widgets enfants de la disposition. L'appel à la fonction setSizePolicy() dans le constructeur garantit que le bouton s'étendra horizontalement pour remplir tout l'espace disponible ; par défaut, les boutons QToolButtonne s'étendent pas pour remplir l'espace disponible. Sans cet appel, les différents boutons d'une même colonne auraient des largeurs différentes.
QSize Button::sizeHint() const { QSize size = QToolButton::sizeHint(); size.rheight() += 20; size.rwidth() = qMax(size.width(), size.height()); return size; }
Dans sizeHint(), nous essayons de renvoyer une taille qui convienne à la plupart des boutons. Nous réutilisons l'indice de taille de la classe de base (QToolButton), mais nous le modifions de la manière suivante :
- Nous ajoutons 20 au composant height de l'indice de taille.
- La composante width de l'indication de taille est au moins aussi grande que la composante height.
Cela permet de garantir qu'avec la plupart des polices, les boutons de chiffres et d'opérateurs seront carrés, sans tronquer le texte des boutons Backspace, Clear, et Clear All.
La capture d'écran ci-dessous montre à quoi ressemblerait le widget Calculator si nous n'avions pas défini la politique de taille horizontale à QSizePolicy::Expanding dans le constructeur et si nous n'avions pas réimplémenté QWidget::sizeHint().

L'exemple de la calculatrice avec les règles de taille par défaut et les indices de taille
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.