4.1. Kalkulator
Prosta 1-okienkowa aplikacja ilustrująca podstawy tworzenia interfejsu graficznego i obsługi działań użytkownika za pomocą Pythona 3 i biblioteki PyQt6. Przykład wprowadza również podstawy programowania obiektowego (ang. Object Oriented Programing).
Bibliotekę Pyqt6 instalujemy w środowisku wirtualnym Pythona za pomocą polecenia:
(.venv) pip install pyqt6
4.1.1. Pokaż okno
Zaczynamy od utworzenia pliku o nazwie kalkulator.py i wstawiamy do niego poniższy kod:
1from PyQt6.QtWidgets import QApplication, QWidget
2
3
4class Kalkulator(QWidget):
5 def __init__(self, parent=None):
6 super().__init__(parent)
7 self.interfejs()
8
9 def interfejs(self):
10
11 self.resize(300, 100)
12 self.setWindowTitle("Prosty kalkulator")
13 self.show()
14
15
16if __name__ == '__main__':
17 import sys
18 app = QApplication(sys.argv)
19 okno = Kalkulator()
20 sys.exit(app.exec())
Podstawą naszego programu będzie moduł PyQt6.QtWidgets, z którego importujemy
klasy QApplication – obsługa aplikacji, i QWidget – podstawową klasę wszystkich elementów interfejsu graficznego.
Wygląd okna naszej aplikacji definiować będziemy za pomocą klasy Kalkulator
dziedziczącej (zob. dziedziczenie) właściwości i metody z klasy QWidget.
Konstruktor naszej klasy na początku wywołuje konstruktor klasy bazowej: super(self).__init__(parent).
Następnie wywołujemy metodę interfejs(), w której tworzyć będziemy GUI naszej aplikacji.
Ustawiamy właściwości okna aplikacji i jego zachowanie:
self.resize(300, 100)– szerokość i wysokość okna;setWindowTitle("Prosty kalkulator")) – tytuł okna;self.show()– wyświetlenie okna na ekranie.
Informacja
Słowo self to konwencjonalna nazwa używana wtedy, kiedy odnosimy się do właściwości lub metod obiektu
utworzonego jako instancja jakiejś klasy. Słowo to zawsze występuje jako pierwszy parametr metod definiowanych
jako funkcje klasy. Zob. What is self?
Instrukcja app = QApplication(sys.argv) – tworzy obiekt aplikacji.
Argument sys.argv wskazuje, że aplikacja może otrzymywać parametry z linii poleceń.
W instrukcji okno = Kalkulator() tworzymy okno aplikacji, czyli obiekt będący instancją klasy Kalkulator.
Na koniec uruchamiamy główną pętlę programu (app.exec()), która rozpoczyna obsługę
zdarzeń (zob. główna pętla programu). Zdarzenia (np. kliknięcia) generowane są przez
system lub użytkownika i przekazywane są do aplikacji, która może je obsługiwać.
Poprawne zakończenie aplikacji zapewniające zwrócenie informacji o jej stanie do systemu,
co zapewnia wywołanie sys.exit().
Przetestujmy kod. Program uruchamiamy poleceniem wydanym w terminalu z aktywnym środowiskiem wirtualnym w katalogu ze skryptem:
~$ python3 kalkulator.py
4.1.2. Etykiety
Puste okno być może nie robi wrażenia, zobaczymy więc, jak tworzyć widżety (zob. widżet). Najprostszym przykładem będą etykiety.
Dodajemy wymagane importy i rozbudowujemy metodę interfejs():
1from PyQt6.QtWidgets import QApplication, QWidget
2from PyQt6.QtGui import QIcon
3from PyQt6.QtWidgets import QLabel, QGridLayout
12 def interfejs(self):
13
14 # etykiety
15 etykieta_1 = QLabel("Liczba 1:", self)
16 etykieta_2 = QLabel("Liczba 2:", self)
17 etykieta_3 = QLabel("Wynik:", self)
18
19 # przypisanie widgetów do układu tabelarycznego
20 uklad_t = QGridLayout()
21 uklad_t.addWidget(etykieta_1, 0, 0)
22 uklad_t.addWidget(etykieta_2, 0, 1)
23 uklad_t.addWidget(etykieta_3, 0, 2)
24
25 # przypisanie utworzonego układu do okna
26 self.setLayout(uklad_t)
27
28 # ustawienia rozmiaru, ikony i tytułu okna
29 self.setGeometry(20, 20, 300, 100)
30 self.setWindowIcon(QIcon('kalkulator.png'))
31 self.setWindowTitle("Prosty kalkulator")
Dodawanie widżetów zaczynamy od utworzenia obiektów na podstawie odpowiedniej klasy,
w tym wypadku QtLabel. Do jej konstruktora
przekazujemy tekst, który ma się wyświetlać na etykiecie, np.: etykieta_1 = QLabel("Liczba 1:", self).
Opcjonalny drugi argument, omówione wyżej słowo self, wskazuje obiekt rodzica, w tym przypadku okno,
w którym ją umieszczamy.
Informacja
Tworzenie widżetów z argumentem self umożliwia dostęp do ich właściwości
w zasięgu całej klasy, czyli w innych metodach.
Do rozmieszczania widżetów w oknie służą tzw. układy (ang. layout). Tworzymy więc
układ tabelaryczny: uklad_t = QGridLayout() – i dodajemy do niego obiekty (etykiety) za
pomocą metody addWidget(). Jako argumenty podajemy nazwę obiektu oraz numer wiersza i kolumny
definiujących komórkę, w której znaleźć się ma obiekt. Numeracja wierszy i kolumn zaczyna się od zera.
Zdefiniowany układ przypisujemy do okna nadrzędnego: self.setLayout(uklad_t).
Na koniec używamy metody setGeometry() do określenia położenia okna aplikacji
(początek układu jest w lewym górnym rogu ekranu) i jego rozmiaru (szerokość, wysokość).
Dodajemy również ikonę pokazywaną w pasku tytułowym lub w miniaturze na pasku zadań:
self.setWindowIcon(QIcon('kalkulator.png')).
Informacja
Plik graficzny z ikoną musimy pobrać i umieścić w katalogu
ze skryptem kalkulator.py.
Przetestuj wprowadzone zmiany.
4.1.3. Pola edycyjne i przyciski
Dodamy teraz pozostałe widżety tworzące graficzny interfejs naszej aplikacji, czyli pola edycyjne (zob. QLineEdit) i przyciski (zob. QPushButton). Jak zwykle zaczynamy od zaimportowania potrzebnych klas:
4from PyQt6.QtWidgets import QLineEdit, QPushButton, QHBoxLayout
Dodatkowa klasa QHBoxLayout umożliwi nam rozmieszczenie przycisków w układzie poziomym.
Następnie przed instrukcją self.setLayout(uklad_t) w metodzie interfejs() wstawiamy następujący kod:
28 # 1-liniowe pola edycyjne
29 self.liczba_1 = QLineEdit()
30 self.liczba_2 = QLineEdit()
31 self.wynik = QLineEdit()
32
33 # właściwości dodawanych pól
34 self.wynik.readonly = True
35 self.wynik.setToolTip('Wpisz <b>liczby</b> i wybierz działanie...')
36
37 uklad_t.addWidget(self.liczba_1, 1, 0)
38 uklad_t.addWidget(self.liczba_2, 1, 1)
39 uklad_t.addWidget(self.wynik, 1, 2)
40
41 # przyciski
42 prz_dodaj = QPushButton("&Dodaj", self)
43 prz_odejmij = QPushButton("&Odejmij", self)
44 prz_dziel = QPushButton("&Mnóż", self)
45 prz_mnoz = QPushButton("D&ziel", self)
46 prz_koniec = QPushButton("&Koniec", self)
47
48 uklad_h = QHBoxLayout()
49 uklad_h.addWidget(prz_dodaj)
50 uklad_h.addWidget(prz_odejmij)
51 uklad_h.addWidget(prz_dziel)
52 uklad_h.addWidget(prz_mnoz)
53
54 uklad_t.addLayout(uklad_h, 2, 0, 1, 3)
55 uklad_t.addWidget(prz_koniec, 3, 0, 1, 3)
56
57 # przypisanie utworzonego układu do okna
Jak widać, dodawanie widżetów polega zazwyczaj na:
utworzeniu obiektu na podstawie klasy opisującej potrzebny element interfejsu, np.
liczba_1 = QLineEdit(self)lubprz_dodaj = QPushButton("&Dodaj", self);ustawieniu właściwości obiektu, np.
self.wynik.readonly = Trueumożliwia tylko odczyt tekstu pola,self.wynik.setToolTip('Wpisz <b>liczby</b> i wybierz działanie...')– definiuje podpowiedź, która pojawia się po najechaniu kursorem na widżet;przypisaniu obiektu do układu – ponieważ mamy 4 przyciski działań, tworzymy układ horyzontalny (zob. QHBoxLayout):
uklad_h = QHBoxLayout(), dodajemy do niego przyciski za pomocą metodyaddWidget()i dodajemy go do układu tabelarycznego:uklad_t.addLayout(uklad_h, 2, 0, 1, 3). Argumenty liczbowe w metodzieaddLayout()oznaczają odpowiednio wiersz i kolumnę, tj. komórkę, do której wstawiamy obiekt, oraz liczbę wierszy i kolumn, które chcemy wykorzystać.
Informacja
Znak & przed jakąś literą w opisie przycisków tworzy skrót klawiaturowy dostępny po naciśnięciu
ALT + litera.
Po uruchomieniu programu powinniśmy zobaczyć okno podobne do poniższego:
4.1.4. Obsługa zdarzeń
Mamy okno z polami edycyjnymi i przyciskami, ale kontrolki te na nic nie reagują. Nauczymy się więc obsługiwać poszczególne zdarzenia.
Na początku skryptu importujemy klasę QMessageBox pozwalającą tworzyć okna z komunikatami oraz przestrzeń nazw Qt zawierającą różne stałe:
5from PyQt6.QtWidgets import QMessageBox
6from PyQt6.QtCore import Qt
Dalej w metodzie interfejs() po instrukcji self.setLayout(uklad_t) dopisujemy:
60 prz_koniec.clicked.connect(self.koniec)
61 prz_dodaj.clicked.connect(self.dzialanie)
62 prz_odejmij.clicked.connect(self.dzialanie)
63 prz_mnoz.clicked.connect(self.dzialanie)
64 prz_dziel.clicked.connect(self.dzialanie)
65
Jednym ze zdarzeń jest kliknięcie przycisku (clicked), które wiążemy za pomocą metody
conect() z metodami koniec() i dzialanie().
Metody te trzeba dodać do klasy Kalkulator(). Zacznijmy od metody koniec():
73 def koniec(self):
74 self.close()
75
Metoda koniec() wywołana po kliknięciu przycisku „Koniec” wywołuje metodę close() okna głównego,
co powoduje jego zamknięcie.
Informacja
Omówiony fragment kodu ilustruje mechanizm zwany w bibliotece Qt sygnały i sloty (ang. signals & slots). Zapewnia on komunikację między obiektami. Sygnał powstaje w momencie wystąpienia jakiegoś wydarzenia, np. kliknięcia. Slot może z kolei być wbudowaną w Qt funkcją lub Pythonowym wywołaniem (ang. callable), np. klasą lub metodą.
4.1.5. Działania
Ze zdarzeniem kliknięcia przycisków działań powiązana została metoda działania().
Dodajmy ją więc do klasy Kalkulator():
76 def dzialanie(self):
77
78 nadawca = self.sender()
79
80 try:
81 liczba1 = float(self.liczba_1.text())
82 liczba2 = float(self.liczba_2.text())
83
84 if nadawca.text() == "&Dodaj":
85 wynik = liczba1 + liczba2
86 elif nadawca.text() == "&Odejmij":
87 wynik = liczba1 - liczba2
88 elif nadawca.text() == "&Mnóż":
89 wynik = liczba1 * liczba2
90 else: # dzielenie
91 try:
92 wynik = round(liczba1 / liczba2, 9)
93 except ZeroDivisionError:
94 QMessageBox.critical(
95 self, "Błąd", "Nie można dzielić przez zero!")
96 return
97
98 self.wynik.setText(str(wynik))
99
100 except ValueError:
101 QMessageBox.warning(self, "Błąd", "Błędne dane", QMessageBox.StandardButton.Ok)
Ponieważ jedna metoda ma obsłużyć cztery sygnały, musimy znać źródło sygnału (ang. source),
czyli nadawcę (ang. sender), w tym przypadku przycisk, który został kliknięty: nadawca = self.sender().
W bloku try: przekształcamy wprowadzone w polach tekstowych liczby na typ zmiennoprzecinkowy.
Operacja ta z powodu błędnych danych może zwrócić wyjątek, który przechwytujemy w klauzuli
except ValueError i wyświetlamy ostrzeżenie: QMessageBox.warning().
Jeżeli dane są poprawne, w złożonej instrukcji warunkowej identyfikujemy kliknięty
poprzez umieszczony na nim komunikat, np. if nadawca.text() == "&Dodaj":.
Następnie wykonujemy odpowiednie dla danego przycisku działanie.
Warto zwrócić uwagę, że w przypadku dzielenia ponownie korzystamy z przechwytywania wyjątków, tym razem do obsługi
ewentualnego błędu dzielenia przez zero (ZeroDivisionError).
Instrukcja round(liczba1 / liczba2, 9) zaokrągla wynik dzielenia do 9 miejsc po przecinku.
Na koniec zamieniamy uzyskany wynik na ciąg znaków i wypisujemy w polu tekstowym za pomocą
metody setText(): self.wynikEdt.setText(str(wynik)).
Sprawdź działanie programu.
Wskazówka
Jeżeli po zaimplementowaniu działań, aplikacja po uruchomieniu nie aktywuje kursora
w pierwszym polu edycyjnym, należy tuż przed ustawianiem właściwości okna głównego
(self.setGeometry()) umieścić wywołanie self.liczba_1.setFocus(),
które ustawia focus na wybranym elemencie.
4.1.6. Zamykanie aplikacji
Zamknięcie okna również jest rodzajem wydarzenia (QCloseEvent),
które można przechwycić. Np. po to, aby zapobiec utracie niezapisanych danych.
Do klasy Kalkulator() dopiszmy kolejna metodę:
103 def closeEvent(self, event):
104 msg_box = QMessageBox()
105 msg_box.setWindowTitle('Komunikat')
106 msg_box.setText('Czy na pewno koniec?')
107 msg_box.setStandardButtons(QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No)
108 msg_box.setDefaultButton(QMessageBox.StandardButton.No)
109 odp = msg_box.exec()
110
111 if odp == QMessageBox.StandardButton.Yes.value:
112 event.accept()
113 else:
114 event.ignore()
115
W nadpisanej metodzie closeEvent()
wyświetlamy użytkownikowi prośbę o potwierdzenie zamknięcia za pomocą okna dialogowego
utworzonego za pomocą klasy QMessageBox.
Używamy następujących metod:
setWindowTitle()– do ustawienia tytuł okna,setText()– do podania komunikatu dla użytkownika, w tym przypadku pytania,setStandardButtons– do zdefiniowania dostępnych przycisków,setDefaultButton– do wskazania przycisku domyślnego.
Jeżeli użytkownik kliknie przycisk „Yes” (if odp == QMessageBox.StandardButton.Yes.value:),
zezwalamy na dalszą obsługę zdarzenia event.accept(), w przeciwnym razie odrzucamy je event.ignore().
4.1.7. Naciśnięcie klawisza
Może wygodnie byłoby zamykać aplikację naciśnięciem klawisza ESC?
Dopiszmy jeszcze jedną metodę w klasie Kalkulator:
116 def keyPressEvent(self, e):
117 if e.key() == Qt.Key.Key_Escape:
118 self.close()
119
Podobnie jak w przypadku closeEvent() tworzymy własną wersję funkcji
keyPressEvent obsługującej
naciśnięcia klawiszy QKeyEvent.
Sprawdzamy naciśnięty klawisz if e.key() == Qt.Key_Escape: i jeżeli jest to ESC,
zamykamy okno.
4.1.8. Materiały
Strona główna dokumentacji Qt6
Materiały Python 101
udostępniane przez
Centrum Edukacji Obywatelskiej na licencji
Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowa.
- Utworzony:
2026-04-19 o 17:41 w Sphinx 7.3.7
- Autorzy: