PyQt5 vodič s primjerima: Izrada Python Grafička sučelja s Qt-om

⚡ Pametni sažetak

PyQt is a Python binding for the cross-platform Qt widget toolkit, letting developers build native desktop GUIs. It wraps more than six hundred C++ classes covering windows, buttons, layouts, signals, slots, and themes for Windows, macOSi Linux.

  • 🔘 What is PyQt: A Python binding for the Qt C++ framework, maintained by Riverbank Computing.
  • ☑️ verzije: PyQt4 binds Qt 4.x and 5.x; PyQt5 targets only the Qt 5.x series.
  • Instalacija: The pip package manager installs PyQt5 from a wheel with a single command.
  • 🧪 First app: QApplication and QWidget create and display a basic window on screen.
  • 🛠️ Signals and slots: Widget events raise signals that connect to Python slot functions.
  • 🤖 Spremno za umjetnu inteligenciju: PyQt builds desktop front-ends that run and visualize machine learning models.

Vodič za PyQt5

The sections below take you from PyQt basics through installation, core widgets, layouts, and themes with runnable examples.

Što je PyQt?

PyQt je Python binding of the open-source widget-toolkit Qt, which also functions as a cross-platform application development framework. Qt is a popular C++ framework for writing GUI applications for all major desktop, mobile, and embedded platforms (it supports Linux, Windows, macOS, Android, iOS, Raspberry Pi i više).

PyQt is free software developed and maintained by Riverbank Computing, a company based in England, whereas Qt is developed by a Finnish firm called The Qt Company.

Features of PyQt

Evo važnih značajki PyQt-a:

PyQt consists of more than six hundred classes covering a range of features such as:

  • Grafičko korisničko sučelje
  • SQL baze podataka
  • Web alati
  • XML obrada
  • Umrežavanje

These features can be combined to create advanced UIs as well as standalone applications. A lot of major companies across all industries use Qt. Some examples are LG, Mercedes, AMD, Panasonic, and Harman.

PyQt verzije

PyQt is available in two editions, PyQt4 and PyQt5. PyQt4 provides glue code for binding 4.x and 5.x versions of the Qt framework while PyQt5 provides a binding for only the 5.x versions. As a result, PyQt5 is not backward compatible with the deprecated modules of the older version. In this PyQt GUI tutorial, PyQt5 will be used for the demonstration of examples. Apart from these two versions, Riverbank Computing also provides PyQt3D—the Python bindings for the Qt3D framework. Qt3D is an application framework used to create real-time simulation systems with 2D/3D rendering.

Kako instalirati PyQt5

U ovom vodiču za PyQt5 vidjet ćemo dva načina instaliranja PyQt-a:

  • Korištenje Wheel datoteka
  • Izgradnja i instalacija iz izvora

Qt (izgovara se slatko) složen je sustav, a baza koda PyQt sadrži kompilirane C++ i Python code under the hood. As a result, it is a complicated process to build and install it from the source compared to other Python libraries. However, you can easily install PyQt5 using wheels.

Montaža s kotačima

Kotači su novi standard Python format pakiranja i distribucije. Jednostavno rečeno, kotač je ZIP arhiva s posebnim imenom i .whl ekstenzija datoteke. Kotači se mogu postaviti pomoću pipa (Pythonupravitelj paketa), koji je prema zadanim postavkama uključen u novije verzije Python.

Dakle, ako imate Python 3.4 ili noviji instaliran, već imate pip. Međutim, ako koristite stariju verziju Python, you must download and install pip before going forward. You can search for instructions at this link: https://pypi.org/project/pip/.

To install PyQt5:

Korak 1) Otvorite naredbeni redak.
Otvorite naredbeni redak ili PowerShell u svom Windows stroj.

instalirajte PyQt5

Korak 2) Upišite sljedeće.

 pip install PyQt5

Korak 3) Instalacija uspjela.
Ovaj korak u ovom vodiču za PyQt5 će preuzeti paket PyQt5 whl (oko 50 MB) i instalirati ga na vaš sustav.

instalirajte PyQt5

Alternativno, također možete preuzeti a Windows binary for the version of Python instaliran na vašem računalu.

