4.2. Widżety
Prosta 1-okienkowa aplikacja prezentująca większość podstawowych widżetów dostępnych w bibliotece Qt6 za pomocą Pythona 3 i biblioteki PyQt6. Przykład ilustruje również techniki programowania obiektowego (ang. Object Oriented Programing).
W wybranym katalogu przygotuj środowisko wirtualne Pythona. Zainstaluj bibliotekę PyQt6 w aktywowanym środowisku:
(.venv) pip install pyqt6
Uwaga
Wymagana wiedza:
Znajomość Pythona w stopniu średnim.
Znajomość podstaw projektowania interfejsu z wykorzystaniem bibliotek Qt (zob. scenariusz Kalkulator).
Przedstawiona aplikacja składa się z 3 plików, które muszą być zapisane w tym samym katalogu, np.
widzety.
4.2.1. QPainter – podstawy rysowania
Zaczynamy od utworzenia głównego pliku aplikacji o nazwie widzety.py.
Wstawiamy do niego poniższy kod:
1from PyQt6.QtWidgets import QApplication, QWidget
2from gui_z1 import UiWidget
3
4
5class Widgety(QWidget, UiWidget):
6 """ Główna klasa aplikacji """
7
8 def __init__(self):
9 super().__init__()
10 self.setWindowTitle('Widżety')
11
12
13if __name__ == '__main__':
14 import sys
15 app = QApplication(sys.argv)
16 okno = Widgety()
17 okno.show()
18 sys.exit(app.exec())
Klasa Widgety posłuży do zdefiniowania głównego okna oraz logiki działania naszej aplikacji.
Dziedziczy z klasy QWidget – podstawowej klasy biblioteki Qt, która jest bazą dla każdego elementu GUI,
w tym okna głównego. Dziedziczy również z klasy UiWidget importowanej z pliku gui.py,
w którym zdefiniujemy elementy interfejsu graficznego.
W konstruktorze klasy (__init()__) wywołujemy konstruktory klas rodziców (super().__init__())
oraz ustawiamy tytuł okna aplikacji (self.setWindowTitle('Widżety')).
Pozostały kod tworzy instancję aplikacji w oparciu o klasę QApplication, a także
instancję okna głównego, czyli klasy Widgety, wyświetla je i uruchamia pętlę zdarzeń.
Kod klasy UiWidget umieszczamy we wspomnianym pliku o nazwie gui.py:
1from ksztalty import Ksztalty, Ksztalt
2from PyQt6.QtWidgets import QHBoxLayout, QVBoxLayout
3
4
5class UiWidget:
6 """ Klasa definiująca GUI """
7
8 def __init__(self):
9
10 # widget definiujący kształt, instancja klasy Ksztalt
11 self.ksztalt1 = Ksztalt(None, Ksztalty.RECT)
12
13 # układ poziomy dla kształtów oraz przycisków CheckBox
14 uklad_h1 = QHBoxLayout()
15 uklad_h1.addWidget(self.ksztalt1)
16
17 # główny układ okna, pionowy
18 uklad_okna = QVBoxLayout()
19 uklad_okna.addLayout(uklad_h1)
20
21 # ustawienie głównego układu okna
22 self.setLayout(uklad_okna)
W konstruktorze klasy tworzymy widżet ksztalt1, który będzie mógł rysować figury geometryczne.
Widżet jest instancję klasy Ksztalt zaimportowanej z pliku ksztalty.py.
Z tego pliku importujemy również klasę Ksztalty, której właściwości oznaczają rysowane figury,
w tym przypadku prostokąt: self.ksztalt1 = Ksztalt(None, Ksztalty.Rect)
Jeden widżet może zawierać wiele różnych elementów GUI, które trzeba w jakiś sposób porozmieszczać. Służą do tego układy graficzne (ang. layouts). Biblioteka Qt udostępnia układy:
poziomy (QHBoxLayout),
pionowy (QVBoxLayout),
tabelaryczny (QGridLayout).
Rysowany kształt dodajemy do układu poziomego za pomocą metody addWidget().
Następnie sam układ poziomy dodajemy do pionowego układu okna za pomocą metody addLayout().
Główny układ okna naszego widżetu ustawiamy w metodzie setLayout().
4.2.2. Klasa Ksztalt
W pliku ksztalty.py umieszczamy poniższy kod:
1from PyQt6.QtWidgets import QWidget
2from PyQt6.QtGui import QPainter, QColor, QPolygon
3from PyQt6.QtCore import QPoint, QRect, QSize
4
5
6class Ksztalty:
7 """ Klasa pomocnicza, symuluje typ wyliczeniowy """
8 RECT, ELLIPSE, POLYGON, LINE = range(4)
9
10
11class Ksztalt(QWidget):
12 """ Klasa definiująca widget do rysowania kształtów """
13 # współrzędne prostokąta i trójkąta
14 prost = QRect(1, 1, 101, 101)
15 punkty = QPolygon([
16 QPoint(1, 101), # punkt początkowy (x, y)
17 QPoint(51, 1),
18 QPoint(101, 101)])
19
20 def __init__(self, parent, ksztalt=Ksztalty.RECT):
21 super().__init__(parent)
22
23 # kształt do narysowania
24 self.ksztalt = ksztalt
25
26 # kolor obramowania i wypełnienia w formacie RGB
27 self.kolor_o = QColor(0, 0, 0)
28 self.kolor_w = QColor(255, 255, 255)
29
Za pomocą klasy Ksztalty symulujemy typ wyliczeniowy, tzn. angielskim nazwom kształtów,
które będą dostępne jako dane statyczne klasy, przypisujemy kolejne liczby całkowite zaczynając od 0.
Kształty, które będziemy rysowali, to:
RECT – prostokąt, wartość 0;
ELLIPSE – elipsa, w tym koło, wartość 1;
POLYGON – linia łamana zamknięta, np. trójkąt, wartość 2;
LINE – linia łącząca dwa punkty, wartość 3.
Klasa Ksztalt dziedziczy z klasy QWidget i pozwoli na rysowanie zdefiniowanych
w klasie Ksztalty figur. W konstruktorze definiujemy właściwości obiektu, który ma być rysowany:
self.ksztalt– rysowana figura wskazana w parametrzeksztalt, której domyślna wartość toKsztalty.RECT,self.kolor_o,self.kolor_w– kolory obramowania i wypełnienia.
Kolory tworzymy za pomocą klasy QColor,
używając formatu RGB, np .: QColor(0, 0, 0).
Do klasy Ksztalt dodajemy metody odpowiedzialne za rysowanie:
30 def paintEvent(self, e):
31 qp = QPainter()
32 qp.begin(self)
33 self.rysuj_figury(qp)
34 qp.end()
35
36 def rysuj_figury(self, qp):
37 qp.setPen(self.kolor_o) # kolor obramowania
38 qp.setBrush(self.kolor_w) # kolor wypełnienia
39 # wygładzanie kształtu
40 qp.setRenderHint(QPainter.RenderHint.Antialiasing)
41
42 if self.ksztalt == Ksztalty.RECT:
43 qp.drawRect(self.prost)
44 elif self.ksztalt == Ksztalty.ELLIPSE:
45 qp.drawEllipse(self.prost)
46 elif self.ksztalt == Ksztalty.LINE:
47 qp.drawLine(self.prost.topLeft(), self.prost.bottomRight())
48 elif self.ksztalt == Ksztalty.POLYGON:
49 qp.drawPolygon(self.punkty)
50
Za rysowanie każdego widżetu odpowiada metoda paintEvent().
Nadpisujemy ją. Tworzymy instancję klasy QPainter
umożliwiającej rysowanie różnych kształtów (qp = QPainter()). Między metodami begin() i end()
wywołujemy metodę rysuj_figury(), w której implementujemy kod rysujący poszczególne kształty.
Metoda rysuj_figury() otrzymuje obiekt klasy QPainter. Jego metody setPen() i setBrush()
pozwalają ustawić kolor odpowiednio obramowania i wypełnienia. Następnie w instrukcji warunkowej
sprawdzamy rodzaj rysowanego kształtu i wywołujemy metodę rysującą odpowiednią figurę:
drawRect()– rysuje prostokąt,drawEllipse()– rysuje elipsę (koło),qp.drawLine()– pozwala narysować linię wyznaczoną przez współrzędne punktu początkowego i końcowego typuQPoint; nasza klasa wykorzystuje tu współrzędne lewego górnego (self.prost.topLeft()) i prawego dolnego (self.prost.bottomRight()) rogu domyślnego prostokątaprost,drawPolygon()– pozwala rysować wielokąty, jako argument podajemy listę typu QPolygon punktów typu QPoint opisujących współrzędne kolejnych wierzchołków; domyślne współrzędne zdefiniowane zostały jako atrybutpunktyklasyKsztalty,
Informacja
Każdy rysowany kształt wpisany jest w prostokąt zdefiniowany jako właściwość statyczna
klasy Ksztalt: prost = QRect(1, 1, 101, 101).
Obiekt ten jest instancją klasy QRect.
Dwie pierwsze wartości to współrzędne lewego górnego, a dwie następne prawego dolnego rogu prostokąta
w 2-wymiarowym układzie współrzędnych.
Początek układu współrzędnych, w odniesieniu do którego definiujemy w Qt pozycję widżetów czy punkty opisujące kształty, znajduje się w lewym górnym rogu obiektu rodzica, np. głównego okna aplikacji.
Informacja
Warto zrozumieć różnicę pomiędzy zmiennymi klasy a zmiennymi instancji.
Zmienne (właściwości, atrybuty) klasy, określane również jako dane statyczne, są wspólne
dla wszystkich jej instancji. W naszej aplikacji zdefiniowaliśmy w ten sposób
zmienne prost i punkty klasy Ksztalt.
Zmienne instancji natomiast są inne dla każdego obiektu.
Definiujemy je w konstruktorze, używając słowa self. Np. każda instancja klasy
Ksztalt może mieć inną wartość właściwości self.ksztalt.
Ćwiczenie
Uruchom skrypt
widzety.py.Spróbuj zmienić rodzaj rysowanej figury oraz kolory jej obramowania i wypełnienia.
Być może zauważysz, że po uruchomieniu naszego skryptu rozmiar okna aplikacji nie jest dopasowany
do rozmiaru rysowanej figury. Spróbujemy to zmienić uzupełniając kod klasy Ksztalt:
51 def sizeHint(self):
52 return QSize(102, 102)
53
54 def minimumSizeHint(self):
55 return QSize(102, 102)
56
57 def ustaw_ksztalt(self, ksztalt):
58 self.ksztalt = ksztalt
59 self.update()
60
61 def ustaw_kolor_w(self, r=0, g=0, b=0):
62 self.kolor_w = QColor(r, g, b)
63 self.update()
W nadpisanych metodach sizeHint() i minimumSizeHint() określamy sugerowany i minimalny
rozmiar naszego kształtu. Są one niezbędne, aby układy graficzne (ang. layouts), w których
umieścimy kształty, zarezerwowały odpowiednio dużo miejsca na ich wyświetlenie.
Kod uzupełniliśmy również o dwie metody ustaw_ksztalt() i ustaw_kolor_w(), które przydadzą się nam dalej.
Jak wskazują nazwy – pozwolą one zmieniać kształt i jego kolor wypełnienia już po utworzeniu obiektu.
Metoda self.update() wymusi ponowne narysowanie kształtu.
Ćwiczenie
Ponownie przetestuj działanie aplikacji, spróbuj zmienić rodzaj rysowanej figury oraz kolor jej wypełnienia.
Informacja
W kolejnych krokach będziemy dodawać widżety różnego typu. Kod tworzący odpowiednie obiekty
i ustawiający ich początkowe właściwości dopisywać będziemy w pliku gui.py
w konstruktorze klasy UiWidget. Dodając widżety, musimy pamiętać o zaimportowaniu
odpowiedniej klasy z PyQt6.QtWidgets na początku pliku.
Kod wiążący sygnały ze slotami umieścimy w pliku widzety.py,
w konstruktorze klasy Widgety. Sloty implementować będziemy jako funkcje
tej klasy.
4.2.3. Przyciski CheckBox
Wykorzystując klasę Ksztalt utworzymy kolejny obiekt do rysowania figur. Dodamy także
przyciski typu QCheckBox umożliwiające zmianę
rodzaju wyświetlanej figury.
Importy w pliku gui.py:
from PyQt6.QtWidgets import QCheckBox, QButtonGroup
Klasa UiWidget przyjmuje następującą postać:
6class UiWidget:
7 """ Klasa definiująca GUI """
8
9 def __init__(self):
10
11 # widget definiujący kształt, instancja klasy Ksztalt
12 self.ksztalt1 = Ksztalt(None, Ksztalty.RECT)
13 self.ksztalt2 = Ksztalt(None, Ksztalty.ELLIPSE)
14 self.ksztalt_aktywny = self.ksztalt1
15
16 # przyciski CheckBox
17 uklad_chk = QVBoxLayout() # układ pionowy
18 self.grupa_chk = QButtonGroup()
19 for i, v in enumerate(('Kwadrat', 'Koło', 'Trójkąt', 'Linia')):
20 self.chk = QCheckBox(v)
21 self.grupa_chk.addButton(self.chk, i)
22 uklad_chk.addWidget(self.chk)
23 przyciski = self.grupa_chk.buttons()
24 przyciski[self.ksztalt_aktywny.ksztalt].setChecked(True)
25
26 # przycisk CheckBox do wyboru aktywnego kształtu
27 self.ksztalt_chk = QCheckBox('<=')
28 self.ksztalt_chk.setChecked(True)
29 uklad_chk.addWidget(self.ksztalt_chk)
30
31 # układ poziomy dla kształtów oraz przycisków CheckBox
32 uklad_h1 = QHBoxLayout()
33 uklad_h1.addWidget(self.ksztalt1)
34 uklad_h1.addLayout(uklad_chk)
35 uklad_h1.addWidget(self.ksztalt2)
36 # koniec CheckBox
37
38 # główny układ okna, pionowy
39 uklad_okna = QVBoxLayout()
40 uklad_okna.addLayout(uklad_h1)
41
42 # ustawienie głównego układu okna
43 self.setLayout(uklad_okna)
Dodajemy drugi obiekt self.ksztalt2, domyślnie rysujący elipsę.
Definiujemy też dodatkową właściwość self.ksztalt_aktywny, która przechowywała będzie
aktualnie wybrany kształt, tzn. albo ksztal1 (domyślnie) albo ksztalt2.
Do tworzenia przycisków typu CheckBox wykorzystujemy pętlę for, która odczytuje z krotki
kolejne indeksy i etykiety przycisków. Jeśli masz wątpliwości, jak to działa,
przetestuj następujący kod w konsoli Pythona:
>>> for i, v in enumerate(('Kwadrat', 'Koło', 'Trójkąt', 'Linia')):
... print(i, v)
Odczytane etykiety przekazujemy do konstruktora: self.chk = QCheckBox(v).
Przyciski wyboru kształtu działać mają na zasadzie wyłączności, w danym momencie
powinien być zaznaczony tylko jeden z nich. Tworzymy więc grupę logiczną grupa_chk na podstawie
klasy QButtonGroup.
Do grupy dodajemy przyciski, oznaczając je kolejnymi indeksami:
self.grupa_chk.addButton(self.chk, i).
Metoda buttons() zwraca listę przycisków, którą zapisujemy w zmiennej przyciski.
Przycisk odpowiadający aktualnemu kształtowi wskazujemy przez indeks self.ksztalt_aktywny.ksztalt
i wywołujemy metodę setChecked(True), która go zaznacza.
Poza pętlą tworzymy jeszcze jeden przycisk (self.ksztaltChk = QCheckBox("<=")),
niezależny od powyższej grupy. Jego stan wskazuje aktywny kształt.
Domyślnie go zaznaczamy: self.ksztaltChk.setChecked(True), co oznacza,
że aktywną figurą będzie pierwszy kształt.
Wszystkie elementy interfejsu umieszczamy w układzie poziomym o nazwie uklad_h1.
Po lewej stronie znajdzie się ksztalt1, w środku układ przycisków wyboru,
a po prawej ksztalt2.
4.2.3.1. Obsługa sygnałów
Teraz zajmiemy się obsługą sygnałów. Przypomnijmy, że są to wydarzenia zachodzące w obrębie okna
naszej aplikacji (ruch myszy, kliknięcia, naciśnięcia klawiszy itp.) przechwytywane przez
główną pętlę zdarzeń naszej aplikacji. Do ich obsługi używamy slotów, czyli funkcji,
w tym wypadku będą to metody klasy Widgety.
W pliku widzety.py rozbudowujemy klasę Widgety:
5class Widgety(QWidget, UiWidget):
6 """ Główna klasa aplikacji """
7
8 def __init__(self):
9 super().__init__()
10 self.setWindowTitle('Widżety')
11
12 # Sygnały i sloty
13 # przyciski CheckBox
14 self.grupa_chk.buttonClicked.connect(self.ustaw_ksztalt)
15 self.ksztalt_chk.clicked.connect(self.aktywuj_ksztalt)
16
17 def ustaw_ksztalt(self):
18 self.ksztalt_aktywny.ustaw_ksztalt(self.grupa_chk.checkedId())
19
20 def aktywuj_ksztalt(self, wartosc):
21 nadawca = self.sender()
22 if wartosc:
23 self.ksztalt_aktywny = self.ksztalt1
24 nadawca.setText('<=')
25 else:
26 self.ksztalt_aktywny = self.ksztalt2
27 nadawca.setText('=>')
28 przyciski = self.grupa_chk.buttons()
29 przyciski[self.ksztalt_aktywny.ksztalt].setChecked(True)
30
Grupa przycisków grupa_chk po kliknięciu emituje sygnał buttonClicked()`.
Przekazujemy jego obsługę do slotu (metody klasy Widgety) ustaw_ksztalt().
W slocie ustaw_ksztalt() używamy metody o tej samej nazwie klasy Ksztalt
do ustawienia nowej figury do narysowania. Jako argument przekazujemy
identyfikator klikniętego przycisku odczytywany za pomocą metody checkedId().
Jest to liczba całkowita, która wskazuje jedną z figur zdefiniowanych w klasie Ksztalty.
Przypomnijmy (zob. wyżej), że metoda ustaw_ksztalt() z klasy Kształt aktualizuje
identyfikator figury i wywołuje metodę update(), która wywołuje metodę
paintEvent(), a ta metodę rysuj_figury(), która rysuje nową figurę.
Kliknięcie przycisku checkbox wskazującego aktywną figurę obsługujemy za pomocą
slotu aktywuj_ksztalt(). Jej zadaniem jest ustawienie pierwszego lub drugiego
kształtu jako aktywnego. Jeżeli przekazany do slotu argument wartosc będzie
miał wartość True, co oznacza, że checkbox został zaznaczony, aktywujemy
ksztalt1, w przeciwnym razie ksztalt2. Zmieniamy również odpowiednio
tekst wyświetlany przy przycisku.
Informacja
Warto zapamiętać, jak uzyskać dostęp do obiektu nadawcy, który wygenerował dany sygnał.
W odpowiednim slocie używamy kodu self.sender().
Ćwiczenie
Uruchom kilkakrotnie aplikację. Spróbuj zmieniać inicjalne rodzaje domyślnych kształtów i kolory wypełnienia figur.
4.2.5. ComboBox i SpinBox
Modyfikowane kanały koloru można również wybierać z rozwijalnej listy typu QComboBox, a ich wartości ustawiać za pomocą widżetu QSpinBox.
W pliku gui.py dodajemy importy:
from PyQt6.QtWidgets import QComboBox, QSpinBox
Po komentarzu # koniec RadioButton uzupełniamy konstruktor klasy UiWidget:
73 # Lista ComboBox i SpinBox
74 self.lista_rgb = QComboBox()
75 for v in 'RGB':
76 self.lista_rgb.addItem(v)
77 self.lista_rgb.setEnabled(False)
78 # SpinBox
79 self.spin_rgb = QSpinBox()
80 self.spin_rgb.setMinimum(0)
81 self.spin_rgb.setMaximum(255)
82 self.spin_rgb.setEnabled(False)
83 # układ pionowy dla ComboBox i SpinBox
84 uklad_v1 = QVBoxLayout()
85 uklad_v1.addWidget(self.lista_rgb)
86 uklad_v1.addWidget(self.spin_rgb)
87 # do układu poziomego grupy Radio dodajemy układ ComboBox i SpinBox
88 uklad_h3.insertSpacing(1, 25)
89 uklad_h3.addLayout(uklad_v1)
90 # koniec ComboBox i SpinBox
91
Do listy utworzonej na podstawie klasy ComboBox dodajemy za pomocą pętli for
litery poszczególnych kanałów: self.lista_rgb.addItem(v).
Obiekt typu SpinBox podobnie jak Slider wymaga ustawienia zakresu wartości <0-255>.
Stosujemy takie same metody, jak wcześniej, tj. setMinimum() i setMaximum().
Obydwa widżety na początku wyłączamy metodą setEnabled(False). Umieszczamy jeden nad drugim
w pionowym układzie uklad_v1, a układ dodajemy obok przycisków Radio uklad_h3.addLayout(uklad_v1),
oddzielając go odstępem 25 px: uklad_h3.insertSpacing(1, 25).
4.2.5.1. Obsługa sygnałów
W pliku widzety.py dodajemy import:
from PyQt6.QtWidgets import QRadioButton
Do konstruktora dodajemy kod przechwytujący 3 sygnały:
27 # Lista ComboBox i SpinBox
28 self.grupa_rbb.clicked.connect(self.ustaw_stan)
29 self.lista_rgb.currentTextChanged.connect(self.ustaw_kanal)
30 self.spin_rgb.valueChanged.connect(self.zmien_kolor)
31
Pierwszy sygnał, tj. kliknięcie przycisku CheckBox grupy przycisków RadioButton
wiążemy ze slotem ustaw_stan():
81
82 def ustaw_stan(self, wartosc):
83 if wartosc:
84 # włączone przyciski RadioButton
85 self.lista_rgb.setEnabled(False)
86 self.spin_rgb.setEnabled(False)
87 else:
88 # włączona lista ComboBox
89 self.lista_rgb.setEnabled(True)
90 self.spin_rgb.setEnabled(True)
91 self.kanaly = set()
92 self.kanaly.add(self.lista_rgb.currentText())
93 self.wypisz_kanal(wartosc, self.spin_rgb)
94
Jeżeli metoda ustaw_stan() w parametrze wartosc otrzyma True, tzn. przycisk
jest zaznaczony, wyłączamy widżety ComboBox i SpinBox (setEnabled(False)).
W przeciwnym razie je włączamy (setEnabled(True)), a także resetujemy zbiór kanałów
i dodajemy do niego kanał wybrany na liście: self.kanaly.add(self.lista_rgb.currentText()).
Na koniec ustawiamy wartość aktywnego kanału w obiekcie SpinBox.
Zmianę kanału na liście ComboBox, tj. sygnał currentTextChanged obsługujemy za pomocą dodanej
wcześniej metody ustaw_kanal(), która przyjmuje następującą postać:
46 def ustaw_kanal(self, wartosc=''):
47 nadawca = self.sender()
48 if isinstance(nadawca, QRadioButton) and wartosc:
49 # nadawca to QRadioButton
50 self.kanaly = set() # resetujemy zbiór kanałów
51 kanal = nadawca.text()
52 self.kanaly.add(kanal)
53 self.wypisz_kanal(kanal, self.suwak)
54 elif isinstance(nadawca, QComboBox):
55 # nadawca to QComboBox
56 self.kanaly = set() # resetujemy zbiór kanałów
57 self.kanaly.add(wartosc)
58 self.wypisz_kanal(wartosc, self.spin_rgb)
59
Dodajemy warunek isinstance(nadawca, QComboBox) sprawdzający, czy nadawcą jest obiekt typu QComboBox.
Jeżeli tak, resetujemy zbiór kanałów i dodajemy literę wybranego kanału: self.kanaly.add(wartosc).
Na koniec ustawiamy wartość tego kanału w obiekcie SpinBox: self.wypisz_kanal(wartosc, self.spin_rgb).
Informacja
Slot ustaw_kanal() w przypadku sygnału toogled obiektu typu QRadioButton otrzymuje
w argumencie wartosc wartość True lub False w zależności od tego, czy przycisk jest zaznaczony
czy nie. W przypadku sygnału currentTextChanged obiektu typu QComboBox
argument wartosc zawiera literę wybranego kanału.
Zmiana wartości w kontrolce SpinBox, czyli sygnał valueChanged, przekierowujemy
do dodanego wcześniej slotu zmien_lolor(), który obsługuje również zmiany wartości na suwaku.
Uruchom aplikację i sprawdź jej działanie.
4.2.7. QLabel i QLineEdit
Dodamy do aplikacji zestaw widżetów typu QLineEdit, tzn. 1-liniowych pól edycyjnych. Pola będą oznaczone etykietami typu QLabel i będą umożliwiały ustawienia składowych koloru wypełnienia aktywnego kształtu.
W pliku gui.py dodajemy importy:
from PyQt6.QtWidgets import QLabel, QLineEdit
Następnie po komentarzu # koniec PushButton uzupełnij konstruktor klasy UiWidget:
111 # etykiety QLabel i pola QLineEdit
112 uklad_h4 = QHBoxLayout()
113 self.label_r = QLabel('R')
114 self.label_g = QLabel('G')
115 self.label_b = QLabel('B')
116 self.edit_r = QLineEdit('0')
117 self.edit_g = QLineEdit('0')
118 self.edit_b = QLineEdit('0')
119 for v in 'rgb':
120 label = getattr(self, 'label_' + v)
121 edit = getattr(self, 'edit_' + v)
122 edit.setObjectName('edit_' + v)
123 edit.setMaxLength(3)
124 uklad_h4.addWidget(label)
125 uklad_h4.addWidget(edit)
126 # koniec QLabel i QLineEdit
127
128 # główny układ okna, pionowy
Zaczynamy od utworzenia trzech etykiet i trzech pól edycyjnych dla każdego kanału.
W pętli wykorzystujemy funkcję Pythona
getattr(obiekt, nazwa),
która potrafi zwrócić podany jako nazwa atrybut obiektu. W tym przypadku
kolejne etykiety i pola edycyjne, które umieszczamy obok siebie w poziomie.
Przy okazji ograniczamy długość wpisywanego w pola edycyjne tekstu do 3 znaków:
edit.setMaxLength(3).
Układ uklad_h4 trzeba jeszcze dodać do głównego układu okna:
uklad_okna.addLayout(uklad_h4)
4.2.7.1. Obsługa sygnałów
W pliku widzety.py dodajemy import:
from PyQt6.QtWidgets import QLineEdit
Obsługę sygnałów dopisujemy w konstruktorze:
39 # etykiety QLabel i pola QEditLine
40 for v in 'rgb':
41 edit = getattr(self, 'edit_' + v)
42 edit.editingFinished.connect(self.ustaw_kanal)
43 edit.editingFinished.connect(self.zmien_kolor)
44
W pętli, podobnej jak w pliku interfejsu, sygnał zakończenia edycji tekstu w polu typu QLineEdit
wiążemy z dodanymi wcześniej slotami ustaw_kanal i zmien_kolor().
Będziemy mogli wpisywać w tych polach nowe wartości składowych koloru.
Uzupełniamy metodę ustaw_kanal():
77 elif isinstance(nadawca, QLineEdit):
78 self.kanaly = set() # resetujemy zbiór kanałów
79 kanal = nadawca.objectName()[-1].upper()
80 self.kanaly.add(kanal)
81
Jeżeli nadawca jest obiektem typu QLineEdit, odczytujemy ostatnią literę z jego nazwy
i zamieniamy na wielką: nadawca.objectName()[-1].upper(). Litera ta oznacza edytowany kanał,
który dodajemy do zbioru kanałów.
Następnie zmieniamy metodę zmien_kolor(), która do tej pory otrzymywała wartości typu całkowitego
z suwaka QSlider lub pola QSpinBox. Pole edycyjne zwraca liczbę, ale w postaci tekstu, który trzeba
zamienić na typ całkowity. Dodajemy więc na początku metody instrukcję:
90 def zmien_kolor(self, wartosc=0):
91 if isinstance(self.sender(), QLineEdit):
92 wartosc = int(self.sender().text())
93 self.lcd.display(wartosc)
Natomiast na końcu omawianej metody umieszczamy wywołanie nowej metody: self.info().
Kod metody info() dopisujemy do klasy Widgety:
119 def info(self):
120 font_b = "QWidget { font-weight: bold }"
121 font_n = "QWidget { font-weight: normal }"
122
123 for v in 'rgb':
124 label = getattr(self, 'label_' + v)
125 if v.upper() in self.kanaly:
126 label.setStyleSheet(font_b)
127 else:
128 label.setStyleSheet(font_n)
129
130 self.edit_r.setText(str(self.kolor_w.red()))
131 self.edit_g.setText(str(self.kolor_w.green()))
132 self.edit_b.setText(str(self.kolor_w.blue()))
133
Jej zadanie polega na wyróżnieniu kanałów znajdujących się w zbiorze kanaly poprzez pogrubienie czcionki etykiet
i uaktywnieniu odpowiednich pól edycyjnych. Jeżeli kanał jest nieaktywny, ustawiamy normalną czcionkę etykiety
i wyłączamy pole edycji. Wszystko dzieje się w pętli wykorzystującej omawianą już funkcję getattr()
do uzyskania dostępu do kolejnych obiektów. Na końcu metody wartości poszczególnych kanałów koloru
wpisujemy do odpowiednich pól edycyjnych.
Informacja
Typ czcionki zmieniamy z pomocą stylów CSS zdefiniowanym na początku funkcji pod nazwą
font_b i font_n. Później przypisujemy je etykietom za pomocą metody setStyleSheet().
Wprowadź omówione zmiany i przetestuj działanie aplikacji.
4.2.8. Dodatki
Nasza aplikacja działa, ale można dopracować w niej kilka szczegółów. Poniżej zaproponujemy kilka zmian, które potraktować należy jako zachętę do samodzielnych ćwiczeń i przeróbek.
Pola edycyjne QLineEdit dla składowych zielonej i niebieskiej powinny być na początku nieaktywne.
Zaznaczenie jednej z grup widżetów powinno wyłączać inne grupy, tj. w danym momencie powinna być aktywna albo grupa przycisków Radio albo lista Combo albo grupa przycisków Push z polami edycyjnymi.
Jeżeli aktywujemy grupę Push, należy zaznaczyć (wcisnąć) przycisk odpowiadający ostatniemu aktywnemu kanałowi.
Stan pól edycyjnych powinien odpowiadać stanowi przycisków Push, wciśnięty przycisk to aktywne pole i odwrotnie.
Funkcja
zmien_kolor()nie jest zabezpieczona przed błędnymi danymi wprowadzanymi do pól edycyjnych.Dodaj dwa osobne przyciski, które umożliwią kopiowanie koloru i kształtu z jednej figury na drugą.
Dodaj etykietę lub pole edycyjne, które będzie wyświetlało aktualnie ustawiony kolor dla aktywnego kształtu w formacie szesnastkowym.
4.2.9. Materiały
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:42 w Sphinx 7.3.7
- Autorzy: