Как закрыть приложение pyqt
Руководство по работе с библиотекой Qt с использованием Python 3
Установка необходимых компонентов для работы
Установить Python версии не менее 3.5.
В операционных система семейства Unix процесс установки сводится к запуску пакетного менеджера с выбором необходимого пакета.
Пример установки Python 3 в Ubuntu:
По умолчанию, путь к интерпретатору Python 3 при установке на любой операционной системе добавляется к переменной среды PATH. В Windows для запуска интепретатора достаточно набрать в консоли:
В Unix системах, как правило, может быть уже установлена более ранняя версия интерпретатора, поэтому для корректного запуска 3-ей ветки Python рекомендуется выполнить в терминале команду:
В Python установка сторонних библиотек (или пакетов) происходит с помощью пакетного менеджера pip.
По умолчанию, в Windows при установке самого интерпретатора устанвливается и пакетный менеджер.
В Unix системах установка пакетного менеджера pip происходит отдельно.
Пример установки pip в Ubuntu:
Установка сторонних пакетов (библиотек) сводится к следующей команде в терминале Unix/консоли Windows:
pip автоматически найдет, скачает и установит необходимый пакет из сети интернет.
Для установки библиотеки Qt следует выполнить следующую команду:
Основы работы с Qt
Библиотека Qt является кросплатформенной и предназначена для написания графических приложений. Она инкапсулирует в себе все основные понятия любой из операционных графических систем: окна, фреймы, модальные диалоги, кнопки, текстовые поля, панели и т.д. Ядро библиотеки и всех ее компонентов написаны на языке программирования C++. Библиотека не является монолитной и разбита на несколько основных модулей, которые содержат классы для работы с графикой, сетью, файлами, аудио/видео, потоками ОС и т.д.
Пакет PyQt5 является портом для работы с Qt на языке Python. Ввиду этого, пакет наследует основные названия модулей библиотеки и их классов. Однако, в отличие от процесса работы на C++ под Qt, работа на языке Python с использованием пакета PyQt5 избавляет программиста от низкоуровневых особенностей C++ и ускоряет процесс написания программного кода. В пользу PyQt5 стоит сказать, что скорость понимания абстракций графического интерфейса, написанных на языке Python, бывает более высокой, нежели на C++.
Как правило, процесс ознакомления с PyQt5 тесно связан с чтением документации по тому или иному классу. Однако, стоит заметить, что все описания классов модулей Qt представлены на языке C++. Но такие понятия, как классы, его атрибуты и методы в языке C++ интуитивно легко перекладываются на язык Python. Тем самым, любые описания и примеры использования того или иного атрибута/метода на языке C++ в документации Qt справедливы и для Python, где любые упоминания про указатели и ссылки просто опускаются при работы с PyQt5.
На данный момент библиотека Qt развивается в двух направлениях: Qt Widgets и Qt Quick. Qt Widgets является фундаментальным и базовым направлением библиотеки. Данный модуль существует с момента сущестования платформы и направлен на создания графических приложений в стиле объектно-ориентированного подхода, где любой компонент интерфейса представлен объектом класса, тем самым организуя весть графический интерфейс пользователя в иерархию объектов. Qt Quick является более современным ответвлением платформы Qt. Данное направление вводит новые высокоуровневые абстракции вроде машины конечного автомата и вносит принципы реактивного программирования. Также, Qt Quick предлагает идею декларативного описания пользовательского интерфейса средствами JavaScript подобного языка QML.
В данном руководстве описываются базовые принципы работы с Qt Widgets совместно с пакетом PyQt5.
1. Основной цикл обработки событий
Приложение Qt построено на основе бесконечного цикла, который обратывает события: события операционной системы, события пользоваля приложения (поведение мыши, использование клавиатуры). Для запуска минимального приложения, следует передать циклу контекст, в котором начнут обрабатываться события. Базовым контекстом является понятие Виджет, которое представлено классом QWidget.
Виджет - это аналог окна, которое как может иметь рамку, кнопки сворачивания и закрытия, а может их и не иметь. С точки зрения операционных систем, это именно контекст, где операционная система может реагировать на события пользователя. В Qt виджеты могут содержать иерархию из других виджетов. При этом, разработчик никак не ограничен тем, как будет выглядеть виджет. Им могут быть как стандартное текстовое поле, кнопка, текстовая метка, так и сложный графический объект со своим стилем прорисовки.
Минимальное приложение Qt состоит из определения класса наследника QWidget, создания его экземпляра и запуска бесконечного цикла обработки событий.
Как было сказано, виджет представлен классом QWidget. Для создания виджета следует определить класс-наследник QWidget. В конструкторе класса следует добавить последний аргумент по умолчанию parent и вызвать инструкцию:
Параметр parent указывает на родительский виджет описываемого. Если описываемый является корневым, parent = None. Стоит сказать, что создание любого другого дочернего виджета, разметки должно просходить с передачей последним аргументом родительского виджета parent. Наиболее часто, родителем оказывается описываемый виджет self.
Часто, виджетам устанавливают ту или иную разметку, по правилам которой внутри располагаются другие виджеты. Наиболее используемыми классами разметки являются QVBoxLayout (вертикальная разметка) и QHBoxLayout (горизонтальная разметка). Для добавления дочерних виджетов в разметку предназачен метод addWidget(QWidget). Чтобы установить виджету ту или иную разметку используется метод setLayout(QLayout).
Хорошим стилем считается создание всех дочерних разметок/виджетов описываемого в теле контструктора класса. Как правило, создается приватный метод, например _initUI, и вызывается в конструкторе __init__.
Qt предоставляет набор удобных стандартных виджетов, поведение которых также можно изменить определением нового класса-наследника.
2.1 Текстовая метка: QLabel
Часто используемые методы:
- text() → string: возвращает текст метки.
- setText(string): устанавливает текст метки.
2.2 Текстовое поле: QLineEdit
Часто используемые методы:
- text() → string: возвращает текст поля ввода.
- setText(string): устанавливает текст поля ввода.
2.3 Кнопка: QPushButton
Часто используемые методы:
2.4 Многострочное поле ввода: QTextEdit
Часто используемые методы:
- setText(string): устанавливает текст в поле ввода.
- toPlainText() → string: возвращает обычный текст.
- toHtml() → string: возврашает текст в формате HTML.
2.5 Слайдер: QSlider
Часто используемые методы:
- setTickInterval(int): устанавливает шаг.
- tickInterval() → int: возвращает шаг.
- setMinimum(int): устанавливает минимальное значение.
- setMaximum(int): устанавливает максимальное значение.
2.6 Чек-бокс: QCheckBox
Часто используемые методы:
3. Взаимодействие с пользователем: введение в сигналы/слоты
В библиотеке Qt любые из классов, которые являются наследниками класса QObject могут участвовать в механизме сигналов/слотов. Практически все классы Qt являются наследниками QObject, в том числе и виджеты.
Механизм сигналов/слотов - является базовым понятием в обработке событий. Событиями могут выступать: действия пользователя графического интерфейса, события операционной системы и т.д. Само понятие события в Qt носит название Сигнал. Реакция на сигнал это всегда какая-либо простая функция, которая носит название Слот.
Как правило, дочерние компоненты описываемого виджета генерируют какие-либо сигналы. Например, сигнал клика по кнопке. Для реакции на данное событие создается метод внтури описываемого виджета. Стоит упомянуть, что сигнал может передавать любую порцию информации: число, строка, список и т.д.. Если сигнал отправляет какие-либо данные, то в методе на реакцию данного сигнала должен передаваться аргумент(ы) для обработки передаваемой информации.
Пример использования механизма сигналов/слотов:
Как видно из примера, виджет sld генерирует сигнал valueChanged, информирующий об изменении позиции слайдера. В свою очередь, данный сигнал связан с методом display виджета lcd. В данном случае, valueChanged является сигналом и отсылает значение типа int, а display является методом-сигналом, в который передается значение типа int. Связывание сигнала и слота происходит с помощью метода сигнала connect, который имеется у любого сигнала Qt.
Для определения слота, следует создать метод у класса описываемого виджета и привязать нужный сигнал к новому слоту с помощью метода сигнала connect.
Пример определения слота:
Чтобы определить слот, который реагирует на сигналы, отправляющие какую-либо информацию, следует лишь добавить аргумент(ы).
Пример определения слота на сигнал, передающий значение типа int:
Виджет slider генерирует сигнал valueChanged при изменении слайдера. В свою очередь, данный сигнал связан с слотом/методом _handleChangeSlider, который принимает аргумент value типа int. При любом изменении слайдера вызывается метод _handleChangeSlider, который устанавливает текст метке label на значение ползунка слайдера. Стоит сказать, что метод метки label.setText принимает строковое значение, поэтому значение, отправляемое сигналом, числового типа int явно приводится к строковому типу str.
В документации библиотеки Qt к тому или иному классу виджета все сигналы находятся в секции Signals. Особое внимание стоит обращать на типы данных, которые возвращают сигналы.
В этой части руководства PyQt5
мы изучим некоторую базовую функциональность.
Этот простой пример выводит маленькое окно. Мы можем делать множество вещей с этим окном. Мы можем менять его размер, раскрывать на весь экран или свернуть в панель задач. Это потребовало бы много программного кода. Кто-то уже запрограммировал эту функциональность. Поскольку это повторяется в большинстве приложений, нет необходимости программировать это с самого начала. PyQt5 – это инструментарий высокого уровня. Если бы мы писали с помощью инструментов более низкого уровня, нижеследующий пример кода мог бы с лёгкостью содержать сотни строк.
Пример кода выше выводит маленькое окно на экране.
Здесь мы обеспечили необходимый импорт модулей. Основные виджеты расположены в модуле PyQt5.QtWidgets.
Каждое приложение PyQt5 должно создать объект приложения. Параметр sys.argv - это список аргументов из командной строки. Скрипты Python могут быть запущены из программной оболочки. Это один из способов, как мы можем контролировать запуск наших скриптов.
Виджет QWidget – это основной класс всех объектов пользовательского интерфейса в PyQt5. Мы обеспечиваем конструктор по умолчанию для QWidget. Конструктор по умолчанию не имеет родителя. Виджет без родителя называется окном.
Метод resize() изменяет размер виджета. Здесь задана ширина 250px и высота 150px.
Метод move() перемещает виджет на позицию с координатами x=300 и y=300 на экране.
Здесь мы устанавливаем название нашего окна. Оно показывается в строке заголовка.
Метод show() отображает виджет на экране. Виджет сначала создаётся в памяти и позже показывается на экране.
Наконец, мы входим в главный цикл приложения. Обработка событий начинается в этой точке. Главный цикл получает события из системы и отправляет их виджетам приложения. Цикл завершается, если мы вызываем метод exit() или главное окно было закрыто. Метод sys.exit() гарантирует чистый выход. Среда будет проинформирована, когда приложение завершится.
Метод exec_() содержит нижнее подчеркивание, потому что exec – уже используемое имя в Python. И, соответственно, имя exec_() было использовано взамен.
Рисунок: Окно Simple
Иконка приложения
Иконка приложения – это маленькое изображение, которое обычно отображается в верхнем левом углу строки заголовка. В следующем примере мы увидим, как сделать её в PyQt5. Мы также познакомимся с несколькими новыми методами.
Предыдущий пример был написан в процедурном стиле. Язык программирования Python поддерживает и процедурный, и объектно-ориентированный стиль программирования. Программирование в PyQt5 означает объектно-ориентированное программирование (ООП).
Есть три важных вещи в объектно-ориентированном программировании – это классы, данные и методы. Здесь мы создаём новый класс с именем Example. Класс Example наследуется из класса QWidget. Это значит, что мы вызываем два конструктора: первый для класса Example и второй для унаследованного класса. Метод super() возвращает объект родителя класса Example и мы вызываем его конструктор. Метод __init__() – это конструктор класса в языке Python.
Создание GUI поручено методу initUI().
Все три метода были унаследованы из класса QWidget. setGeometry делает две вещи: он определяет место окна на экране и устанавливает его размер. Первые два параметра – позиции x и y нашего окна. Третий – ширина, и четвёртый – высота окна. Фактически, setGeometry сочетает методы resize() и move() в одном. Последний метод устанавливает иконку приложения. Чтобы сделать это, мы создали объект QIcon. QIcon принимает путь к нашей иконке для её отображения.
Приложение и объекты примера созданы. Главный цикл запущен.
Рисунок: Окно с иконкой
Показ всплывающих подсказок
Мы можем обеспечить любой из наших виджетов всплывающей подсказкой.
В этом примере, мы показываем подсказку для двух PyQt5 виджетов.
Этот статический метод устанавливает шрифт, используемый для показа всплывающих подсказок. Мы используем шрифт 10px SansSerif.
Чтобы создать подсказку, мы вызываем метод setTooltip(). Мы можем использовать HTML форматирование текста.
Мы создаём виджет кнопки и устанавливаем всплывающую подсказку для неё.
Меняем размер у кнопки, перемещаем её в окно. Метод sizeHint() даёт рекомендуемый размер для кнопки.
Рисунок: Всплывающие подсказки
Закрытие окна
Очевидный способ закрыть окно – это кликнуть на знаке «X» в строке заголовка. В следующем примере, мы покажем, как мы можем программно закрыть наше окно. Мы кратко коснёмся темы сигналов и слотов.
Ниже следует конструктор виджета QPushButton, который мы используем в нашем примере.
Параметр string_text – это текст, который будет отображён на кнопке. Parent – это виджет, на котором мы разместим нашу кнопку. В этом случае это будет QWidget.
Виджеты имеют иерархическую форму. В этой иерархии большинство виджетов имеют родителей. Виджеты без родителей – это окна верхнего уровня.
В этом примере, мы создали кнопку выхода. После нажатия на кнопку, приложение завершается.
Нам необходим объект из модуля QtCore.
Мы создали кнопку. Кнопка является образцом класса QPushButton. Первый параметр конструктора – это метка кнопки. Второй параметр – родительский виджет. Виджет родителя – это Example, который наследуется из QWidget.
Система обработки событий в PyQt5 построена на механизме сигналов и слотов. Если мы кликаем по кнопке, выдаётся сигнал clicked. Слот может быть слотом Qt или любым слотом, вызываемым из Python. QCoreApplication содержит цикл главного события; он обрабатывает и выполняет все события. Метод instance даёт нам его текущий экземпляр. Заметим, что QCoreApplication создаётся с QApplication. Кликнутый сигнал соединяется с методом quit(), который и завершает приложение. Взаимосвязь сделана между двумя объектами: отправителем и получателем. Отправитель – нажатие кнопки, получатель – объект приложения.
Рисунок: Кнопка выхода
Диалоговое окно
Если мы закрываем QWidget, вызывается QCloseEvent. Чтобы изменить поведение виджета, нам необходимо переопределить обработчик событий closeEvent().
Здесь мы проверяем возвращаемое значение. Если мы кликаем кнопку «Yes», мы принимаем событие, которое ведёт к закрытию виджета и завершению приложения. В противном случае, мы игнорируем закрывающее событие.
Рисунок: Диалоговое окно
Центрирование окна на экране
Нижеследующий скрипт показывает, как мы можем центрировать окно экране.
Класс QtGui.QDesktopWidget предоставляет информацию о пользовательском рабочем столе, включая размер экрана.
Код, который будет центрировать окно, размещён в специальном методе center().
Мы получаем прямоугольник, точно определяющий форму главного окна.
Мы выясняем разрешение экрана нашего монитора. Из этого разрешения, мы получаем центральную точку.
Наш прямоугольник уже имеет высоту и ширину. Теперь мы устанавливаем центр прямоугольника в центр экрана. Размер прямоугольника не изменяется.
Мы перемещаем верхнюю левую точку окна приложения в верхнюю левую точку прямоугольника qr, таким образом центрируя окно на нашем экране.
Python регулярно включают в разнообразные рейтинги лучших языков программирования благодаря большому сообществу и легко читаемому синтаксису. Более того, Python под силу создать современный графический пользовательский интерфейс — GUI — для обычных скриптов. В руководстве уделим внимание модулю для разработки GUI PyQt5 , но стоит также упомянуть аналоги: Tkinter и WxWidget .
Статья подойдет в том числе и начинающим программистам, поэтому, не теряя времени, приступаем!
Содержание руководства:
1. Установка и настройка PyQt5
Скачайте и установите последнюю версию Python для вашей системы, а если Python уже на месте, то установите пакеты при помощи следующей команды (откройте командную строку, введите и нажмите Enter):
2. Основы PyQt5
Теперь на вашем компьютере сохранен пакет PyQt5 , поэтому давайте начнем с написания первого окна графического интерфейса. Откройте ваш любимый текстовый редактор или IDE и выполните приведенный ниже код:
Результат выполнения программы:
Теперь разберем код окна интерфейса с заголовком сверху. Если обсуждать кратко, то в первую очередь импортируем PyQt5 и его классы, полезные в создании виджета GUI, а затем создаем функцию main() для реализации оконного интерфейса.
- QMainWindow() — словно контейнер, содержащий все виджеты, такие как кнопки, текст, поле ввода и т. д.
- SetGeometry() — один из методов QMainWindow() , устанавливает размер окна. Синтаксис: setGeometry(x, y, длина, ширина) .
- SetWindowTitle() — устанавливает заголовок окна.
- Win.show() — создает и отображает весь разработанный интерфейс.
- Sys.exit(app.exec_()) — устанавливает, что конкретное окно не закроется без нажатия на кнопку с крестиком. Без этой строчки кода GUI-программа завершится через секунду после выполнения.
3. Заголовок окна
Label Text — это текст, отображаемый внутри окна. С написанием Label Text в PyQt5 вам поможет виджет Qlabel .
Выполните следующий код:
Результат выполнения программы:
Если кратко, то вызываем метод Qlabel() и передаем в него переменную QMainWindow .
- Метод SetText() устанавливает Label Text, в качестве аргумента принимает только строковые данные.
- Метод Move(x, y) применяется для установки положения Label Text внутри окна.
4. События и кнопки
Кнопки — важная часть любого программного обеспечения, ведь именно кнопка определяет действие пользователя, а следовательно, и результат работы программы тоже. Для создания кнопок в PyQt5 придётся применить другой виджет под названием QtWidgets , выполните следующий код:
Результат выполнения программы:
В коде вышеизложенного примера переменная QMainWindow передается в метод из Qwidget под названием QPushbutton . Далее разберем код примера по пунктам.
- SetText() устанавливает название для кнопки, как можно увидеть на приведенном выше изображении.
- Move() снова применяется для установки положения кнопки в окне по координатам на осях x и y.
Теперь пришел черед событийно-ориентированного программирования (Event-Driven-Programming)! Проще говоря, нужно определить действие для кнопки, то есть, если пользователь на нее нажмет, то что-то должно произойти. Ознакомьтесь со следующим кодом, а дальше рассмотрим подробные объяснения:
Теперь в примере определяется не только главная функция по имени main() , но и функция по имени click() , передающаяся в качестве параметра для button.clicked.connect() в main() . Таким образом указывается конкретная функция, срабатывающая при нажатии на конкретную кнопку.
Запустив такой код и нажав на кнопку, вы увидите вывод на экране консоли. Дело за вами, что же написать внутри функции click() ! Протестируйте код на вашей операционной системе.
5. Поля ввода
Поля ввода также называются текстовыми полями — это области для пользовательского ввода информации. Для объявления поля ввода в PyQt5 применяется специальный виджет QlineEdit() , а в него, как обычно, передается в качестве параметра QMainWindow .
Посмотрите на следующий код и обратите внимание на его результат:
Результат выполнения программы:
Проанализируйте следующий код:
Результат выполнения программы:
Результат выполнения программы:
- QMessageBox.Warning
- QMessageBox.Critical
- QMessageBox.Information
- QMessageBox.Question
Выводы
Что же, руководство предоставило вам все необходимые знания для самостоятельного создания графических пользовательских интерфейсов на PyQt5 .
Все библиотеки для разработки приложений работают с главным циклом, который обрабатывает такие события, как отображение окна на экране, его перемещение, изменение размера, реакция на нажатие кнопки. Словом, любое взаимодействие с интерфейсом.
Некоторые из этих событий могут быть связаны с функцией, которую мы предоставляем.
Например, метод button1_pressed(), который вызывается этой библиотекой, когда пользователь нажимает на элемент управления button1.
При работе с Qt способ реагирования на эти события обычно заключается в подключении сигнала к слоту.
Проблема возникает, когда в ответ на одно из этих событий или во время создания интерфейса мы выполняем операцию, длительность которой оказывается значительной (можно сказать, что это любая задача, занимающая более секунды).
Это приводит к тому, что процессор занят выполнением нашей задачи и не может заниматься основным циклом приложения.
В результате интерфейс перестает отвечать на запросы: мы не можем переместить его, закрыть, изменить размер или выполнить любой другой тип взаимодействия с ним.
Перейдем к конкретному примеру.
Следующий код дублирует окно с меткой (QLabel) и кнопкой (QPushButton), которая при нажатии загружает файл, используя стандартный модуль urllib.request.
Вы заметите, что во время загрузки, которая в моем случае длится около пяти секунд, интерфейс полностью замирает.
Разберем три решения этой же проблемы с их сильными и слабыми сторонами.
Первое решение: потоки
Это решение предполагает запуск нового потока для выполнения нашей трудоемкой задачи.
Поскольку основной цикл Qt выполняется в основном потоке программы, а наша операция выполняется во вторичном потоке, интерфейс остается активным, пока файл загружается в фоновом режиме.
Для этого мы используем класс QThread, который предоставляет кроссплатформенный API для создания потоков.
Ядром кода является класс Downloader, который наследуется от QThread и повторно реализует метод run() (строка 17), содержимое которого будет выполняться в новом потоке, когда мы создадим экземпляр и вызовем метод start() (строки 43 и 50).
В строке 49 мы соединяем сигнал finished, который отдает Qt, когда поток завершает выполнение, с нашим методом downloadFinished().
Хотя в примере речь идет о загрузке файла, этот метод позволяет переместить любую ресурсоемкую задачу в новый поток: просто поместите его внутрь метода run().
С другой стороны, многопоточное программирование должно выполняться с особой осторожностью.
Только метод run() выполняется в новом потоке, в то время как все остальные (включая сам Downloader.init()) выполняются в основном.
Кроме того, важно быть осторожным, чтобы не разделять объекты, к которым могут одновременно обращаться два или более потоков.
Второе решение: processEvents()
Альтернативой запуску нового потока является выполнение всей работы в основном потоке, но периодически позволяя Qt обрабатывать события приложения, чтобы интерфейс не перестал отвечать.
В предыдущих вариантах кода функция, которая выполняет тяжелую работу и блокирует выполнение на несколько секунд, была r.read().
Поскольку этот метод не возвращает нам контроль над программой, пока файл не будет полностью загружен, мы должны создать собственный цикл, который получает удаленный файл небольшими пакетами (128 байт) и в то же время позволяет Qt обрабатывать его события.
К счастью для нас, read() опционально принимает в качестве аргумента количество байт данных, которые мы хотим прочитать.
Здесь ключ лежит между строками 32-48, где строится цикл, который обрабатывает события Qt, потребляет кусок информации из сети и отправляет его в локальный файл, до тех пор, пока больше нет данных для извлечения.
Поскольку весь код выполняется в одном потоке, нам не нужно беспокоиться о проблемах, связанных с доступом к объектам и их модификацией, как в предыдущем случае.
Однако r.read(128) все равно является вызовом, который блокирует выполнение кода даже на очень короткое время, практически незаметное.
Если скорость интернет-соединения слишком низкая, даже извлечение этого небольшого количества байтов может привести к зависанию пользовательского интерфейса.
Третье решение: Twisted
Модуль qt5reactor позволяет объединить основные циклы Twisted и Qt в одном приложении, предоставляя нам доступ ко всему арсеналу асинхронных функций, предоставляемых сетевой библиотекой.
Для этого третьего решения мы также будем использовать библиотеку treq (похожую на Requests, но построенную на базе Twisted) для доступа к URL файла и загрузки содержимого.
Мы устанавливаем эти два инструмента просто с помощью pip:
Теперь код выглядит следующим образом:
Twisted, вероятно, является наиболее оптимальным решением, когда задачи, которые нам нужно выполнить, всегда связаны с доступом к какому-либо ресурсу в Интернете и часто встречаются в коде.
Здесь нам также не придется иметь дело с проблемами совместного использования объектов между потоками, поскольку Twisted всегда работает в главном потоке.
Те, кто немного разбирается в Twisted, найдут это решение весьма удачным.
И это действительно так: Qt и Twisted очень хорошо подходят друг другу благодаря своей структуре, философии и даже соглашениям об именах.
Заключение
Приведем основные моменты каждого из них.
Потоковая альтернатива эффективна для любых тяжелых задач, хотя мы уже говорили, что ее следует применять с осторожностью.
Читайте также: