To już czterdziesty artykuł o Pythonie i dopiero czterdziesty zarazem. Ostatni z ustalonego przeze mnie cyklu. Na swoim Facebooku ogłosiłem, że zmieniam główny temat pisanych artykułów po napisaniu 40 premierowych materiałów. Kolejny raz programowanie graficznego interfejsu użytkownika, w skrócie GUI (ang. "Graphical User Interface"). Jeśli "EasyGUI" nie spełnia Waszych oczekiwań, sięgnijcie po pakiet "PyQt5", specjalny "binding" w Pythonie potężnego zestawu bibliotek o nazwie "Qt". Niniejszy artykuł rozwieje wątpliwości odnośnie tworzenia okna, zmiany jego pozycji i rozmiaru, zmiany tytułu okna, dodania komunikatu, dodania przycisku, dodania metody wywołującej się po jego kliknięciu ("callback"), a także jak wprowadzać tabelkę oraz dane do jej komórek. Informuję, że temat wymaga podstawowego pojęcia na temat programowania obiektowego w Pythonie.

OSTRZEGAM, ten artykuł jest o wiele większy niż zdecydowana większość wpisów jakie już w życiu napisałem!

PAKIET "PYQT5" WYMAGA INSTALACJI

Raz kolejny zmuszony jestem napisać o tym samym. "PyQt5" trzeba najpierw zainstalować poprzez wbudowany w język system zarządzania pakietami, "pip". Pakiet nie zawiera się w standardowej bibliotece Pythona. Wiersz poleceń z uprawnieniami administratora i jedziemy z poleceniem:

pip install pyqt5

Po wciśnięciu Enter, wystarczy trochę poczekać (ponad 50 megabajtów do pobrania) aż zostaniemy powiadomieni o skutku przeprowadzonej operacji.

Instalacja w systemie "pip"

Komunikat o poprawności instalacji pakietu "PyQt5" w systemie "pip".

Pakiet "PyQt5" nie wymaga innych dodatków czy pakietów, zatem ten krok powinien pójść bezboleśnie.

GRAFICZNY INTERFEJS UŻYTKOWNIKA? A PO CO TO KOMU?

Już pisałem o tym w artykule o pakiecie do podobnych zastosowań więc tylko o tym przypomnę. GUI jest bardzo ważnym elementem ułatwiającym korzystanie z Waszego programu nie wymagając wiedzy informatycznej od użytkownika, który z zawodu nie jest informatykiem. Setki komend o przedziwnych hasłach i skrótach myślowych zostaje "przykrytych" pięknymi zdaniami jak "Dodaj nowy plik" albo "Wyczyść katalog". To wszystko sprawia, że człowiekowi nie jest potrzebna znajomość informatyki, aby móc się sprawnie poruszać po programie i wpływać na operacje. Ponadto, automatyzujemy pewne procedury "scalając" mnóstwo osobnych poleceń i czynności w jedną wielką "wtyczkę" dzięki której komputer wykonuje całe zadanie od A do Z przy pomocy jednego kliknięcia. Czyli nawet programiście może to pomóc, bo nie traci czasu na wprowadzanie poleceń, tylko skupia się na konkretach. Klika przycisk, podaje dane, a cała reszta idzie sama. Są obopólne korzyści.

ZASADA DZIAŁANIA

Pakiet "PyQt5" działa inaczej niż "EasyGUI". Tutaj tworzenie GUI jest znacznie rozszerzone. Polega na wstawianiu elementów do okna takich jak przyciski, teksty, tabelki. Ponadto, mamy możliwość programowania zachowania elementów, aby wywoływały pewne funkcje w momencie "odpalenia" zdarzenia. Takim zdarzeniem jest na przykład kliknięcie przycisku, ale nie tylko. Może to być przeciągnięcie suwaka, zaznaczenie opcji w stylu wł. / wył. (tzw. "ptaszek"), może to być również wprowadzenie dowolnego znaku do pola tekstowego. To są przykładowe zdarzenia jakie mogą od nas otrzymać jakąś metodę do wykonania.

MOŻLIWOŚCI JAKIE OFERUJE PAKIET "PYQT5"

Teraz główny punkt artykułu. Pisanie kodu źródłowego razem z tłumaczeniem i zastosowaniem.

IMPORT MODUŁU

Najpierw import modułu. Aby uniknąć pisania tego samego ciągu co każdą metodę pochodzącą od tego pakietu, proponuję użyć "from":

from PyQt5.QtWidgets import *

W ten sposób, będzie można napisać od razu nazwę metody czy zmiennej bez powyższego ciągu za każdym razem. "PyQt5.QtWidgets", oto moduł który nas teraz interesuje.

