Создание виджетов

Для построения пользовательского интерфейса библиотека Qt, поставляемая в рамках ГИС Аксиома, предоставляет большой набор инструментария.

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

Для того, чтобы наш диалог обладал некоторыми специфичными свойствами, унаследуем его от стандартного базового класса PySide2.QtWidgets.QDialog и переопределим его поведение в соответствие с нашими потребностями. Первоначальный код будет выглядеть примерно так:

from PySide2.QtWidgets import QDialog
from axipy import view_manager


class MyDialog(QDialog):
    pass


dialog = MyDialog(view_manager.global_parent)
dialog.resize(500, 300)
dialog.exec()

Здесь мы создали пользовательский класс диалога и активировали его. Отметим, что свойство view_manager.global_parent, переданное в конструктор рассматривается как объект-владелец данного диалога. Если данное свойство не проставить, то диалог в панели задач будет показан как отдельное приложение.

Тестирование можно производить в рамках самой ГИС Аксиомы. Для этого необходимо открыть редактор. Кнопка запуска находится на панели «Консоль Python». Если она отсутствует в интерфейсе, то для включения необходимо в выпадающем меню кнопки «Панели» включить пункт «Консоль Python». Сохраним данный файл в файловой системе под именем mydialog.py.

Далее, чтобы разместить на данном диалоге элементы управления можно пойти двумя путями:

  • Создать эти элементы программно

  • Использовать для этого файл ресурсов *.ui

Второй вариант более понятен и удобен, но мы вкратце рассмотрим оба подхода.

Программное наполнение диалога

Создадим простой диалог, на который программно добавим кнопку и поле ввода. По нажатию на кнопку в консоль будет выведен текст элемента ввода.

from PySide2.QtWidgets import QDialog, QPushButton, QLineEdit
from axipy import view_manager


class MyDialog(QDialog):
    def __init__(self, parent):
        super().__init__(parent)
        self.button = QPushButton(self)
        self.button.setText("Кнопка")
        self.button.move(150, 50)
        # Реакция на нажатие кнопки
        self.button.clicked.connect(self.button_clicked)
        self.edit = QLineEdit(self)
        self.edit.setText("Текст")
        self.edit.move(10, 50)

    def button_clicked(self):
        print(self.edit.text())


dialog = MyDialog(view_manager.global_parent)
dialog.resize(500, 300)

dialog.exec()

В данном примере элементы располагаются с явным указанием их места. Это не совсем удобно и в зависимости от размеров элементов управления на конкретной системе могут накладываться друг на друга. Или при изменении размеров самой формы могут вообще исчезать. Для решения этой проблемы обычно используются классы динамического размещения элементов на форме. В нашем случае это наследники класса PySide2.QtWidgets.QLayout. Модифицируем пример с использованием класса PySide2.QtWidgets.QGridLayout.

from PySide2.QtCore import Qt
from PySide2.QtWidgets import QDialog, QPushButton, QLineEdit, QGridLayout
from axipy import view_manager


class MyDialog(QDialog):
    def __init__(self, parent):
        super().__init__(parent)
        # создаем layout
        layout = QGridLayout()
        self.button = QPushButton()
        self.button.setText("Кнопка")
        self.button.clicked.connect(self.button_clicked)
        # Добавляем кнопку
        layout.addWidget(self.button, 0, 0, Qt.AlignTop)
        self.edit = QLineEdit()
        self.edit.setText("Текст")
        # Добавляем элемент ввода
        layout.addWidget(self.edit, 0, 1, Qt.AlignTop)
        # Назначаем layout для диалога
        self.setLayout(layout)

    def button_clicked(self):
        print(self.edit.text())


dialog = MyDialog(view_manager.global_parent)
dialog.resize(500, 300)

dialog.exec()

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

Если элементов на форме много, то такой способ размещения элементов достаточно трудоемок. Рассмотрим альтернативный способ размещения с использованием дизайнера Qt Designer.

Использование файла ресурсов *.ui. Прямая загрузка.

Для формирования UI представления формы нам понадобится инструмент Qt Designer. Он поставляется совместно со средствами разработки Qt, но его также можно установить отдельно как пакет python qt5_applications. Подробнее о инсталляции пакетов см в разделе Без интернета. Ручная установка пакетов.. Т.е. в конечном итоге в зависимости от окружения нам нужно выполнить команду:

python3 -m pip install --user qt5_applications

После успешной инсталляции в каталог site-packages из файлового менеджера переходим в каталог site-packages/qt5_applications/Qt/bin и запускаем на выполнение файл designer.

Как альтернативный и более простой вариант, можно воспользоваться возможностью установки и запуска QtDesigner посредством соответствующего модуля. Для этого на вкладке «Основные» необходимо нажать кнопку «Модули». Далее, на вкладке «Дополнительные модули» следует нажать «Загрузить список модулей». Из полученного списка выбираем «Qt Designer» и нажимаем «Установить». На панели «Основные» должна появиться кнопка «QtDesigner».