Kada je dovršen, prijeđite na sljedeći odjeljak u ovom vodiču za PyQt5 da biste napisali svoju prvu GUI aplikaciju.

Osnovni PyQt Concepts i Programi

Now that you have successfully installed PyQt5 on your computer, you are ready to write Python GUI applications.

Let us start with a simple app in this PyQt5 tutorial which will display an empty window on your screen.

Fire up your Python IDLE i upišite sljedeće:

Program 1

import sys
from PyQt5.QtWidgets import QApplication, QWidget
if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()
    w.resize(300,300)
    w.setWindowTitle("Guru99")
    w.show()
    sys.exit(app.exec_())

Spremite ga kao app.py (naziv nije važan) i pritisnite F5 za pokretanje programa. Alternativno, samo dvaput kliknite spremljenu datoteku za pokretanje aplikacije. Ako ste sve ispravno napravili, otvorit će se novi prozor s naslovom Guru99 kao što je prikazano dolje.

Osnovni PyQt Concepts

Great! It is working. It is not much, but it is enough to grasp the basics. Now in this PyQt tutorial, let us see in detail what each of the lines in your program does.

from PyQt5.QtWidgets import QApplication, QWidget

Ova izjava uvozi sve module koji su vam potrebni za stvaranje GUI-ja u trenutni prostor naziva. Modul QtWidgets sadrži sve glavne widgete koje ćete koristiti u ovome Python Qt vodič.

app = QApplication(sys.argv)

Ovdje stvarate objekt klase QApplication. Ovaj korak je neophodan za PyQt5; svaka UI aplikacija mora stvoriti instancu QApplication, kao neku vrstu ulazne točke u aplikaciju. Ako ga ne izradite, prikazat će se pogreške.

sys.argv je popis parametara naredbenog retka koje možete proslijediti aplikaciji kada je pokrećete kroz ljusku ili tijekom automatizacije sučelja.

In this PyQt5 example, you did not pass any arguments to QApplication. Therefore, you can also replace it with the code below and not even have to import the sys module.

app = QApplication([])
w = QWidget()

Zatim napravimo objekt klase QWidget. QWidget je osnovna klasa svih UI objekata u Qt-u i gotovo sve što vidite u aplikaciji je widget. To uključuje dijaloge, tekstove, gumbe, trake i tako dalje. Značajka koja vam omogućuje dizajn složenih korisničkih sučelja je da widgeti mogu biti ugniježđeni, tj. možete imati widget unutar widgeta, koji je unutar još jednog widgeta. Vidjet ćete ovo na djelu u sljedećem odjeljku.

w.resize(300,300)

Metoda promjene veličine klase QWidget omogućuje vam postavljanje dimenzija bilo kojeg widgeta. U ovom slučaju promijenili ste veličinu prozora na 300 x 300 piksela.

Here, you should remember that widgets could be nested together; the outermost widget (i.e., the widget with no parent) is called a Window.

w.setWindowTitle("Guru99")

Metoda setWindowTitle() omogućuje vam prosljeđivanje niza znakova kao argumenta koji će postaviti naslov prozora na niz znakova koji prosljeđujete. U primjeru PyQt5, prikazat će se naslovna traka Guru99.

w.show()

show() jednostavno prikazuje widget na ekranu monitora.

sys.exit(app.exec_())

Metoda app.exec_() pokreće Qt/C++ petlja događaja. Kao što znate, PyQt je uglavnom napisan u C++ and uses the event loop mechanism to implement parallel execution. app.exec_() passes the control over to Qt, which will exit the application only when the user closes it from the GUI. That is why ctrl+c will not exit the application as in other Python programs. Since Qt has control over the app, Python events are not processed unless we set them up inside the application. Also, note that the exec method has an underscore in its name; this is because exec() was already a keyword in Python and the underscore resolves the naming conflict.

Iza praznih prozora

In the previous section, you saw how to make a basic widget in Qt. It is now time to make some more involved interfaces with which the users can truly interact. Again, fire up your IDLE i napiši sljedeće.

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QMessageBox