STRUKTURA OKNA

"Qt" zostało skonstruowane na kształt paradygmatu obiektowego. W związku z tym, najlepszym wariantem jest opakowanie okna w klasę, a następnie utworzenie jej instancji. Oto najprostsza postać kodu wyświetlająca "gołe" okno:

from PyQt5.QtWidgets import *

class App(QWidget):
    def __init__(self):
        super().__init__()
        self.show()

app = QApplication([])
window = App()

app.exec_()

W klasie wykorzystywane jest dziedziczenie i rozpoznacie je po dwóch czynnikach. Raz, nazwa klasy bazowej "wpakowana" w nawias obok nazwy "App" (to jest przykładowa nazwa na potrzeby artykułu, możecie dać jaką chcecie). Dwa, to wywołanie metody "__init__" z klasy bazowej za pomocą słowa kluczowego "super".

Potem jest wywołanie metody "show". Pozwala ona wyświetlić nasze okienko. Nie zapomnijcie o tej metodzie! Bez niej, powłoka "utknie" i nawet "Ctrl + C" Wam nie pomoże. Pozostanie tylko zamknąć awaryjnie cały interpreter (przynajmniej ze strony "IDLE").

Dwie zmienne już poza klasą odpowiadają za tworzenie instancji. "QApplication" tworzy wskaźnik do aplikacji przyjmując w parametrze listę argumentów, takich samych jak się wprowadza do pliku wykonywalnego w wierszu poleceń (na kształt "-n" na przykład). Ponieważ my z nich nie korzystamy, wprowadzamy pustą listę, ale ona musi tam być! Jak nie, to wyjątekDruga zmienna jest instancją naszego nowo utworzonego okna. Jeżeli o niej zapomnicie, nic się Wam nie wyświetli, a powłoka się zawiesi.

Ostatnim poleceniem jest wywołanie metody "exec_" "z ogonkiem" na końcu nazwy! To spowoduje uruchomienie pętli kontrolującej okno i zwrócenie kodu wyjściowego po jego zamknięciu. Czy widzicie, że mimo utworzenia instancji okna, nie trzeba jej wprowadzać jako parametru do "exec_"? Pakiet "PyQt5" robi to za nas automatycznie.

Działanie pustego okna

Na aplikację w pakiecie "PyQt5" składa się dodanie okna będącego instancją klasy dziedziczącej od klasy "QWidget".

Tak wygląda okno po uruchomieniu programu. Kliknięcie krzyżyka spowoduje jego prawidłowe zamknięcie, a program się zakończy.

ZMIANA TYTUŁU OKNA

Pakiet "PyQt5" przypisuje nowym okienkom domyślną nazwę "pythonw". Jest mało interesująca więc zrozumiem każdego, kto będzie chciał nadać inną nazwę. Oto metoda "setWindowTitle", która zawiera się w dziedziczonej klasie "QWidget". Osadzamy ją w metodzie "__init__" i piszemy tak:

self.setWindowTitle([łańcuch znaków jako nazwa okna])

ZMIANA POZYCJI I WYMIARÓW OKNA

Chcecie zmienić pozycję okna i jego wymiary? Proszę uprzejmie! Służy do tego metoda o nazwie "setGeometry". W parametrach podajemy pozycję okna w osi X, pozycję okna w osi Y, szerokość i wysokość. Wprowadzamy ją wewnątrz naszej klasy, oczywiście:

self.setGeometry([liczba całkowita jako pozycja okna w osi X], [liczba całkowita jako pozycja okna w osi Y], [liczba całkowita jako szerokość okna], [liczba całkowita jako wysokość okna])

UMIESZCZANIE TEKSTU

Pora na wprowadzenie elementu! Zaczniemy od czegoś prostego. "QLabel" pozwala utworzyć prosty tekst, ale już osadzony w okienku. Aby ujrzeć tekst w oknie, wystarczy utworzyć instancję klasy "QLabel" w naszej klasie:

text = QLabel(self)

Za parametr wprowadzamy dowolną instancję klasy dziedziczącej od "QWidget". Nasze okienko spełnia takie wymaganie, zatem podajemy "self". Gdybyśmy mieli więcej okienek, możemy później wprowadzić którekolwiek z nich.

Chwileczkę! Zanim zaczniecie testować, dorzućcie sobie pod spodem metodę "setText". Dzięki niej, możecie wprowadzić tekst jaki ma się znaleźć. Samo utworzenie "QLabel" niczego nie da, ujrzycie pustkę na ekranie. Obiekt będzie, ale bez tekstu.