Запускаем инструмент. Выбираем тип диалога или формы. В нашем случае это Dialog without buttons. Далее, помещаем на него посредством перетаскивания мышью кнопку PySide2.QtWidgets.QPushButton и элемент ввода PySide2.QtWidgets.QLineEdit.

../_images/ui_dialog_0.png

Слева от этих элементов поместить метки, перетащив элемент PySide2.QtWidgets.QLabel. Меняем у кнопки ее текст на «Кнопка» (двойным щелчком на элементах) и для поля ввода заносим текст «Текст». Также можно поменять названия элементов на button и edit (чтобы они совпадали с предыдущими примерами). Для этого после выделения соответствующего элемента, в таблице свойств возле свойства objectName установим значение button. Выберем диалог и установим свойство windowTitle равным «Мой диалог».

../_images/ui_dialog_1.png

Для того, чтобы при изменении размеров формы расположение элементов на ней не уезжало, необходимо применить динамическое выравнивание (Layout). Далее, для нашей формы применим динамическое размещение элементов, выбрав на панели его тип. Для этого выберем мышью основную форму и нажмем кнопку на панели Lay Out in a Grid. Элементы займут все пространство и при изменении размеров окна, их размер пропорционально будет изменяться. Чтобы элементы прижать кверху, перетянем по аналогии с кнопкой и полем ввода элемент Vertical Spacer и отпустим его над нижней частью (но внутри) окна. После этого наши элементы должны с середины переместиться наверх.

../_images/ui_dialog_2.png

Если мы хотим, чтобы при изменении размеров окна изменялись размеры одного из элементов, необходимо сделать следующее: Выделим и удалим ранее добавленный объект Vertical Spacer. После этого находим и перетягиваем объект PySide2.QtWidgets.QFrame. Кладем его по аналогии с удаленным Vertical Spacer, но помещаем его сверху на форму над всеми ранее добавленными элементами. Если он занял не все пространство, его необходимо растянуть мышью. После для свойств Horizontal и Vertical Policy устанавливаем значение Expanding.

../_images/ui_dialog_3.png

Сохраняем файл под именем mydialog.ui и кладем его рядом с файлом mydialog.py.

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

import os

from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QDialog
from axipy import view_manager


class MyDialog(QDialog):
    def __init__(self, parent):
        super().__init__(parent)
        # Путь файла в файловой системе
        ui_file = os.path.join(os.path.dirname(__file__), "mydialog.ui")
        # Загружаем файл ui
        self.ui = QUiLoader().load(ui_file, parent)
        self.ui.button.clicked.connect(self.button_clicked)

    def button_clicked(self):
        print(self.ui.edit.text())

    # Перенаправим метод show на self.ui
    def show(self):
        return self.ui.show()


# Показ диалога как немодальное окно. Для модального используем метод exec
dialog = MyDialog(view_manager.global_parent)
dialog.resize(500, 300)
dialog.exec()

Стоит заметить, что к элементам теперь необходимо обращаться не через self, а через self.ui. В примерах поставки ГИС Аксиома есть подобное решение с реализацией в виде модуля ru_axioma_gis_axipy_example_dialog.

Использование наследования на базе файла ресурсов *.ui

Наряду с прямой загрузкой файла *.ui как ресурса можно использовать метод наследования классов. Для этого необходимо создать файл класса python *.py, из файла *.ui. В QtDesigner открываем файл из предыдущего раздела mydialog.ui. В меню выбираем Форма/Показать код python…. В появившейся форме нажимаем кнопку Сохранить и указываем имя ui_mydialog.py.

../_images/ui_save_python.png

В итоге мы получили класс Ui_Dialog, который будем использовать как базовый для нашего диалога. Вторым базовым классом будет PySide2.QtWidgets.QDialog. Код будет выглядеть следующим образом:

# Импортируем сгенерированный класс
from .ui_mydialog import Ui_Dialog
from PySide2.QtWidgets import QDialog

class MyDialog(QDialog, Ui_Dialog):

    def __init__(self, parent=None):
        super().__init__(parent)
        # Загружаем объекты из сгенерированного Ui_Dialog
        self.setupUi(self)
        self.button.clicked.connect(self.button_clicked)

    def button_clicked(self):
        print(self.edit.text())

dlg = MyDialog(view_manager.global_parent)
dlg.exec()

После любого изменения файла mydialog.ui необходимо пересоздать файл ui_mydialog.py. Также, необходимо отметить, что вносить изменения в файл ui_mydialog.py не рекомендуется, т.к. после очередного изменения с последующим пересозданием, все правки будут утеряны.