def dialog():
    mbox = QMessageBox()

    mbox.setText("Your allegiance has been noted")
    mbox.setDetailedText("You are now a disciple and subject of the all-knowing Guru")
    mbox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
            
    mbox.exec_()

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()
    w.resize(300,300)
    w.setWindowTitle("Guru99")
    
    label = QLabel(w)
    label.setText("Behold the Guru, Guru99")
    label.move(100,130)
    label.show()

    btn = QPushButton(w)
    btn.setText('Beheld')
    btn.move(110,150)
    btn.show()
    btn.clicked.connect(dialog)

    
    w.show()
    sys.exit(app.exec_())

Save the file as appone.py or anything you like and press F5 to run the program. If you have not made any mistakes, the IDLE otvorit će novi prozor s nekim tekstom i gumbom kao što je prikazano u nastavku.

Izvan praznog Windows

  1. Nakon što kliknete na gumb u prvom prozoru, otvorit će se novi okvir s porukom s tekstom koji ste napisali.
  2. Sada možete kliknuti na gumb Sakrij pojedinosti/Prikaži pojedinosti za uključivanje/isključivanje vidljivosti dodatnog teksta.

As you can see, since we had not set any window title in the message box, a default title was provided by Python sebe.

Now that it is working, let us take a look at the extra code that you have added to the previous PyQt5 example.

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QMessageBox

Ovo uvozi još neke widgete koje ste koristili u primjerima PyQt5, naime QLabel, QPushButton i QMessageBox.

def dialog():
    mbox = QMessageBox()

    mbox.setText("Your allegiance has been noted")
    mbox.setDetailedText("You are now a disciple and subject of the all-knowing Guru")
    mbox.setStandardButtons(QMessageBox.Ok | QMessageBox.Cancel)
            
    mbox.exec_()

Ovdje ste definirali metodu pod nazivom dialog koja stvara widget okvira s porukom i postavlja dio teksta na gumbe i druga polja.

Metoda dijaloga poziva se iz glavnog bloka programa kada se pritisne gumb u određenom widgetu (u ovom slučaju, btn PushButton). Događaj klika pokrenut na tom gumbu uzrokuje izvršavanje ove funkcije. Takva se funkcija u Qt-u naziva slot, a o njoj ćete naučiti više signali i mjesta u narednim paragrafima.

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = QWidget()
    w.resize(300,300)
    w.setWindowTitle("Guru99")

This is the main section of the app and, as in the previous example, you start by creating an instance of QApplication followed by a simple widget, i.e. an instance of QWidget.

label = QLabel(w)
    btn = QPushButton(w)

Dodali ste dva nova widgeta u ovu aplikaciju: QLabel i QPushButton. QLabel se koristi za ispis teksta koji se ne može uređivati ​​ili rezerviranih mjesta unutar widgeta, dok se QPushButton koristi za stvaranje gumba na koji se može kliknuti.

Ovdje je važno primijetiti da kada kreirate label i btn objekte, prosljeđujete objekt prozora (w) konstruktorima QLabel i QPushButton. Ovako funkcionira gniježđenje u PyQt5. Da biste stvorili widget unutar drugog widgeta, proslijedite referencu nadređenog widgeta podređenom konstruktoru.

label.move(100,130)
btn.move(110,150)

move() se koristi za postavljanje položaja widgeta u odnosu na njegov nadređeni widget. U prvom slučaju, oznaka će se pomaknuti 100px s lijeve strane i 130px s vrha prozora.

Slično, gumb će biti postavljen 110px od lijevog i 150px od vrha prozora. Ovaj primjer je grubi način postizanja izgleda i općenito se ne koristi u proizvodnji; ovdje je uključen samo u svrhu učenja. Qt podržava različite izglede koje ćete detaljno vidjeti u nadolazećim odjeljcima ovog vodiča za PyQt.

btn.clicked.connect(dialog)

Finally, this is an example of signals and slots in Qt. In GUI-based applications, functions are executed based on the actions performed by the user, like hovering over an element or clicking a button. These actions are called događaji. Podsjetimo se da metoda app.exec_() prenosi kontrolu na Qt događaj petlja. To je ono čemu služi petlja događaja: osluškivanje događaja i izvođenje radnji kao odgovor.