[zmienna typu "QLabel"].setText([łańcuch znaków jako tekst])

Dopiero teraz po uruchomieniu programu, tekst będzie na Was "czekał", abyście mu się przyglądali.

Działanie okna z obiektem "QLabel" w pakiecie "PyQt5" w Pythonie

Klasa "QLabel" odpowiada za wstawianie tekstu w środek okna.

To dopiero początek!

UMIESZCZANIE PRZYCISKU

Kolejny podpunkt to pokazanie Wam jak utworzyć przycisk. Nie tylko jego utworzenie i zmianę tekstu, ale także zaprogramowanie go, żeby coś zrobił po jego kliknięciu (fachowo to się nazywa "callback", czyli "wywołanie zwrotne"). Instancja klasy "QPushButton" - oto co trzeba zrobić:

button = QPushButton([łańcuch znaków jako tekst przycisku], self)

To załatwia dwa punkty z trzech, bo to tylko utworzy przycisk, umieści go w punkcie (0, 0) w lewym górnym rogu i przypisze tekst dla niego. Jeśli chcemy, aby coś zrobił, potrzebna nam będzie dodatkowa metoda i kolejne wywołanie.

ZAPROGRAMOWANIE PRZYCISKU

Zaraz po utworzeniu instancji przycisku, zdefiniujcie sobie nową funkcję (może być w środku klasy, może być na zewnątrz), która będzie wykonywać cokolwiek np. wyświetlenie komunikatu poprzez "print". Następnie, korzystając z niniejszej metody "connect":

[zmienna typu "QPushButton"].clicked.connect([nazwa funkcji wywołującej się po kliknięciu])

zobaczycie po uruchomieniu aplikacji, przycisk z podanym tekstem. A spróbujcie go nacisnąć, to ujrzycie na wyjściu komunikat. A wszystko dzięki kilku prostym instrukcjom.

Działanie okna z obiektem "QPushButton" w pakiecie "PyQt5" w Pythonie

Klasa "QPushButton" pozwala wstawić interaktywny przycisk z własnym tekstem oraz zaprogramować wywołanie określonej funkcji po jego kliknięciu.

"CALLBACK" Z PARAMETREM

Jak mogliście zauważyć, jest trochę lipa przy funkcjach przyjmujących parametr. Nie da się w sposób tradycyjny wprowadzić parametrów, gdyż parametr oczekuje jedynie nazwy. Jest jednak niekonwencjonalne obejście. Polega ono na wprowadzeniu słowa kluczowego "lambda" i potraktowanie zapisu jako wyrażenie lambda (funkcja anonimowa):

[zmienna typu "QPushButton"].clicked.connect(lambda: [nazwa funkcji z parametrami])

Tym sposobem tworzymy odwołanie do funkcji anonimowej, która wywołuje inną funkcję (naszą) i tam wprowadzamy sobie co chcemy.

UMIESZCZANIE TABELI

Kojarzycie pewnie "Microsoft Excel" i sławny termin "arkusz kalkulacyjny". Kto powiedział, że nie możemy zrobić prostej tabelki tutaj? Pakiet "PyQt5" udostępnia taką możliwość, jednak wymaga ona trochę większego ciągu instrukcji.

Po pierwsze, sam "byt" czyli instancja klasy "QTableWidget":

table = QTableWidget([liczba całkowita jako liczba rzędów], [liczba całkowita jako liczba kolumn], self)

Gdy na tym etapie odpalicie aplikację, tabelka się pojawi, tylko zajmie niewielką część okna. Jeśli to Wam odpowiada, w porządku. Gdy jednak chcecie, aby tabelka miała więcej miejsca, musicie "opakować" ją w specjalnym "układzie" nazywanym po angielsku "layout" (nagłówek niżej).

WPROWADZANIE DANYCH DO KOMÓREK TABELI

Używając metody "setItem", nakazujecie programowi dodanie tekstu do odpowiedniej komórki Waszej tabelki. Jednak nie wolno nam podać samego łańcucha znaków, tylko...instancję klasy "QTableWidgetItem":

[zmienna typu "QTableWidget"].setItem([liczba całkowita jako rząd], [liczba całkowita jako kolumna], QTableWidgetItem([łańcuch znaków jako treść komórki]))

Pamiętajcie, że liczymy od zera! Po uruchomieniu, podany przez Was tekst znajdzie się w odpowiedniej komórce tabeli. Jeżeli macie problemy z ustalaniem do której komórki chcecie się dostać, radzę "przestawić się" na spojrzenie jak na tablicę dwuwymiarową.

USTAWIANIE UKŁADU "QVBOXLAYOUT"

"Layout" służy jako "worek" do umieszczania w nim wielu różnych elementów. Możemy chcieć mieć ładnie ułożone przyciski albo sekcje z tekstami gdzie poniżej nich umieszczamy pole tekstowe. Oto przykład:

layout = QVBoxLayout()

"QVBoxLayout" ("virtual box layout", tak przypuszczam) jest jednym z kilku dostępnych rodzajów układów oferujących umieszczanie pionowo jednego obiektu po drugim. Za pomocą metody "addWidget" dodajemy dowolny obiekt dziedziczący od klasy "QWidget":

[zmienna typu "QLayout"].addWidget([zmienna typu "QWidget"])

a z poziomu klasy wywołujemy metodę "setLayout" celem przypisania naszego układu do okna, tym samym nakładając go na okno:

self.setLayout([zmienna typu "QLayout"])

Po takim zabiegu, tabelka będzie miała całą przestrzeń dla siebie, a ona sama będzie "rozciągliwa" tworząc pewną ramkę wokół wymiarów okna.

Działanie okna z obiektem "QTableWidget" w pakiecie "PyQt5" w Pythonie

Umieszczenie tabelki odbywa się poprzez utworzenie instancji klasy "QTableWidget". Przy pomocy układów ("layout"), jesteśmy w stanie wstawić dowolny obiekt, aby przylegały do siebie jeden obok drugiego.

DODANIE KOMUNIKATU

Ostatnim rozdziałem będą komunikaty. Coś jak przy "EasyGUI" tylko są inaczej zaprogramowane. Tworzymy sobie zmienną, a po znaku przypisania wprowadzamy metodę "question", bo zwraca ona wartość:

answer = QMessageBox.question([łańcuch znaków jako tytuł komunikatu], [łańcuch znaków jako treść komunikatu], [stałe jako przyciski jakie mają się znaleźć])

Pakiet "PyQt5" posiada pakiet stałych służących do identyfikacji przycisków. Oto one:

  • QMessageBox.Yes
  • QMessageBox.No
  • QMessageBox.Ok
  • QMessageBox.Cancel

Tylko to Was interesuje co do trzeciego parametru. Możecie także podstawić kilka naraz tworząc w ten sposób alternatywę binarną:

QMessageBox.Yes | QMessageBox.No

Zabieg ten polega na obliczeniu wartości binarnej z dwóch lub więcej innych liczb w systemie binarnym porównując każdy bit z każdej liczby i zwraca jedynkę ("prawdę") jeśli którykolwiek bit jest prawdziwy. Po ustaleniu ostatniego bitu, zwraca wynik w systemie binarnym.

Z taką "odpowiedzią" możecie później zrobić co chcecie, na przykład poddać sprawdzeniu w instrukcji warunkowej. Jeśli chcecie sprawdzić jaką odpowiedź się kliknęło, nie korzystacie z liczb, tylko z tych samych stałych wymienionych powyżej:

if answer == QMessageBox.Yes:
	# dowolne instrukcje

Po uruchomieniu aplikacji, zobaczycie taki lub podobny komunikat:

Działanie okna z obiektem "QMessageBox" w pakiecie "PyQt5" w Pythonie

"QMessageBox" odpowiada za wyświetlanie komunikatów. Posiada parę stałych wartości którymi posługujemy się w celu identyfikacji odpowiedzi oraz przycisków jakie mamy zamiar wstawić.

Kliknięcie jednego z przycisków spowoduje jego zamknięcie, a odpowiedź zostanie przechowana w zmiennej.

PRZEMIESZCZANIE ELEMENTU

Na końcówkę, słówko o metodzie "move", gdybyście chcieli przesunąć dowolny element o ileś pikseli w osi X i Y. Tak się to wpisuje:

[zmienna typu "QWidget"].move([liczba całkowita jako pozycja w osi X], [liczba całkowita jako pozycja w osi Y])

To spowoduje przesunięcie tekstu, przycisku, czegokolwiek innego o podaną liczbę pikseli od lewego górnego rogu okna.


Oto zakończenie prezentacji "bindingu" bibliotek "Qt" na Pythona. Pakiet "PyQt5" został naprawdę porządnie zrobiony na bazie pierwowzoru ("oryginał" napisano w C++) i znakomicie chowa istnienie jakichkolwiek wskaźników. Miejcie na uwadze, że możecie potrzebować odpowiedniej licencji na wykorzystanie komercyjne mimo tego, że pakiet jest darmowy. Korzystanie z łatwiejszej wersji bibliotek "Qt" nie zwalnia Was z obowiązku posiadania ważnej licencji twórców oryginału napisanego w C++.

PODOBNE ARTYKUŁY