Как установить pyqt6 на windows

Before you start coding you will first need to have a working installation of PyQt6 on your system. If you don’t have PyQt6 set up yet, the following sections will guide you through how to do this on Windows, macOS and Linux.

This guide is also available for macOS and Linux.

Note that the following instructions are only for installation of the GPL licensed version of PyQt. If you need to use PyQt in a non-GPL project you will
need to purchase an alternative license from Riverbank Computing to release your software.

Installation on Windows

PyQt6 for Windows can be installed as for any other application or library. As of Qt 5.6 installers are available to install via PyPi, the Python Package archive. To install PyQt6 from Python3 simply run —

After install is finished, you should be able to run python and import PyQt6.

Note that if you want access to Qt Designer or Qt Creator you will need to download this from https://qt.io/download[the Qt downloads site].

Over 10,000 developers have bought Create GUI Applications with Python & Qt!

[[ discount.discount_pc ]]% OFF for
the next [[ discount.duration ]]
[[discount.description ]]
with the code [[ discount.coupon_code ]]

Purchasing Power Parity

Developers in [[ country ]] get [[ discount.discount_pc ]]% OFF on all books & courses
with code [[ discount.coupon_code ]]

Project description

Qt is set of cross-platform C++ libraries that implement high-level APIs for
accessing many aspects of modern desktop and mobile systems. These include
location and positioning services, multimedia, NFC and Bluetooth connectivity,
a Chromium based web browser, as well as traditional UI development.

PyQt6 is a comprehensive set of Python bindings for Qt v6. It is implemented
as more than 35 extension modules and enables Python to be used as an
alternative application development language to C++ on all supported platforms
including iOS and Android.

PyQt6 may also be embedded in C++ based applications to allow users of those
applications to configure or enhance the functionality of those applications.

Author

PyQt6 is copyright (c) Riverbank Computing Limited. Its homepage is
https://www.riverbankcomputing.com/software/pyqt/.

Support may be obtained from the PyQt mailing list at
https://www.riverbankcomputing.com/mailman/listinfo/pyqt/.

License

PyQt6 is released under the GPL v3 license and under a commercial license that
allows for the development of proprietary applications.

Documentation

The documentation for the latest release can be found
here.

Installation

The GPL version of PyQt6 can be installed from PyPI:

pip install PyQt6

pip will also build and install the bindings from the sdist package but
Qt’s qmake tool must be on PATH.

The sip-install tool will also install the bindings from the sdist package
but will allow you to configure many aspects of the installation.

Download files

Download the file for your platform. If you’re not sure which to choose, learn more about installing packages.

Source Distribution

Built Distributions

In this PyQt6 tutorial we are going to learn about PyQt6 Introduction & Installation, first we are going to talk about PyQt6 and after that we are going to learn about the installation process of PyQt6, at the end we create our first GUI window.

What is PyQt (PyQt6) ? 

PyQt is a set of Python bindings for The Qt Company’s Qt application framework and runs on all platforms supported by Qt including Windows, macOS, Linux, iOS and Android. PyQt6 supports Qt v6, PyQt5 supports Qt v5 and PyQt4 supports Qt v4. The bindings are implemented as a set of Python modules and contain over 1,000 classes, before this there were PyQt4 and PytQt5 now we have the latest version and that is PyQt6. PyQt brings together the Qt C++ cross-platform application framework and the cross-platform interpreted language Python. Qt is more than a GUI toolkit. It includes abstractions of network sockets, threads, Unicode, regular expressions, SQL databases, SVG, OpenGL, XML, a fully functional web browser, a help system, a multimedia framework, as well as a rich collection of GUI widgets. Qt also includes Qt Designer, a graphical user interface designer. PyQt is able to generate Python code from Qt Designer. It is also possible to add new GUI controls written in Python to Qt Designer. Python is a simple but powerful object-orientated language. Its simplicity makes it easy to learn, but its power means that large and complex applications can be created. Its interpreted nature means that Python programmers are very productive because there is no edit/compile/link/run development cycle.

Installation

You can easily use pip for the installation of PyQt6.

Now let’s create our first window, this is our code for our GUI window.

from PyQt6.QtWidgets import QApplication, QWidget

import sys

app = QApplication(sys.argv)

window = QWidget()

window.show()

sys.exit(app.exec())

Every PyQt6 application should create the object of the QApplication class, you can give sys.argv as parameter of you can leave it blank, if you want to use command prompt to run the application than you need to give sys.argv, sys. argv is a list in Python, which contains the command-line arguments passed to the script.

In here we need to create the object of QWidget class, there are three types of classes that you can create in PyQt, we have QWidget, QDialog and QMainWindow.

  • QWidget: The QWidget class is the base class of all user interface
    objects, The widget is the important point of the user interface:
    it receives mouse, keyboard and other events from the window
    system, and paints a representation of itself on the screen.
  • QDialog: The QDialog class is the base class of dialog window
    and A dialog window is a top-level window mostly used for
    short-term tasks and brief communications with the user.
    QDialogs may be modal or modeless.
  • QMainWindow: The QMainWindow class provides a main application window
    A main window provides a framework for building an
    application’s user interface. PyQt5 has QMainWindow and its
    related classes for main window management. QMainWindow has
    its own layout to which you can add QToolBars, QDockWidgets,
    a QMenuBar, and a QStatusBar.

After creating of the window you need to show your window.

Now this is the big difference between PyQt5 and PyQt6, in PyQt5 you need to use app.exec_(), but in PyQt6 we can not use this, we need to use app.exec().

Run the complete code and this is the result.

PyQt6 Introduction & Installation

PyQt6 Introduction & Installation

Now we are going to add more functionality to our code and we want to use Object Oriented terminology for creating of our Window in PyQt6.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

from PyQt6.QtWidgets import QApplication, QWidget

from PyQt6.QtGui import QIcon

import sys

    def __init__(self):

        super().__init__()

        self.setWindowTitle(«Geekscoders.com — PyQt6 Window»)

        self.setWindowIcon(QIcon(«qt.png»))

        self.setGeometry(500,200, 400,300)

        #self.setFixedHeight(200)

        #self.setFixedWidth(200)

        #self.setStyleSheet(‘background-color:red’)

        stylesheet = (

            «background-color : red;»

        )

        self.setStyleSheet(stylesheet)

app = QApplication(sys.argv)

window = Window()

window.show()

sys.exit(app.exec())

We have created a main window class that extends from QWidget, and we have added the title, window icon, geometry, fixed width and height, also color for the window background.

__init__” is a reserved method in python classes. It is called as a constructor in object oriented programming. This method is called when an object is created from a class and it allows the class to  initialize the attributes of the class.

The super() function is used to give access to methods and properties of a parent class. And the super() function makes class inheritance more manageable.

The word ‘self’ is used to represent the instance of the class. By using the “self” keyword we can access the attributes and methods of the class in python.

Run the complete code and this is the result.

PyQt6 Introduction & Installation

PyQt6 Introduction & Installation

Creating Window With Qt Designer

Now we want to create our window using Qt Designer, Qt Designer is the Qt tool for designing and building graphical user interfaces (GUIs) with Qt Widgets. You can compose and customize your windows or dialogs in a what-you-see-is-what-you-get (WYSIWYG) manner, and test them using different styles and resolutions.

First of all you need to install Qt Designer, because by default we don’t have Qt Designer, you need to install pyqt5 tools and you can easily use pip for the installation, even tough we are using PyQt6, but you can use pyqt5 tools with PyQt6.

After installation, you need to write pyqt5designer or pyqt5designer.exe in your terminal and you will see your Qt Designer.

PyQt6 Qt Designer

PyQt6 Qt Designer

You can right click on the window, after that click on Changestylesheet for changing the background color.

QWidget {

backgroundcolor:«#550000»

}

Now you need to save your file, you will see that you have .ui extension, now there are two ways that you can use from this design, the first way is loading your .ui file, for this we are going to use uic module.

Loading Qt Designer UI File 

Create a Python file, i have called it WindowLoad.py and you need to add this code, make sure that you have already saved the ui file in the same directory of the Python file.

from PyQt6.QtWidgets import QApplication, QWidget

from PyQt6 import uic

import sys

class UI(QWidget):

    def __init__(self):

        super().__init__()

        #this is used for loading ui file

        uic.loadUi(«Window.ui», self)

app = QApplication([])

window = UI()

window.show()

sys.exit(app.exec())

You can see that we have used uic.loadUi(“Window.ui”, self), you need to give the ui file name.

Run the code and this is the result.

Qt Designer Window

Qt Designer Window

Converting UI File to Py File.

In this way we are using pyuic6 module, it is located in the script folder of your python installation, after that run this code in your terminal.