Kad god se dogodi neki događaj, poput klika korisnika na gumb, odgovarajući Qt widget pokreće a signal. Ovi se signali mogu povezati s Python Funkcije (kao dijaloška funkcija u ovom primjeru) tako da se funkcija izvršava kada se aktivira signal. Te se funkcije nazivaju mjesta na Qt jeziku.

Subsequently, the basic syntax to trigger a slot function in response to the signal from an event is as follows:

 widget.signal.connect(slot)

This means that whenever a signal pokreće a widget, povezani prorez funkcija će se izvršiti. Ukratko, Qt koristi signale i utore za komunikaciju između objekata i olakšavanje ponovne upotrebe komponenti i interaktivnosti.

Sada kada znate kako ugniježditi widgete i implementirati interakcije pomoću signala i utora, evo popisa korisnih widgeta i drugih klasa koje možete koristiti u svojim PyQt aplikacijama.

Komponente i widgeti

Postoji veliki broj widgeta dostupnih u PyQt-u za izradu GUI aplikacija. Međutim, s PyQt5, došlo je do preslagivanja klasa u različite module i revizija u licencama.

Therefore, it is crucial to have a high-level view of the structure of PyQt5. In this section, you will see how PyQt5 is organized internally and learn about the different modules, libraries, and API classes provided by PyQt5.

PyQt5 struktura direktorija

PyQt5 struktura direktorija

Ovo su temeljni moduli koje koristi PythonQt vezivanje, posebno PyQt5.

  • Qt: It combines all the classes/modules mentioned below into a single module. It considerably increases the memory used by the application. However, it is easier to manage the framework by only importing one module.
  • QtCore: Sadrži osnovne negrafičke klase koje koriste drugi moduli. Ovo je mjesto gdje se implementiraju Qt petlja događaja, signali i povezivanje utora, itd.
  • QtWidgeti: Contains most of the widgets available in PyQt5.
  • QtGui: Sadrži GUI komponente i proširuje QtCore modul.
  • QtNetwork: Sadrži klase koje se koriste za implementaciju mrežnog programiranja kroz Qt. Podržava TCP poslužitelje, TCP utičnice, UDP utičnice, SSL rukovanje, mrežne sesije i DNS pretraživanja.
  • QtMultimedia: provides low-level multimedia functionality.
  • QtSql: implementira integraciju baze podataka za SQL baze podataka. Podržava ODBC, MySQL, Oracle, SQLitei PostgreSQL.

PyQt5 Widgeti

Here is a list of the most frequently used widgets in PyQt5:

  • QLineEdit: Ovo je polje za unos koje korisniku omogućuje unos jednog retka teksta.
line = QLineEdit()
  • QRadioButton: This is an input field with a selectable button, similar to the radio buttons in HTML.
rad = QRadioButton("button title")
rad.setChecked(True)  #to select the button by default.
  • QComboBox: Koristi se za prikaz padajućeg izbornika s popisom stavki koje je moguće odabrati.
drop = QComboBox(w)
drop.addItems(["item one", "item two", "item three"])
  • QCheckBox: Prikazuje kvadratni okvir koji se može odabrati ispred oznake koja je označena ako je odabrana, slično radio gumbima.
check = QCheckBox("button title")
  • QMenuBar: It displays a horizontal menu bar at the top of a window. You can only add objects of the QMenu class to this bar. Those QMenu objects can further contain strings, QAction objects or other QMenu objects.
  • QToolBar: To je vodoravna traka ili okno koje se može pomicati unutar prozora. Može sadržavati gumbe i druge widgete.
  • QTab: It is used to break down the contents of a window into multiple pages that can be accessed through different tabs on top of the widget. It consists of two sections: the tab bar and the tab page.
  • QScrollBarKoristi se za stvaranje traka za pomicanje koje omogućuju korisniku pomicanje gore i dolje unutar prozora. Sastoji se od pomičnog klizača, klizača track i dva gumba za pomicanje klizača gore ili dolje.
scroll = QScrollBar()
  • QSplitter: Razdjelnici se koriste za odvajanje sadržaja prozora tako da su widgeti ispravno grupirani i da ne izgledaju pretrpano. QSplitter je jedan od primarnih rukovatelja izgledom dostupnih u PyQt5 i koristi se za horizontalno i vertikalno dijeljenje sadržaja.
  • QDock: A dock widget is a sub-window with two properties: it can be moved within the main window, and it can be docked outside the parent window to another location on the screen.