pyuic6 x Window.ui o mywindow.py

And you will see the converted file, this is my converted file.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

from PyQt6 import QtCore, QtGui, QtWidgets

class Ui_Form(object):

    def setupUi(self, Form):

        Form.setObjectName(«Form»)

        Form.resize(400, 300)

        Form.setStyleSheet(«QWidget {\n»

«\n»

«background-color:\»#550000\»\n»

«\n»

«}»)

        self.retranslateUi(Form)

        QtCore.QMetaObject.connectSlotsByName(Form)

    def retranslateUi(self, Form):

        _translate = QtCore.QCoreApplication.translate

        Form.setWindowTitle(_translate(«Form», «Qt Designer Window»))

if __name__ == «__main__»:

    import sys

    app = QtWidgets.QApplication(sys.argv)

    Form = QtWidgets.QWidget()

    ui = Ui_Form()

    ui.setupUi(Form)

    Form.show()

    sys.exit(app.exec())

Summary: in this tutorial, you’ll learn how to use the Qt Designer tool to design user interfaces for PyQt applications.

Install the PyQt tools

PyQt6 tools are compatible with Python 3.9 at the time of writing this tutorial. Therefore, you need to install Python 3.9 to continue the tutorial.

Note that if you have Python 3.10 or higher, you can install Python 3.9 in a separate directory and use PyQt6 tools. For example, you can install Python 3.9 in the C:\Python39 directory on Windows.

Create a new virtual environment

First, create a directory to host the PyQt6 projects e.g., D:\pyqt6

mkdir pyqt6Code language: Python (python)

Second, create a virtual environment using Python 3.9 using the venv module:

python -m venv D:\pyqt6\pyqt6-envCode language: Python (python)

Activate the virtual environment

First, navigate to the pyqt6-env virtual environment directory:

cd D:\pyqt6\pyqt6-envCode language: Python (python)

Second, navigate to the Scripts folder:

cd ScriptsCode language: Python (python)

Third, activate the virtual environment by executing the activate.bat file:

activateCode language: Python (python)

It’ll show the following on Windows:

(pyqt6-env) d:\pyqt6\pyqt6-env\Scripts>Code language: Python (python)

Install PyQt6 and its tools

First, execute the following command to install pyqt6 package in the pyqt6-env virtual environment:

pip install pyqt6Code language: Python (python)

Second, install the pyqt6-tools package that contains the Qt Designer and other related tools:

pip install pyqt6-toolsCode language: Python (python)

The pyqt-tools package will install the Qt Designer in the following location:

D:\pyqt6\pyqt6-env\Lib\site-packages\qt6_applications\Qt\bin\designer.exeCode language: Python (python)

Third, execute the pyuic6 command (within the pyqt6-env virtual environment) to check the version:

pyuic6 -VCode language: Python (python)

And you’ll see the following output:

6.1.0Code language: Python (python)

The pyuic6 is a tool for converting a design file (.ui) generated by Qt Designer to a Python file (.py).

Launch the Qt Designer

From the Shell, type the designer command to launch the Qt Designer:

(pyqt6-env) d:\pyqt6\pyqt6-env\Scripts>designerCode language: Python (python)

The Qt Designer will look like this:

Qt Designer

Creating a login form

We’ll create a simple login form using the Qt designer and load it into our Python program.

First, select File > New or press Ctrl-N keyboard shortcut to open the New Form dialog:

Qt Designer - New

Second, select the Widget from the templates\forms and click the Create button:

Qt Designer - Create a Widget

It’ll create a QWidget as follows:

Qt Designer - Form

You can set the layout for the widget, and drag and drop widgets from the Widget Box to the form.

Setting widget properties

In the Property Editor, you can set a name for the widget e.g., login_form

Qt Designer - Widget Properties

and the window title:

Qt Designer -Window Title

Adding widgets to the login form

First, add the widgets QLabel, QLineEdit, and QPushButton to the form:

Qt Designer - widgets

The following table lists the fields, their types, and names:

Field Widget Object Name
Login Window QWidget login_form
Email Address QLineEdit email_line_edit
Password QLineEdit password_line_edit
Login Button QPushButton btn_login

Second, set the echo mode of the password field to Password:

Qt Designer - Password field

Third, right-click the widget and set its layout to Form Layout:

Qt Designer - Set Form Layout

The form will change to the following:

Qt Designer - Form Layout

Fourth, change the size of the Login button by setting its Horizontal Size Policy to Fixed:

Qt Designer - Change the button size

Fifth, save the form to the D:\pyqt6 directory as login_form.ui file.

Sixth, select Form > Preview... menu or the keyboard shortcut Ctrl-R to preview the form:

Qt Designer - Preview

Finally, close the Qt Designer.

There’re two ways to use the login_form.ui from a Python program:

  1. Convert the .ui file to Python code and use the generated code from the program.
  2. Directly use the .ui file in the program.

Converting .ui file to Python code

First, execute the following command to convert the login_form.ui file to login_form.py file:

pyuic6 -o login_form.py login_form.uiCode language: Python (python)

Note that you need to run the pyuic6 from the pyqt6-env virtual environment.

The pyuic6 generated the login_form.py from the login_form.ui file. The login_form.py contains the following generated Python code:

# Form implementation generated from reading ui file 'login_form.ui'
#
# Created by: PyQt6 UI code generator 6.1.0
#
# WARNING: Any manual changes made to this file will be lost when pyuic6 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt6 import QtCore, QtGui, QtWidgets


class Ui_login_form(object):
    def setupUi(self, login_form):
        login_form.setObjectName("login_form")
        login_form.resize(269, 108)
        self.formLayout = QtWidgets.QFormLayout(login_form)
        self.formLayout.setObjectName("formLayout")
        self.label = QtWidgets.QLabel(login_form)
        self.label.setObjectName("label")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label)
        self.email_line_edit = QtWidgets.QLineEdit(login_form)
        self.email_line_edit.setObjectName("email_line_edit")
        self.formLayout.setWidget(0, QtWidgets.QFormLayout.ItemRole.FieldRole, self.email_line_edit)
        self.label_2 = QtWidgets.QLabel(login_form)
        self.label_2.setObjectName("label_2")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.LabelRole, self.label_2)
        self.password_line_edit = QtWidgets.QLineEdit(login_form)
        self.password_line_edit.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password)
        self.password_line_edit.setObjectName("password_line_edit")
        self.formLayout.setWidget(1, QtWidgets.QFormLayout.ItemRole.FieldRole, self.password_line_edit)
        self.btn_login = QtWidgets.QPushButton(login_form)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Fixed, QtWidgets.QSizePolicy.Policy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.btn_login.sizePolicy().hasHeightForWidth())
        self.btn_login.setSizePolicy(sizePolicy)
        self.btn_login.setObjectName("btn_login")
        self.formLayout.setWidget(2, QtWidgets.QFormLayout.ItemRole.FieldRole, self.btn_login)

        self.retranslateUi(login_form)
        QtCore.QMetaObject.connectSlotsByName(login_form)

    def retranslateUi(self, login_form):
        _translate = QtCore.QCoreApplication.translate
        login_form.setWindowTitle(_translate("login_form", "Login"))
        self.label.setText(_translate("login_form", "Email Address"))
        self.label_2.setText(_translate("login_form", "Password"))
        self.btn_login.setText(_translate("login_form", "Login"))Code language: Python (python)

It’s important to note that you should never manually change the login_form.py file. Because all your manual adjustments will be lost if you edit the login_form.ui in the Qt Designer and regenerate the login_form.py again.

Second, create the login.py file and import the login_ui.py file:

import sys
from PyQt6.QtWidgets import QApplication, QWidget
from login_form import Ui_login_form


class Login(QWidget):
    def __init__(self):
        super().__init__()

        # use the Ui_login_form
        self.ui = Ui_login_form()       
        self.ui.setupUi(self)       
        
        # show the login window
        self.show()
    

if __name__ == '__main__':
    app = QApplication(sys.argv)
    login_window = Login()
    sys.exit(app.exec())Code language: Python (python)

How it works.

  1. Import Ui_login_form class from the login_form.py file
  2. Create a new instance of the Ui_login_form and call the setupUi() method to set up the user interfaces.

Third, execute the login.py file:

python login.pyCode language: Python (python)

It’ll show the login window:

Qt Designer - Login Window

Referencing child widgets

To use the child widgets of Ui_login_form widget, you reference their names using the self.ui variable.

For example, you can add a simple authentication when the user enters an email address and password and click the Login button as follows:

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QMessageBox
from login_form import Ui_login_form


class Login(QWidget):
    def __init__(self):
        super().__init__()

        # use the Ui_login_form
        self.ui = Ui_login_form()       
        self.ui.setupUi(self)       

        # authenticate when the login button is clicked
        self.ui.btn_login.clicked.connect(self.authenticate)
        
        # show the login window
        self.show()

    def authenticate(self):
        email = self.ui.email_line_edit.text()
        password = self.ui.password_line_edit.text()

        if email == '[email protected]' and password == '123456':
            QMessageBox.information(self, 'Success',"You're logged in!")
        else:
            QMessageBox.critical(self, 'Error',"Invalid email or password.")
    

if __name__ == '__main__':
    app = QApplication(sys.argv)
    login_window = Login()
    sys.exit(app.exec())Code language: Python (python)

How it works.

First, connect the clicked signal of the button to the authenticate method. Notice that we reference the btn_login button via the self.ui variable:

self.ui.btn_login.clicked.connect(self.authenticate)Code language: Python (python)

Second, define the authenticate() method that gets values from the email_line_edit and password_line_edit and perform a simple check of these values against hard-coded values:

def authenticate(self):
    email = self.ui.email_line_edit.text()
    password = self.ui.password_line_edit.text()

    if email == '[email protected]' and password == '123456':
        QMessageBox.information(self, 'Success',"You're logged in!")
    else:
        QMessageBox.critical(self, 'Error',"Invalid email or password.")Code language: Python (python)

Security Notice: Never do this in real applications.

Besides creating an instance of the Ui_login_form inside the login window, you can inherit the Ui_login_form window using multiple inheritances and directly reference the child widgets like this:

import sys
from PyQt6.QtWidgets import QApplication, QWidget, QMessageBox
from login_form import Ui_login_form


class Login(QWidget,Ui_login_form):
    def __init__(self):
        super().__init__()

        # setup the UI
        self.setupUi(self)       

        # authenticate when the login button is clicked
        self.btn_login.clicked.connect(self.authenticate)
        
        # show the login window
        self.show()

    def authenticate(self):
        email = self.email_line_edit.text()
        password = self.password_line_edit.text()

        if email == '[email protected]' and password == '123456':
            QMessageBox.information(self, 'Success',"You're logged in!")
        else:
            QMessageBox.critical(self, 'Error',"Invalid email or password.")
    

if __name__ == '__main__':
    app = QApplication(sys.argv)
    login_window = Login()
    sys.exit(app.exec())Code language: Python (python)

Using .ui file directly

Another way to use the design generated by the Qt Designer is to load the .ui file directly using the loadUi() function of the uic module:

from PyQt6.QtWidgets import QApplication, QWidget, QMessageBox
from PyQt6 import uic
import sys

class Login(QWidget):
    def __init__(self):
        super().__init__()
        self.ui = uic.loadUi('login_form.ui', self)

        # authenticate when the login button is clicked
        self.ui.btn_login.clicked.connect(self.authenticate)

        self.show()
    
    def authenticate(self):
        email = self.email_line_edit.text()
        password = self.password_line_edit.text()

        if email == '[email protected]' and password == '123456':
            QMessageBox.information(self, 'Success',"You're logged in!")
        else:
            QMessageBox.critical(self, 'Error',"Invalid email or password.")

if __name__ == '__main__':
    app = QApplication(sys.argv)
    login_window = Login()
    sys.exit(app.exec())Code language: Python (python)

The loadUi() function returns an instance of the QWidget and you can reference the child widgets via the self.ui variable.

When you should use Qt Designer

The .ui file generated by the Qt designer creates an abstraction layer between the available widget and the code that consumes it.

Therefore, if you are starting out with PyQt, you should code the UI manually instead of using Qt Designer. By doing this, you know exactly what widgets are available in the application.

However, if you’re familiar with PyQt and work on a large application, you should use Qt Designer to create a good design and improve productivity.

Summary

  • Use Qt Designer to design user interfaces for large applications.
  • Use the pyuic6 tool to convert a .ui file into a Python source code file.
  • Use loadUi() function of the uic module to load the .ui file directly.

Did you find this tutorial helpful ?

К старту курса по разработке на Python делимся детальным руководством по работе с современным PyQt. Чтобы читать было удобнее, мы объединили несколько статей в одну:

  1. Первое приложение

  2. Слоты и сигналы

  3. Виджеты

За подробностями приглашаем под кат.


Простое приложение Hello World! на Python и Qt6

PyQt — это библиотека Python для создания приложений с графическим интерфейсом с помощью инструментария Qt. Созданная в Riverbank Computing, PyQt является свободным ПО (по лицензии GPL) и разрабатывается с 1999 года. Последняя версия PyQt6 — на основе Qt 6 — выпущена в 2021 году, и библиотека продолжает обновляться. Это руководство можно также использовать для PySide2, PySide6 и PyQt5.

Сегодня используются две основные версии: PyQt5 на основе Qt5 и PyQt6 на основе Qt6. Обе почти полностью совместимы, за исключением импорта и отсутствия поддержки некоторых продвинутых модулей из Qt6. В PyQt6 вносятся изменения в работу пространств имён и флагов, но ими легко управлять. В этом руководстве мы узнаем, как использовать PyQt6 для создания настольных приложений.

Сначала создадим несколько простых окон на рабочем столе, чтобы убедиться, что PyQt работает, и разберём базовые понятия. Затем кратко изучим цикл событий и то, как он связан с программированием графического интерфейса на Python. В заключение поговорим о QMainWindow с полезными элементами интерфейса, такими как панели инструментов и меню. Подробно я расскажу о них в следующих руководствах.

Создание приложения

Установка PyQt:

pip install pyqt6
# и на будущее
pip install pyqt-tools

Сначала создадим новый файл Python с любым названием (например app.py) и сохраним его. Исходный код приложения показан ниже. Введите его полностью и постарайтесь не ошибиться. Если что-то напутаете, Python укажет, что именно:

from PyQt6.QtWidgets import QApplication, QWidget

import sys # Только для доступа к аргументам командной строки

# Приложению нужен один (и только один) экземпляр QApplication.
# Передаём sys.argv, чтобы разрешить аргументы командной строки для приложения.
# Если не будете использовать аргументы командной строки, QApplication([]) тоже работает
app = QApplication(sys.argv)

# Создаём виджет Qt — окно.
window = QWidget()
window.show()  # Важно: окно по умолчанию скрыто.

# Запускаем цикл событий.
app.exec()


# Приложение не доберётся сюда, пока вы не выйдете и цикл
# событий не остановится.

Запускаем приложение из командной строки, как и любой скрипт Python:

python3 app.py

Выполнив его, мы увидим окно. В Qt автоматически создаётся окно с обычным оформлением, возможностью его перетаскивать и менять размер. То, что вы увидите, зависит от платформы, где этот пример выполняется. Вот как отображается это окно на Windows, macOS и Linux (Ubuntu):

Окно на Windows, macOS и Linux

Окно на Windows, macOS и Linux

Разбор кода

Пройдём код построчно, чтобы понять, что именно происходит. Сначала мы импортируем классы PyQt для приложения: здесь это обработчик приложения QApplication и базовый пустой виджет графического интерфейса QWidget (оба из модуля QtWidgets):

from PyQt6.QtWidgets import QApplication, QWidget

Основные модули для Qt: QtWidgets, QtGui и QtCore.

Возможен ещё from import *, но этот вид импорта обычно не приветствуется в Python. Дальше создаём экземпляр QApplication и передаём sys.arg (список Python с аргументами командной строки, передаваемыми приложению):

app = QApplication(sys.argv)

Если не будете использовать аргументы командной строки для управления Qt, передайте пустой список:

app = QApplication([])

Затем создаём экземпляр QWidget, используя имя переменной window:

window = QWidget()
window.show()

В Qt все виджеты верхнего уровня — окна, то есть у них нет родительского элемента и они не вложены в другой виджет или макет. В принципе, окно можно создать, используя любой виджет.

Виджеты без родительского элемента по умолчанию невидимы. Поэтому после создания объекта window необходимо всегда вызывать функцию .show(), чтобы сделать его видимым. .show() можно удалить, но тогда, запустив приложение, вы не сможете выйти из него!

В окне находится пользовательский интерфейс приложения. У каждого приложения он как минимум один. Приложение (по умолчанию) завершает работу при закрытии последнего окна.

Наконец, вызываем app.exec(), чтобы запустить цикл события.

Что такое «цикл событий»?

Прежде чем вывести окно на экран, разберём ключевые понятия, касающиеся организации приложений в мире Qt. Если вам уже знакомы циклы событий, можете пропустить эту часть статьи.