Izgledi i teme

U prethodnim primjerima PyQt5 samo ste koristili metode move() i resize() za postavljanje položaja widgeta u vašem GUI-ju.

Međutim, PyQt ima robustan mehanizam za upravljanje izgledom koji se može koristiti za stvaranje naprednih korisničkih sučelja za aplikacije. U ovom odjeljku naučit ćete o dvije važne klase koje se koriste u Qt-u za stvaranje i upravljanje izgledima.

  1. QBoxRaspored
  2. QGridLayout

QBoxRaspored

QBoxRaspored se koristi za poravnavanje podređenih widgeta izgleda u vodoravni ili okomiti red. Dvije interesne klase koje nasljeđuju od QBoxRaspored je:

  • QHBoxIzgled: koristi se za vodoravno postavljanje podređenih widgeta.
  • QVBoxRaspored: koristi se za okomito postavljanje podređenih widgeta.

Na primjer, ovako su tri gumba poravnata s QHBoxIzgled će izgledati.

QBoxRaspored

import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":

    app = QApplication([])
    w = QWidget()
    w.setWindowTitle("Musketeers")

    btn1 = QPushButton("Athos")
    btn2 = QPushButton("Porthos")
    btn3 = QPushButton("Aramis")

    hbox = QHBoxLayout(w)

    hbox.addWidget(btn1)
    hbox.addWidget(btn2)
    hbox.addWidget(btn3)

    w.show()

    sys.exit(app.exec_())

A ovako će izgledati u QV-uBoxIzgled.

QBoxRaspored

import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":

    app = QApplication([])
    w = QWidget()
    w.setWindowTitle("Musketeers")

    btn1 = QPushButton("Athos")
    btn2 = QPushButton("Porthos")
    btn3 = QPushButton("Aramis")

    vb = QVBoxLayout(w)

    vb.addWidget(btn1)
    vb.addWidget(btn2)
    vb.addWidget(btn3)

    w.show()

    sys.exit(app.exec_())

Jedina funkcija koja u ovom trenutku treba bilo kakvo objašnjenje je metoda addWidget(). Koristi se za umetanje widgeta u HBox ili VBox raspored. Također se koristi u drugim izgledima gdje uzima različit broj parametara kao što ćete vidjeti u sljedećem odjeljku. Widgeti će se pojaviti unutar izgleda redoslijedom kojim ste ih umetnuli.

QGridLayout

QGridLayout se koristi za stvaranje sučelja u kojima su widgeti postavljeni u obliku mreže (poput matrice ili 2D polja). Za umetanje elemenata u raspored rešetke, možete upotrijebiti matrični prikaz za definiranje broja redaka i stupaca u rešetki, kao i položaj tih elemenata.

Na primjer, da biste stvorili mrežu 3*3 (tj. mrežu s tri retka i tri stupca), napisat ćete sljedeći kod:

Import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":
    app = QApplication([])

    w = QWidget()

    grid = QGridLayout(w)

    for i in range(3):
        for j in range(3):
            grid.addWidget(QPushButton("Button"),i,j)


    w.show()
    sys.exit(app.exec_())

Ovo će biti izlaz:

QGridLayout

The addWidget() method in the grid layout takes these arguments:

  • Objekt widgeta koji želite dodati u mrežu
  • X-koordinata objekta
  • Y-koordinata objekta
  • The row-span (default = 0)
  • The col-span (default = 0)

To understand it better, you can manually insert each widget as shown below.

import sys
from PyQt5.QtWidgets import *

if __name__ == "__main__":
    app = QApplication([])

    w = QWidget()

    grid = QGridLayout(w)
    grid.addWidget(QPushButton("Button one"),0,0)
    grid.addWidget(QPushButton("Button two"),0,1)
    grid.addWidget(QPushButton("Button three"),1,0)
    grid.addWidget(QPushButton("Button four"),1,1)


    w.show()
    sys.exit(app.exec_())

Ovako će izgledati mreža:

QGridLayout