Основной элемент всех приложений в Qt — класс QApplication. Для работы каждому приложению нужен один — и только один — объект QApplication, который содержит цикл событий приложения. Это основной цикл, управляющий всем взаимодействием пользователя с графическим интерфейсом:

При каждом взаимодействии с приложением — будь то нажатие клавиши, щелчок или движение мыши — генерируется событие, которое помещается в очередь событий. В цикле событий очередь проверяется на каждой итерации: если найдено ожидающее событие, оно вместе с управлением передаётся определённому обработчику этого события. Последний обрабатывает его, затем возвращает управление в цикл событий и ждёт новых событий. Для каждого приложения выполняется только один цикл событий.

Класс QApplication содержит цикл событий Qt (нужен один экземпляр QApplication). Приложение ждёт в цикле событий новое событие, которое будет сгенерировано при выполнении действия. Всегда выполняется только один цикл событий.

QMainWindow

Итак, в Qt любые виджеты могут быть окнами. Например, если заменить QtWidget на QPushButton. В этом примере получается окно с одной нажимаемой кнопкой:

import sys
from PyQt6.QtWidgets import QApplication, QPushButton

app = QApplication(sys.argv)

window = QPushButton("Push Me")
window.show()

app.exec()

Классно, но не очень полезно на самом деле: редко когда нужен пользовательский интерфейс, состоящий только из одного элемента управления. Зато возможность с помощью макетов вкладывать одни виджеты в другие позволяет создавать сложные пользовательские интерфейсы внутри пустого QWidget.

В Qt уже есть решение для окна — виджет QMainWindow, имеющий стандартные функции окна для использования в приложениях, который содержит панели инструментов, меню, строку состояния, закрепляемые виджеты и многое другое. Рассмотрим эти расширенные функции позже, а пока добавим в приложение простой, пустой QMainWindow:

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow

app = QApplication(sys.argv)

window = QMainWindow()
window.show()

# Запускаем цикл событий.
app.exec()

Запускаем и видим главное окно. Точно такое же, как и раньше.

QMainWindow пока не очень интересный. Добавим контент. Чтобы сделать настраиваемое окно, лучше создать подкласс QMainWindow, а затем настроить окно в блоке __init__. Так окно станет независимым в плане поведения. Итак, добавляем подкласс QMainWindow — MainWindow:

import sys

from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


# Подкласс QMainWindow для настройки главного окна приложения
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")
        button = QPushButton("Press Me!")

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Для этого демо используем QPushButton. Основные виджеты Qt всегда импортируются из пространства имён QtWidgets, как и классы QMainWindow и QApplication. При использовании QMainWindow задействуем .setCentralWidget для размещения виджета (здесь виджет — QPushButton) в QMainWindow, по умолчанию он занимает всё окно. Как добавлять в окна несколько виджетов? Об этом поговорим рассмотрим в руководстве по макетам.

При создании подкласса из класса Qt, чтобы разрешить Qt настраивать объект, всегда нужно вызывать функцию super __init__.

В блоке __init__ сначала используем .setWindowTitle(), чтобы поменять заголовок главного окна. Затем добавляем первый виджет — QPushButton — в середину окна. Это один из основных виджетов Qt. При создании кнопки можно ввести текст, который будет на ней отображаться. Вызываем .setCentralWidget() в окне. Это специальная функция QMainWindow, которая позволяет установить виджет на середину окна.

Запускаем и снова видим окно, но на этот раз с виджетом QPushButton в центре. Нажатие кнопки ничего не даст — с этим мы разберёмся после:

QMainWindow с одной кнопкой QPushButton на Windows, macOS и Linux

QMainWindow с одной кнопкой QPushButton на Windows, macOS и Linux

Скоро мы подробно рассмотрим другие виджеты, но, если вам не терпится и хочется забежать вперёд, можете заглянуть в документацию QWidget. Попробуйте добавить различные виджеты в окно.

Изменение размеров окон и виджетов

Сейчас размер окна можно свободно поменять: щёлкните мышью на любой угол и перетаскивайте, меняя таким образом размер. Можно дать возможность пользователям самим менять размер приложений, а можно установить ограничения на минимальные или максимальные размеры или фиксированный размер окна.

В Qt размеры определяются с помощью объекта QSize. Он принимает параметры ширины и высоты. Например, так создаётся окно фиксированного размера 400 x 300 пикселей:

import sys

from PyQt6.QtCore import QSize, Qt
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


# Подкласс QMainWindow для настройки главного окна приложения
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")

        self.setFixedSize(QSize(400, 300))

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(button)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Запускаем и видим окно фиксированного размера. Поменять его размер не получится.

Окно фиксированного размера

Окно фиксированного размера

Элемент управления maximize отключён на Windows и Linux. На macOS можно развернуть приложение на весь экран, но размер центрального виджета не изменится.

Кроме .setFixedSize() можно также вызвать .setMinimumSize() и .setMaximumSize(), чтобы установить минимальный и максимальный размеры соответственно. Попробуйте сами! Эти методы регулирования размеров работают в любом виджете. Продолжить изучение Python вы сможете на наших курсах:

  • Курс Python-разработчик

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

А ещё вы можете приобрести книгу автора этих уроков или продолжить чтение.


Слоты и сигналы

Ранее мы рассмотрели классы QApplication и QMainWindow, цикл событий и добавили в окно простой виджет. А теперь изучим механизмы Qt для взаимодействия виджетов и окон друг с другом. В статью внесены изменения, связанные с PyQt6.

Мы создали окно и добавили в него простой виджет push button, но кнопка пока бесполезна. Нужно связать действие нажатия кнопки с происходящим. В Qt это делается с помощью сигналов и слотов или событий.

Сигналы — это уведомления, отправляемые виджетами, когда что-то происходит. Этим «чем-то» может быть что угодно — нажатие кнопки, изменение текста в поле ввода или изменение текста в окне. Многие сигналы инициируются в ответ на действия пользователя, но не только: в сигналах могут отправляться данные с дополнительным контекстом произошедшего.

Можно также писать собственные сигналы, их мы рассмотрим позже.

Слоты в Qt — это приёмники сигналов. Слотом в приложении на Python можно сделать любую функцию (или метод), просто подключив к нему сигнал. Принимающая функция получает данные, отправляемые ей в сигнале. У многих виджетов Qt есть встроенные слоты, а значит, виджеты можно подключать друг к другу напрямую.

Рассмотрим основные сигналы Qt и их использование для подключения виджетов в приложениях. Сохраните эту заготовку приложения в файле app.py:

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Сигналы QPushButton

Сейчас у нас есть QMainWindow с центральным виджетом QPushButton. Подключим эту кнопку к пользовательскому методу Python. Создадим простой настраиваемый слот the_button_was_clicked, принимающий сигнал clicked от QPushButton:

import sys
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(button)

    def the_button_was_clicked(self):
        print("Clicked!")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Запускаем. Если нажать на кнопку, в консоли появится текст Clicked! («Нажата!»):

Clicked!
Clicked!
Clicked!
Clicked!

Получение данных

В сигналах может отправляться дополнительная информация о произошедшем. И сигнал .clicked — не исключение: с его помощью сообщается о нажатом (или переключенном) состоянии кнопки. Для обычных кнопок это значение всегда False, поэтому первый слот проигнорировал эти данные. Включим возможность нажатия кнопки, чтобы увидеть этот эффект. Ниже добавляется второй слот и выводится состояние нажатия:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_clicked)
        button.clicked.connect(self.the_button_was_toggled)

        self.setCentralWidget(button)

    def the_button_was_clicked(self):
        print("Clicked!")

    def the_button_was_toggled(self, checked):
        print("Checked?", checked)

Запускаем! Если нажать на кнопку, она подсветится и станет checked («Нажатой»). Чтобы отключить её, нажимаем ещё раз. Найдите состояние нажатия в консоли:

Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True
Clicked!
Checked? False
Clicked!
Checked? True

К сигналу подключается сколько угодно слотов, в которых можно реагировать сразу на несколько версий сигналов.

Хранение данных

Текущее состояние виджета на Python часто хранят в переменной, что позволяет работать со значениями без доступа к исходному виджету. Причём для их хранения используются отдельные переменные или словарь. В следующем примере сохраняем значение кнопки checked («Нажата») в переменной button_is_checked в self:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.button_is_checked = True

        self.setWindowTitle("My App")

        button = QPushButton("Press Me!")
        button.setCheckable(True)
        button.clicked.connect(self.the_button_was_toggled)
        button.setChecked(self.button_is_checked)

        self.setCentralWidget(button)

    def the_button_was_toggled(self, checked):
        self.button_is_checked = checked

        print(self.button_is_checked)

Сначала устанавливаем переменной значение по умолчанию True, а затем используем это значение, чтобы установить исходное состояние виджета. Когда состояние виджета меняется, получаем сигнал и соответственно обновляем переменную.

Эта же схема применима к любым виджетам PyQt. Если в виджете нет сигнала, которым отправляется текущее состояние, нужно получить значение из виджета прямо в обработчике. Например, здесь мы проверяем состояние checked («Нажата») в нажатом обработчике:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.button_is_checked = True

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.setCheckable(True)
        self.button.released.connect(self.the_button_was_released)
        self.button.setChecked(self.button_is_checked)

        self.setCentralWidget(self.button)

    def the_button_was_released(self):
        self.button_is_checked = self.button.isChecked()

        print(self.button_is_checked)

Сохраним ссылку на кнопку в self, чтобы получить к ней доступ в слоте.

Сигнал released срабатывает, когда кнопка отпускается, при этом состояние нажатия не отправляется. Его получают из кнопки в обработчике, используя .isChecked().

Изменение интерфейса

Мы уже видели, как принимаются сигналы и выводятся на консоль результаты. Но что происходит с интерфейсом, когда нажимают на кнопку? Обновим метод слота, чтобы изменить кнопку, поменяв текст, отключив её и сделав её недоступной. И отключим пока состояние, допускающее нажатие:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.setCentralWidget(self.button)

    def the_button_was_clicked(self):
        self.button.setText("You already clicked me.")
        self.button.setEnabled(False)

        # Также меняем заголовок окна.
        self.setWindowTitle("My Oneshot App")

Снова нужен доступ к кнопке в методе the_button_was_clicked, поэтому сохраняем ссылку на неё в self. Чтобы поменять текст кнопки, передаём str в .setText(). Чтобы отключить кнопку, вызываем .setEnabled() с аргументом False. И запускаем программу. Если нажать на кнопку, текст изменится и кнопка станет недоступной.

В методах слота можно не только менять кнопку, которая активирует сигнал, но и делать всё что угодно. Например, поменять заголовок окна, добавив в метод the_button_was_clicked эту строку:

self.setWindowTitle("A new window title")

Большинство виджетов, в том числе QMainWindow, имеют свои сигналы. В следующем, более сложном примере подключим сигнал .windowTitleChanged в QMainWindow к пользовательскому методу слота. А также сделаем для этого слота новый заголовок окна:

from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton

import sys
from random import choice

window_titles = [
    'My App',
    'My App',
    'Still My App',
    'Still My App',
    'What on earth',
    'What on earth',
    'This is surprising',
    'This is surprising',
    'Something went wrong'
]


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.n_times_clicked = 0

        self.setWindowTitle("My App")

        self.button = QPushButton("Press Me!")
        self.button.clicked.connect(self.the_button_was_clicked)

        self.windowTitleChanged.connect(self.the_window_title_changed)

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(self.button)

    def the_button_was_clicked(self):
        print("Clicked.")
        new_window_title = choice(window_titles)
        print("Setting title:  %s" % new_window_title)
        self.setWindowTitle(new_window_title)

    def the_window_title_changed(self, window_title):
        print("Window title changed: %s" % window_title)

        if window_title == 'Something went wrong':
            self.button.setDisabled(True)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Сначала создаём список заголовков окна и выбираем один из них наугад, используя встроенную функцию Python random.choice(). Подключаем пользовательский метод слота the_window_title_changed к сигналу окна .windowTitleChanged.

При нажатии на кнопку заголовок окна случайным образом изменится. Если новый заголовок окна изменится на Something went wrong («Что-то пошло не так»), кнопка отключится.

Запускаем! Нажимайте на кнопку, пока заголовок не изменится на Something went wrong. В этом примере стоит обратить внимание вот на что:

  1. Сигнал windowTitleChanged при установке заголовка окна выдаётся не всегда. Он срабатывает, только если новый заголовок отличается от предыдущего: если один и тот же заголовок устанавливается несколько раз, сигнал срабатывает только в первый раз. Чтобы избежать неожиданностей, важно перепроверять условия срабатывания сигналов при их использовании в приложении.

  2. С помощью сигналов создаются цепочки. Одно событие — нажатие кнопки — может привести к тому, что поочерёдно произойдут другие. Эти последующие эффекты отделены от того, что их вызвало. Они возникают согласно простым правилам. И это отделение эффектов от их триггеров — один из ключевых принципов, которые учитываются при создании приложений с графическим интерфейсом. Возвращаться к этому будем на протяжении всего курса.

Мы рассмотрели сигналы и слоты, показали простые сигналы и их использование для передачи данных и состояния в приложении. Теперь переходим к виджетам Qt, которые будут использоваться в приложениях вместе с сигналами.

Подключение виджетов друг к другу напрямую

Мы уже видели примеры подключения сигналов виджетов к методам Python. Когда сигнал из виджета срабатывает, вызывается метод Python, из сигнала он получает данные. Но для обработки сигналов не всегда нужна функция Python — можно подключать виджеты друг к другу напрямую.

Добавим в окно виджеты QLineEdit и QLabel. В __init__ для окна и подключим сигнал редактирования строки .textChanged к методу .setText в QLabel. Когда в QLineEdit меняется текст, он сразу будет поступать в QLabel (в метод .setText):

from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QLineEdit, QVBoxLayout, QWidget

import sys


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        self.label = QLabel()

        self.input = QLineEdit()
        self.input.textChanged.connect(self.label.setText)

        layout = QVBoxLayout()
        layout.addWidget(self.input)
        layout.addWidget(self.label)

        container = QWidget()
        container.setLayout(layout)

        # Устанавливаем центральный виджет Window.
        self.setCentralWidget(container)


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Внимание: чтобы подключить входные данные к метке, нужно определить и эти данные, и метку. В этом коде в макет добавляются два виджета и устанавливаются в окне. Подробно рассмотрим макеты позже, а пока не обращайте на них внимания.

Запускаем программу:

Введите текст в верхнем поле — он сразу появится в виде метки.

У большинства виджетов Qt есть доступные слоты, к которым подключается любой сигнал, возврощающий тот же тип, что он принимает. В документации по виджетам, в разделе Public Slots («Общедоступные слоты»), имеются слоты для каждого виджета. Посмотрите документацию для QLabel.

События

Любое взаимодействие пользователя с приложением Qt — это событие. Есть много типов событий, каждое из которых — это отдельный тип взаимодействия. В Qt события представлены объектами событий, в которые упакована информация о произошедшем. События передаются определённым обработчикам событий в виджете, где произошло взаимодействие.

Определяя пользовательские или расширенные обработчики событий, можно менять способ реагирования виджетов на них. Обработчики событий определяются так же, как и любой другой метод, но название обработчика зависит от типа обрабатываемого события.

QMouseEvent — одно из основных событий, получаемых виджетами. События QMouseEvent создаются для каждого отдельного нажатия кнопки мыши и её перемещения в виджете. Вот обработчики событий мыши:

Обработчик

Событие

mouseMoveEvent

Мышь переместилась

mousePressEvent

Кнопка мыши нажата

mouseReleaseEvent

Кнопка мыши отпущена

mouseDoubleClickEvent

Обнаружен двойной клик

Например, нажатие на виджет приведёт к отправке QMouseEvent в обработчик событий .mousePressEvent в этом виджете.

События можно перехватывать, создав подкласс и переопределив метод обработчика в этом классе. Их можно фильтровать, менять или игнорировать, передавая обычному обработчику путём вызова функции суперкласса методом super(). Они добавляются в класс главного окна следующим образом (в каждом случае в e будет получено входящее событие):

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QTextEdit


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.label = QLabel("Click in this window")
        self.setCentralWidget(self.label)

    def mouseMoveEvent(self, e):
        self.label.setText("mouseMoveEvent")

    def mousePressEvent(self, e):
        self.label.setText("mousePressEvent")

    def mouseReleaseEvent(self, e):
        self.label.setText("mouseReleaseEvent")

    def mouseDoubleClickEvent(self, e):
        self.label.setText("mouseDoubleClickEvent")


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Запускаем! В окне переместите мышь и нажмите на кнопку, затем дважды нажмите на кнопку и посмотрите, какие появляются события.

События перемещения мыши регистрируются только при нажатой кнопке. Чтобы это изменить, вызовите в окне self.setMouseTracking(True). События press («Нажатие кнопки»), click («Клика») и double-click («Двойного клика») срабатывают при нажатии кнопки. Событие release («Отпускание») срабатывает, только когда кнопка отпускается. Клик пользователя регистрируется обычно при нажатии кнопки мыши и её отпускании.

Внутри обработчиков события есть доступ к объекту этого события. Он содержит информацию о событии и используется, чтобы реагировать по-разному в зависимости от того, что произошло. Рассмотрим объекты событий управления мышью.

События управления мышью

В Qt все события управления мышью отслеживаются с помощью объекта QMouseEvent. При этом информация о событии считывается из следующих методов событий:

Метод

Возвращает

.button()

Конкретную кнопку, вызвавшую данное событие

.buttons()

Состояние всех кнопок мыши (флаги OR)

.position()

Относительную позицию виджета в виде целого QPoint .

Эти методы используются в обработчике событий, чтобы на разные события реагировать по-разному или полностью их игнорировать. Через методы позиционирования в виде объектов QPoint предоставляется глобальная и локальная (касающаяся виджета) информация о местоположении. Сведения о кнопках поступают с использованием типов кнопок мыши из пространства имён Qt. Например, в этом коде показаны разные реакции на нажатие левой, правой или средней кнопки мыши в окне:

    def mousePressEvent(self, e):
        if e.button() == Qt.LeftButton:
            # здесь обрабатываем нажатие левой кнопки
            self.label.setText("mousePressEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            # здесь обрабатываем нажатие средней кнопки.
            self.label.setText("mousePressEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            # здесь обрабатываем нажатие правой кнопки.
            self.label.setText("mousePressEvent RIGHT")

    def mouseReleaseEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseReleaseEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseReleaseEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseReleaseEvent RIGHT")

    def mouseDoubleClickEvent(self, e):
        if e.button() == Qt.LeftButton:
            self.label.setText("mouseDoubleClickEvent LEFT")

        elif e.button() == Qt.MiddleButton:
            self.label.setText("mouseDoubleClickEvent MIDDLE")

        elif e.button() == Qt.RightButton:
            self.label.setText("mouseDoubleClickEvent RIGHT")

Идентификаторы кнопок определяются в пространстве имён Qt:

Код

Бинарное значение

Описание

Qt.NoButton

0 (000)

Кнопка не нажата, или событие не связано с нажатием кнопки

Qt.LeftButton

1 (001)

Левая кнопка нажата

Qt.RightButton

2 (010)

Правая кнопка нажата

Qt.MiddleButton

4 (100)

Средняя кнопка [обычно это колёсико мыши] нажата

В мышках для правшей положения левой и правой кнопок меняются местами, то есть при нажатии правой кнопки вернётся Qt.LeftButton. Иными словами, учитывать ориентацию мыши не нужно.

Контекстные меню

Контекстные меню — это небольшие контекстно-зависимые меню, которые обычно появляются в окне при нажатии правой кнопкои мыши. В Qt у виджетов есть определённое событие для активации таких меню.

В примере ниже мы будем перехватывать событие .contextMenuEvent в QMainWindow. Это событие запускается всякий раз перед самым появлением контекстного меню, а передаётся событие с одним значением типа QContextMenuEvent.

Чтобы перехватить событие, просто переопределяем метод объекта с помощью нового метода с тем же именем. В нашем случае создаём метод в подклассе MainWindow с именем contextMenuEvent, и он будет получать все события этого типа:

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtGui import QAction
from PyQt6.QtWidgets import QApplication, QLabel, QMainWindow, QMenu


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

    def contextMenuEvent(self, e):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(e.globalPos())


app = QApplication(sys.argv)

window = MainWindow()
window.show()

app.exec()

Если запустить этот код и нажать правой кнопкой в окне, появится контекстное меню. В действиях меню можно настроить слоты .triggered как обычные (и повторно использовать действия, определённые для меню и панелей инструментов).

При передаче исходного положения в функцию exec оно должно соответствовать родительскому элементу, переданному при определении. В нашем случае в качестве родительского элемента передаётся self, поэтому используем глобальное положение. Для полноты картины нужно сказать, что создавать контекстные меню можно и при помощи сигналов:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.show()

        self.setContextMenuPolicy(Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.on_context_menu)

    def on_context_menu(self, pos):
        context = QMenu(self)
        context.addAction(QAction("test 1", self))
        context.addAction(QAction("test 2", self))
        context.addAction(QAction("test 3", self))
        context.exec(self.mapToGlobal(pos))

Что выбрать — решать только вам.

Иерархия событий

В PyQt каждый виджет — это часть двух различных иерархий: иерархии объектов в Python и иерархии макета в Qt. Реакция на события или их игнорирование влияет на поведение пользовательского интерфейса.

Перенаправление наследования Python

Часто бывает нужно перехватить событие, что-то с ним сделать, запустив при этом поведение обработки событий по умолчанию. Если объект унаследован от стандартного виджета, у него наверняка будет поведение по умолчанию. Запустить его можно, методом super() вызвав реализацию из суперкласса. Будет вызван именно суперкласс в Python, а не .parent() из PyQt:

def mousePressEvent(self, event):
    print("Mouse pressed!")
    super(self, MainWindow).contextMenuEvent(event)

Интерфейс ведёт себя как раньше, при этом мы добавили новое поведение, которое не вмешивается в прежнее.

Передача вверх по иерархии макета

Когда в приложение добавляется виджет, он также получает из макета другой родительский элемент. Чтобы найти родительский элемент виджета, надо вызвать функцию .parent(). Иногда эти родительские элементы указываются вручную (и часто автоматически), например для QMenu или QDialog. Когда в главное окно добавляется виджет, оно становится родительским элементом виджета.

Когда события создаются для взаимодействия пользователя с пользовательским интерфейсом, они передаются в его самый верхний виджет. Если в окне есть кнопка и вы нажмёте её, она получит событие первой.

Если первый виджет не может обработать событие, оно перейдёт к следующему по очереди родительскому виджету. Эта передача вверх по иерархии вложенных виджетов продолжится, пока событие не будет обработано или не достигнет главного окна. В обработчиках событий событие помечают как обработанное через вызов .accept():

    class CustomButton(QPushButton)
        def mousePressEvent(self, e):
            e.accept()

Вызвав в объекте события .ignore(), помечают его как необработанное. В этом случае событие будет передаваться по иерархии вверх:

    class CustomButton(QPushButton)
        def event(self, e):
            e.ignore()

Чтобы виджет пропускал события, вы можете спокойно игнорировать те, на которые каким-то образом реагировали. Аналогично вы можете отреагировать на одни события, заглушая другие. А продолжить изучение Python вы сможете на наших курсах:

  • Курс Python-разработчик

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

Напомним также о книге автора этих уроков.


Виджеты

В большинстве пользовательских интерфейсов и в Qt «виджет» — это компонент, с которым взаимодействует пользователь. Пользовательские интерфейсы состоят из нескольких виджетов, расположенных внутри окна. В Qt большой выбор виджетов и можно создать собственные виджеты.

Краткое демо

Посмотрим на самые распространённые виджеты PyQt. Они создаются и добавляются в макет окна с помощью этого кода (работу макетов в Qt рассмотрим в следующем руководстве):

import sys

from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (
    QApplication,
    QCheckBox,
    QComboBox,
    QDateEdit,
    QDateTimeEdit,
    QDial,
    QDoubleSpinBox,
    QFontComboBox,
    QLabel,
    QLCDNumber,
    QLineEdit,
    QMainWindow,
    QProgressBar,
    QPushButton,
    QRadioButton,
    QSlider,
    QSpinBox,
    QTimeEdit,
    QVBoxLayout,
    QWidget,
)


# Подкласс QMainWindow для настройки основного окна приложения
class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("Widgets App")

        layout = QVBoxLayout()
        widgets = [
            QCheckBox,
            QComboBox,
            QDateEdit,
            QDateTimeEdit,
            QDial,
            QDoubleSpinBox,
            QFontComboBox,
            QLCDNumber,
            QLabel,
            QLineEdit,
            QProgressBar,
            QPushButton,
            QRadioButton,
            QSlider,
            QSpinBox,
            QTimeEdit,
        ]

        for w in widgets:
            layout.addWidget(w())

        widget = QWidget()
        widget.setLayout(layout)

        # Устанавливаем центральный виджет окна. Виджет будет расширяться по умолчанию,
        # заполняя всё пространство окна.
        self.setCentralWidget(widget)


app = QApplication(sys.argv)
window = MainWindow()
window.show()

app.exec()

Запускаем! Появится окно со всеми созданными виджетами:

Виджеты на Windows, Mac и Ubuntu Linux

Виджеты на Windows, Mac и Ubuntu Linux

Посмотрим внимательно на все эти виджеты:

Класс виджета

Описание виджета

QCheckbox

Чекбокс

QComboBox

Окно выпадающего списка

QDateEdit

Для редактирования даты и времени

QDateTimeEdit

Для редактирования даты и времени

QDial

Поворотный циферблат

QDoubleSpinbox

Спиннер для чисел с плавающей точкой

QFontComboBox

Список шрифтов

QLCDNumber

Довольно неприятный дисплей LCD

QLabel

Просто метка, не интерактивная

QLineEdit

Поле ввода со строкой

QProgressBar

Индикатор выполнения

QPushButton

Кнопка

QRadioButton

Переключаемый набор, в котором активен только один элемент

QSlider

Слайдер

QSpinBox

Спиннер для целых чисел

QTimeEdit

Поле редактирования времени

Их гораздо больше, но они не поместятся в статью. Полный список есть в документации Qt.

Разберём подробно самые используемые виджеты и поэкспериментируем с ними в простом приложении. Сохраните следующий код в файле app.py и, запустив его, убедитесь, что он работает:

import sys
from PyQt6.QtWidgets import (
    QMainWindow, QApplication,
    QLabel, QCheckBox, QComboBox, QListBox, QLineEdit,
    QLineEdit, QSpinBox, QDoubleSpinBox, QSlider
)
from PyQt6.QtCore import Qt

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")


app = QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()

Выше мы импортировали несколько виджетов Qt. Разберём каждый по очереди. Добавим их в приложение и посмотрим, как они себя ведут.

QLabel

Начнём с QLabel, одного из простейших виджетов в Qt. Он состоит из строчки текста и устанавливается в приложении. Текст задаётся аргументом QLabel:

widget = QLabel("Hello")

Или с помощью метода .setText():

widget = QLabel("1")  # Создана метка с текстом 1.
widget.setText("2")   # Создана метка с текстом 2.

Параметры шрифта, например его размер или выравнивание в виджете, настраиваются так:

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QLabel("Hello")
        font = widget.font()
        font.setPointSize(30)
        widget.setFont(font)
        widget.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter)

        self.setCentralWidget(widget)

QLabel на Windows, Mac и Ubuntu Linux

QLabel на Windows, Mac и Ubuntu Linux

Рекомендация по шрифту: чтобы изменить свойства шрифта виджета, лучше получить текущий шрифт, обновить его и применить снова. Так начертание шрифта будет соответствовать ограничениям рабочего стола. Выравнивание указывается с помощью флага из пространства имён Qt. Вот флаги для горизонтального выравнивания:

Флаг PyQt6 (полный код)

Поведение

Qt.AlignmentFlag.AlignLeft

Выравнивает по левому краю

Qt.AlignmentFlag.AlignRight

Выравнивает по правому краю

Qt.AlignmentFlag.AlignHCenter

Центрирует по горизонтали в доступном пространстве

Qt.AlignmentFlag.AlignJustify

Выравнивает текст в доступном пространстве

Флаги используются вместе с помощью каналов (|), но флаги вертикального и горизонтального выравнивания не применяются одновременно:

Флаг PyQt6 (полный код)

Поведение

Qt.AlignmentFlag.AlignTop

Выравнивается по верху

Qt.AlignmentFlag.AlignBottom

Выравнивается по низу

Qt.AlignmentFlag.AlignVCenter

Центрирует вертикально в доступном пространстве

align_top_left = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop

При этом для совместного применения двух флагов (не A & B) используется канал | с операцией логического «ИЛИ». Эти флаги — неперекрывающиеся битовые маски. Например, у Qt.AlignmentFlag.AlignLeft шестнадцатеричное значение 0x0001, а у Qt.AlignmentFlag.AlignBottom — 0x0040. С помощью операции логического «ИЛИ» получаем значение «внизу слева» — 0x0041. Этот принцип применим и ко всем остальным парам флагов Qt. Если вам что-то здесь непонятно, смело пропускайте эту часть и переходите к следующей. Только не забывайте использовать |. Наконец, есть флаг, с помощью которого выполняется выравнивание по центру в обоих направлениях одновременно:

Флаг PyQt6

Поведение

Qt.AlignmentFlag.AlignCenter

Центрирует горизонтально и вертикально.

Как ни странно, QLabel используется и для показа изображения с помощью .setPixmap(). Этот метод принимает QPixmap, создаваемый передачей имени файла изображения в QPixmap. Среди примеров из этой книги есть файл otje.jpg, который отображается в окне так:

widget.setPixmap(QPixmap('otje.jpg'))

Кот Отье

Кот Отье

Какая мордочка! По умолчанию изображение масштабируется, сохраняя соотношение ширины и высоты. Если нужно растянуть и подогнать его по размерам окна, установите .setScaledContents(True) в QLabel:

widget.setScaledContents(True)

QCheckBox

А этот виджет, как следует из названия, предоставляет пользователю чекбокс с флажками. Как и во всех виджетах Qt, здесь есть настраиваемые параметры, изменяющие его поведение:

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QCheckBox()
        widget.setCheckState(Qt.CheckState.Checked)

        # Включение трёх состояний: widget.setCheckState(Qt.PartiallyChecked)
        # Или: widget.setTriState(True)
        widget.stateChanged.connect(self.show_state)

        self.setCentralWidget(widget)


    def show_state(self, s):
        print(s == Qt.CheckState.Checked)
        print(s)

QCheckBox на Windows, Mac & Ubuntu Linux

QCheckBox на Windows, Mac & Ubuntu Linux

Состояние чекбокса устанавливается программно с помощью .setChecked или .setCheckState. Первый принимает True или False, то есть чекбокс с галочкой или без неё соответственно. В случае с .setCheckState с помощью флага пространства имён Qt также указывается конкретное состояние чекбокса:

Флаг PyQt6 (Полный код)

Состояние

Qt.CheckState.Unchecked

Элемент не отмечен

Qt.CheckState.PartiallyChecked

Элемент отмечен частично

Qt.CheckState.Checked

Элемент отмечен

Чекбокс, поддерживающий состояние частичной отмеченности (Qt.CheckState.PartiallyChecked) галочками, обычно называют «имеющим третье состояние», т. е. он и отмечен, и не отмечен. Чекбокс в этом состоянии часто отображается в виде серого флажка и используется в иерархических структурах чекбоксов, где дочерние чекбоксы связаны с родительскими.

Установив значение Qt.CheckState.PartiallyChecked, вы переведёте чекбокс в третье состояние. То же самое, но без перевода текущего состояния в состояние частичной отмеченности галочками, делается с помощью .setTriState(True).

При запуске скрипта номер текущего состояния отображается в виде значения целочисленного типа int. Причём состоянию, отмеченному галочками, соответствует 2, не отмеченному — 0, а состоянию частичной отмеченности — 1. Запоминать эти значения не нужно: К примеру, переменная пространства имён Qt.Checked равна 2. Это значение соответствующих флагов состояния. То есть вы можете проверить состояние с помощью state == Qt.Checked.

QComboBox

QComboBox — это выпадающий список, закрытый по умолчанию, с кнопкой, чтобы открыть его. Можно выбрать один элемент из списка, при этом выбранный в данный момент элемент отображается в виджете в виде метки. Поле со списком подходит для выбора варианта из длинного списка вариантов. Такое поле используется при выборе начертания или размера шрифта в текстовых редакторах. В Qt для выбора шрифта есть специальное поле со списком шрифтов — QFontComboBox.

В QComboBox можно добавлять элементы, передавая список строк в .addItems(). Элементы добавляются в порядке расположения соответствующих элементам строк кода.

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QComboBox()
        widget.addItems(["One", "Two", "Three"])

        # Отправляет текущий индекс (позицию) выбранного элемента.
        widget.currentIndexChanged.connect( self.index_changed )

        # Есть альтернативный сигнал отправки текста.
        widget.textChanged.connect( self.text_changed )

        self.setCentralWidget(widget)


    def index_changed(self, i): # i — это int
        print(i)

    def text_changed(self, s): # s — это str
        print(s)

QComboBox на Windows, Mac и Ubuntu Linux

QComboBox на Windows, Mac и Ubuntu Linux

Сигнал .currentIndexChanged срабатывает при обновлении выбранного в данный момент элемента, индекс которого в списке передаётся по умолчанию. Кроме того, есть сигнал .currentTextChanged, при срабатывании которого вместо индекса предоставляется метка выбранного в данный момент элемента. Это часто оказывается полезнее.

Также QComboBox можно редактировать, вводя значения, которых в данный момент нет в списке, — вставлять их или просто использовать как значения. Поле делается редактируемым так:

widget.setEditable(True)

Чтобы определить, как обрабатываются вставляемые значения, устанавливается флаг. Флаги хранятся в самом классе QComboBox. Вот их список:

Флаг PyQt6 (полный код)

Поведение

QComboBox.InsertPolicy.NoInsert

Не вставлять

QComboBox.InsertPolicy.InsertAtTop

Вставить как первый элемент

QComboBox.InsertPolicy.InsertAtCurrent

Заменить текущий выбранный элемент

QComboBox.InsertPolicy.InsertAtBottom

Вставить после последнего элемента

QComboBox.InsertPolicy.InsertAfterCurrent

Вставить после текущего элемента

QComboBox.InsertPolicy.InsertBeforeCurrent

Вставить перед текущим элементом

QComboBox.InsertPolicy.InsertAlphabetically

Вставить в алфавитном порядке

Чтобы использовать их, применяется флаг:

widget.setInsertPolicy(QComboBox.InsertPolicy.InsertAlphabetically)

Кроме того, можно ограничить количество элементов поля, например при помощи .setMaxCount:

widget.setMaxCount(10)

Подробнее о QComboBox рассказывается здесь.

QListWidget

Этот виджет похож на QComboBox, но варианты здесь представлены в виде прокручиваемого списка элементов и возможен выбор нескольких элементов одновременно. В QListWidget есть сигнал currentItemChanged для отправки QListItem (элемента виджета списка) и сигнал currentTextChanged для отправки текста текущего элемента:

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QListWidget()
        widget.addItems(["One", "Two", "Three"])

        widget.currentItemChanged.connect(self.index_changed)
        widget.currentTextChanged.connect(self.text_changed)

        self.setCentralWidget(widget)


    def index_changed(self, i); # i — не индекс, а сам QList
        print(i.text())

    def text_changed(self, s): # s — это строка
        print(s)

QListWidget на Windows, Mac и Ubuntu Linux

QListWidget на Windows, Mac и Ubuntu Linux

QLineEdit

Виджет QLineEdit — это простое однострочное текстовое поле для редактирования вводимых пользователями данных. Он используется для полей, где нет ограничений на входные данные. Например, при вводе адреса электронной почты или имени компьютера:

class MainWindow(QMainWindow):

    def __init__(self):
        super(MainWindow, self).__init__()

        self.setWindowTitle("My App")

        widget = QLineEdit()
        widget.setMaxLength(10)
        widget.setPlaceholderText("Enter your text")

        #widget.setReadOnly(True) # раскомментируйте, чтобы сделать доступным только для чтения

        widget.returnPressed.connect(self.return_pressed)
        widget.selectionChanged.connect(self.selection_changed)
        widget.textChanged.connect(self.text_changed)
        widget.textEdited.connect(self.text_edited)

        self.setCentralWidget(widget)


    def return_pressed(self):
        print("Return pressed!")
        self.centralWidget().setText("BOOM!")

    def selection_changed(self):
        print("Selection changed")
        print(self.centralWidget().selectedText())

    def text_changed(self, s):
        print("Text changed...")
        print(s)

    def text_edited(self, s):
        print("Text edited...")
        print(s)

QLineEdit на Windows, Mac и Ubuntu Linux

QLineEdit на Windows, Mac и Ubuntu Linux

Как показано в этом коде, при однострочном редактировании вы можете установить максимальную длину текста.

В QLineEdit есть ряд сигналов для различных событий редактирования, в том числе при нажатии клавиши return (пользователем), когда пользователь изменил свой выбор. Также есть два сигнала редактирования: один — на случай, когда текст в поле отредактирован, другой — когда он изменён. Здесь различаются два вида изменений: пользовательские и выполненные программой. Сигнал textEdited отправляется только при редактировании текста пользователем.

Кроме того, с помощью маски ввода можно выполнить проверку вводимых данных, чтобы определить, какие символы поддерживаются и где. Применяется к соответствующему полю так:

widget.setInputMask('000.000.000.000;_')

Это позволяет использовать серию трёхзначных чисел, разделённых точками, для проверки адресов IPv4.

QSpinBox и QDoubleSpinBox

QSpinBox — это небольшое поле ввода чисел со стрелками для изменения значения. Оно поддерживает целые числа, а QDoubleSpinBox — числа с плавающей точкой:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QSpinBox()
        # Или: widget = QDoubleSpinBox()

        widget.setMinimum(-10)
        widget.setMaximum(3)
        # Или: widget.setRange(-10,3)

        widget.setPrefix("$")
        widget.setSuffix("c")
        widget.setSingleStep(3)  # Или, например, 0.5 в QDoubleSpinBox
        widget.valueChanged.connect(self.value_changed)
        widget.textChanged.connect(self.value_changed_str)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def value_changed_str(self, s):
        print(s)

Запустив код, вы увидите поле для ввода чисел. Значение в нём показывает префиксные и постфиксные блоки и ограничено диапазоном от +3 до –10:

QSpinBox на Windows, Mac и Ubuntu Linux

QSpinBox на Windows, Mac и Ubuntu Linux

В этом коде показан разнообразный функционал виджета. Чтобы задать диапазон допустимых значений, используются setMinimum и setMaximum. Одновременно они устанавливаются с помощью SetRange. Аннотация типов значений поддерживается префиксами и суффиксами, добавляемыми к числу. Например, для обозначений валюты или денежных единиц используются .setPrefix и .setSuffix соответственно.

Нажав на стрелки «вверх» и «вниз» на виджете, можно увеличить или уменьшить значение на величину, устанавливаемую с помощью .setSingleStep. Но это не повлияет на допустимые для виджета значения.

В QSpinBox и QDoubleSpinBox есть сигнал .valueChanged, срабатывающий при изменении их значений. С помощью необработанного сигнала .valueChanged отправляется числовое значение (целочисленного типа int или типа числа с плавающей точкой float), а с помощью .textChanged — значение в виде строки, включающей символы префикса и суффикса.

QSlider

QSlider — это ползунок, очень схожий по внутреннему функционалу с QDoubleSpinBox. Текущее значение представлено не числами, а в виде маркера ползунка, располагающегося по всей длине виджета. Этот виджет удобен для указания значения в промежутке между максимумом и минимумом, где абсолютная точность не требуется. Чаще всего этот тип виджетов используется при регулировке громкости. Есть и другие сигналы: .sliderMoved срабатывает при перемещении ползунка а .sliderPressed — при нажатии на ползунок:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QSlider()

        widget.setMinimum(-10)
        widget.setMaximum(3)
        # Или: widget.setRange(-10,3)

        widget.setSingleStep(3)

        widget.valueChanged.connect(self.value_changed)
        widget.sliderMoved.connect(self.slider_position)
        widget.sliderPressed.connect(self.slider_pressed)
        widget.sliderReleased.connect(self.slider_released)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def slider_position(self, p):
        print("position", p)

    def slider_pressed(self):
        print("Pressed!")

    def slider_released(self):
        print("Released")

Запустив код, вы увидите ползунок. Переместите его, чтобы изменить значение:

QSlider на Windows, Mac и Ubuntu Linux

QSlider на Windows, Mac и Ubuntu Linux

Также можно сделать вертикальный или горизонтальный ползунок, задав соответствующее расположение при его создании. Флаги расположения определены в пространстве имён Qt, например:

widget.QSlider(Qt.Orientiation.Vertical)

Или так:

widget.QSlider(Qt.Orientiation.Horizontal)

QDial

Наконец, QDial — это круговой виджет, схожий по функционалу с механическим ползунком из реального мира. Красиво, но с точки зрения пользовательского интерфейса не очень удобно. Он часто используется в аудиоприложениях:

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.setWindowTitle("My App")

        widget = QDial()
        widget.setRange(-10, 100)
        widget.setSingleStep(0.5)

        widget.valueChanged.connect(self.value_changed)
        widget.sliderMoved.connect(self.slider_position)
        widget.sliderPressed.connect(self.slider_pressed)
        widget.sliderReleased.connect(self.slider_released)

        self.setCentralWidget(widget)

    def value_changed(self, i):
        print(i)

    def slider_position(self, p):
        print("position", p)

    def slider_pressed(self):
        print("Pressed!")

    def slider_released(self):
        print("Released")

Запустив код, вы увидите круговой ползунок. Поверните его, чтобы выбрать число:

QDial на Windows, Mac и Ubuntu Linux

QDial на Windows, Mac и Ubuntu Linux

На сегодня мы заканчиваем, но это далеко не всё. Мы также расскажем о макете в PyQt, о работе с QAction, тулбарами, диалогами и не только. А пока ещё раз напоминаем о книге автора этих уроков и приглашаем на наши курсы:

  • Курс Python-разработчик

  • Профессия Fullstack-разработчик на Python

  • Курс «Python для веб-разработки»

Узнайте подробности здесь.

Профессии и курсы

  • Как установить psql на windows
  • Как установить pyqt5 на windows 10
  • Как установить proxmox на windows 10
  • Как установить proton vpn на windows
  • Как установить pyinstaller на windows