Također možete proslijediti parametre rowspan i colspan u addWidget() kako biste obuhvatili više od jednog retka ili stupca.

Na primjer,

grid.addWidget(QPushButton("Button five"),2,0,1,0)

Ovo će stvoriti gumb koji se proteže preko oba stupca.

QGridLayout

Teme

PyQt5 dolazi s nekim ugrađenim temama koje možete koristiti u svojim aplikacijama. The setStyle() metoda koja se poziva na instanci QApplication koristi se za postavljanje određene teme u vašu aplikaciju.

For example, adding the following line of code will change the theme of your application from default to Fusion.

app.setStyle("Fusion")

This is how the previous example will look in the Fusion Theme.

Teme

Another useful function for theming your apps is the setPalette() method. Here is the code for changing the color of different widgets using setPalette().

import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QPalette

if __name__ == "__main__":
    app = QApplication([])
    app.setStyle("Fusion")
    
    qp = QPalette()
    qp.setColor(QPalette.ButtonText, Qt.black)
    qp.setColor(QPalette.Window, Qt.black)
    qp.setColor(QPalette.Button, Qt.gray)
    app.setPalette(qp)

    w = QWidget()

    grid = QGridLayout(w)
    grid.addWidget(QPushButton("Button one"),0,0)
    grid.addWidget(QPushButton("Button two"),0,1)
    grid.addWidget(QPushButton("Button three"),1,0)
    grid.addWidget(QPushButton("Button four"),1,1)


    w.show()
    sys.exit(app.exec_())

Evo rezultata.

Teme

Da biste koristili metodu setPalette(), prvo trebate definirati paletu. To se postiže stvaranjem objekta klase QPalette.

 qp = QPalette()

Notice that the QPalette class belongs to the QtGui module and you will need to import it for this to work. Once you have created the QPalette object, use the setColor() method to pass the name of a widget whose color you want to change and the color you want to be set.

 qp.setColor(QPalette.Window, Qt.black)

This will change the color of the window to black. After you have defined your color scheme, use the setPalette() function to apply the palette to your application.

app.setPalette(qp)

To je sve što trebate učiniti ako želite stvoriti neke osnovne teme za svoju aplikaciju. PyQt vam također omogućuje korištenje tablica stilova za definiranje izgleda vaših widgeta. Ako ste upoznati s CSS-om, možete jednostavno definirati napredne stilove za svoju aplikaciju koristeći Qt Style Sheets.

Pitanja i odgovori

Tkinter ships with Python and suits small, simple tools. PyQt offers far more widgets, a visual designer, and a native look, making it better for large, professional desktop applications — at the cost of a steeper learning curve and GPL licensing.

Both bind the same Qt libraries and share almost identical APIs. The key difference is licensing: PyQt uses GPL or a paid commercial license, while PySide (Qt for Python) uses the LGPL, allowing closed-source distribution for free.

Qt Designer is a drag-and-drop tool that saves your layout as an XML .ui file. Convert it to Python with the pyuic5 utility, or load it at runtime using uic.loadUi().

PyQt6 binds the newer Qt 6 framework and requires fully scoped enums, such as Qt.AlignmentFlag.AlignLeft. PyQt5 targets Qt 5 and remains the most widely used version. PyQt6 also drops some deprecated modules and adds better high-DPI support.

Use PyInstaller: run pyinstaller –onefile –windowed app.py to bundle your script, PyQt5, and the Qt libraries into a single executable. The –windowed flag hides the console. fbs and cx_Freeze are popular alternatives.

PyQt5 is beginner-friendly if you know basic Python. Understanding classes, objects, and the signals-and-slots pattern helps most. Qt Designer lets you build layouts visually, so you can create working windows before mastering every widget by hand.

PyQt builds desktop front-ends for machine learning models — dashboards that load data, trigger training or inference, and display predictions, charts, or images. It pairs well with NumPy, pandas, and Matplotlib, wrapping a trained model in a friendly GUI.

Yes. GitHub Copilot autocompletes common PyQt5 patterns like QApplication setup, widget creation, and signal-slot connections from a comment or function name. Always run the generated window to confirm layouts and event handlers behave as intended.

Sažmite ovu objavu uz: