Путевые заметки в процессе познания Python и PyQt/PySide.
Помни - только тег CODE не портит код добавлением пробела в начало пустой строки.
Помни - только тег CODE не портит код добавлением пробела в начало пустой строки.
К вопросу о соразмерности виджетов разрешению экрана...
Запись от iamvic размещена 04.03.2024 в 17:02
Показов 1833
Комментарии 9
Метки pyqt5, python, python 3, qt designer
|
Поучаствовал тут в разборках интересного казуса, как раз подходящего для Памятки. Суть-то в том, что версии HDMI весьма различны и в ряде случаев высоконасыщенный элементами управления виджет, разработанный под последнее слово техники, может просто не влезть на экран в случае замены штатного монитора на старенький резервный с гораздо меньшим разрешением. Оказалось, что можно попробовать использовать скроллинг для таких случаев ![]() а лучше так: 1. программерское решение scroll_probe.py
2. дизайнерское решение scroll_probe_ui.py
scroll_probe.ui
Обновление от 17.03.2024 Есть тут одна заноза в качестве повода для размышлений... В документации-то QScrollArea Class сказано следующее:
![]() scroll_probe.py
| |||||||||||||||||||||
Метки pyqt5, python, python 3, qt designer
Размещено в Памятка
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Всего комментариев 9
Комментарии
-
Как говорится, за недолгим народилось и решение, соответствующее сказанному в документации:
scroll_probe.py
Python 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Scroll Probe import sys from PyQt5 import QtWidgets class Scroll_probe(QtWidgets.QWidget): def __init__(self, parent=None): super(Scroll_probe, self).__init__(parent) text_edt = QtWidgets.QPlainTextEdit() push_btn_1 = QtWidgets.QPushButton("Push Button 1") push_btn_2 = QtWidgets.QPushButton("Push Button 2") push_btn_3 = QtWidgets.QPushButton("Push Button 3") push_btn_4 = QtWidgets.QPushButton("Push Button 4") v_layout = QtWidgets.QVBoxLayout() v_layout.addWidget(push_btn_1) v_layout.addWidget(push_btn_2) v_layout.addWidget(push_btn_3) v_layout.addStretch(1) v_layout.addWidget(push_btn_4) h_layout = QtWidgets.QHBoxLayout() h_layout.addWidget(text_edt) h_layout.addLayout(v_layout) scr_area = QtWidgets.QScrollArea(self) scr_area.setWidgetResizable(True) v_area = QtWidgets.QVBoxLayout() v_area.addLayout(h_layout) h_area = QtWidgets.QHBoxLayout() h_area.addWidget(scr_area) scr_content = QtWidgets.QWidget() scr_content.setLayout(v_area) scr_area.setWidget(scr_content) self.setLayout(h_area) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) mwin = Scroll_probe() mwin.show() sys.exit(app.exec_())
По крайней мере, теперь не требуется явное указание родителя в операторе
и можно быть уверенным, что родительские отношения будут выстроены в соответствии порядком применяемых макетов.Python 1
v_area = QtWidgets.QVBoxLayout()
В предыдущей-то версии было так
и это несколько смущало, хотя и было подсмотрено в коде дизайнерского решенияPython 1
v_area = QtWidgets.QVBoxLayout(scr_content)
.Запись от iamvic размещена 19.03.2024 в 00:12
-
А если глянуть под капот...
Python 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Scroll Probe import sys from PyQt5 import QtWidgets def look_inside(n, ob): print('{!s}: id({!s}({!s})) = {!s}' ''.format( n, ob.objectName(), ob.metaObject().className(), hex(id(ob)) ) ) class Scroll_probe(QtWidgets.QWidget): def __init__(self, parent=None): super(Scroll_probe, self).__init__(parent) text_edt = QtWidgets.QPlainTextEdit() push_btn_1 = QtWidgets.QPushButton("Push Button 1") push_btn_2 = QtWidgets.QPushButton("Push Button 2") push_btn_3 = QtWidgets.QPushButton("Push Button 3") push_btn_4 = QtWidgets.QPushButton("Push Button 4") v_layout = QtWidgets.QVBoxLayout() v_layout.addWidget(push_btn_1) v_layout.addWidget(push_btn_2) v_layout.addWidget(push_btn_3) v_layout.addStretch(1) v_layout.addWidget(push_btn_4) h_layout = QtWidgets.QHBoxLayout() h_layout.addWidget(text_edt) h_layout.addLayout(v_layout) scr_area = QtWidgets.QScrollArea(self) scr_area.setObjectName('scr_area') look_inside(0, scr_area) for x in scr_area.children(): look_inside(1, x) scr_area.setWidgetResizable(True) v_area = QtWidgets.QVBoxLayout() v_area.setObjectName('v_area') v_area.addLayout(h_layout) h_area = QtWidgets.QHBoxLayout() h_area.addWidget(scr_area) scr_content = QtWidgets.QWidget() scr_content.setLayout(v_area) scr_area.setWidget(scr_content) look_inside(2, v_area) self.setLayout(h_area) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) mwin = Scroll_probe() mwin.show() sys.exit(app.exec_())
Запись от iamvic размещена 19.03.2024 в 11:04
-
...то можно увидеть как мутабельные дети экземпляра класса QScrollArea деятельно принимают участие в создании должного GUI:
Дополнение от 22.03.2024
А схема родительских отношений выстроенного ими GUI весьма извилиста (для примера взято дизайнерское решение - имена всем объектам там уже назначены, что облегчает понимание):
Bash 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
self == Form(Scroll_probe) | +---> scrollArea(QScrollArea) | +---> qt_scrollarea_viewport(QWidget) == scrollArea.viewport() | | | +---> scrollAreaWidgetContents(QWidget) | | | +---> verticalLayout_2(QVBoxLayout) | | | +---> plainTextEdit(QPlainTextEdit) | | | +---> pushButton(QPushButton) | | | +---> pushButton_2(QPushButton) | | | +---> pushButton_3(QPushButton) | | | +---> pushButton_4(QPushButton) | +---> qt_scrollarea_hcontainer(QWidget) | +---> qt_scrollarea_vcontainer(QWidget)
Запись от iamvic размещена 19.03.2024 в 11:12
-
И соответствующий код стал выглядеть так:
scroll_probe_ui.py
Python 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 30 31 32 33 34 35 36
#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Scroll Probe with UI file import sys from PyQt5 import ( QtCore, QtWidgets, uic ) class Scroll_probe(QtWidgets.QWidget): def __init__(self, parent=None): super(Scroll_probe, self).__init__(parent) uic.loadUi('scroll_probe.ui', self) @QtCore.pyqtSlot(name='on_pushButton_pressed') @QtCore.pyqtSlot(name='on_pushButton_2_pressed') @QtCore.pyqtSlot(name='on_pushButton_3_pressed') @QtCore.pyqtSlot(name='on_pushButton_4_pressed') def on_button_pressed(self): self.findChild(QtWidgets.QScrollArea).viewport( ).findChild(QtWidgets.QWidget, 'scrollAreaWidgetContents' ).findChild(QtWidgets.QPlainTextEdit, 'plainTextEdit' ).setPlainText('===> {!s} pressed'.format( self.sender().text() ) ) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) mwin = Scroll_probe() mwin.show() sys.exit(app.exec_())
Приятно, что при загрузке scroll_probe.ui лихо отрабатывает
цепляя сигналы на соответствующие слоты. Зато к нужному экземпляру QPlainTextEdit приходится продираться через лабиринтPython 1
QtCore.QMetaObject.connectSlotsByName(Form)
для доказательства существования дерева родительских отношений. Хотя этого обычно и не требуется, достаточно использовать:
Python 1 2 3 4 5 6
def on_button_pressed(self): self.findChild(QtWidgets.QPlainTextEdit, 'plainTextEdit' ).setPlainText('===> {!s} pressed'.format( self.sender().text() ) )
Запись от iamvic размещена 22.03.2024 в 19:05
-
А программерский вариант может выглядеть так:
Python 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Scroll Probe import sys from PyQt5 import QtCore, QtWidgets class Scroll_probe(QtWidgets.QWidget): def __init__(self, parent=None): super(Scroll_probe, self).__init__(parent) text_edt = QtWidgets.QPlainTextEdit() text_edt.setObjectName('text_edt') push_btn_1 = QtWidgets.QPushButton('Push Button 1') push_btn_1.setObjectName('push_btn_1') push_btn_2 = QtWidgets.QPushButton('Push Button 2') push_btn_2.setObjectName('push_btn_2') push_btn_3 = QtWidgets.QPushButton('Push Button 3') push_btn_3.setObjectName('push_btn_3') push_btn_4 = QtWidgets.QPushButton('Push Button 4') push_btn_4.setObjectName('push_btn_4') v_layout = QtWidgets.QVBoxLayout() v_layout.addWidget(push_btn_1) v_layout.addWidget(push_btn_2) v_layout.addWidget(push_btn_3) v_layout.addStretch(1) v_layout.addWidget(push_btn_4) h_layout = QtWidgets.QHBoxLayout() h_layout.addWidget(text_edt) h_layout.addLayout(v_layout) scr_area = QtWidgets.QScrollArea(self) scr_area.setObjectName('scr_area') scr_area.setWidgetResizable(True) v_area = QtWidgets.QVBoxLayout() v_area.addLayout(h_layout) h_area = QtWidgets.QHBoxLayout() h_area.addWidget(scr_area) scr_content = QtWidgets.QWidget() scr_content.setObjectName('scr_content') scr_content.setLayout(v_area) scr_area.setWidget(scr_content) self.setLayout(h_area) QtCore.QMetaObject.connectSlotsByName(self) @QtCore.pyqtSlot(name='on_push_btn_1_pressed') @QtCore.pyqtSlot(name='on_push_btn_2_pressed') @QtCore.pyqtSlot(name='on_push_btn_3_pressed') @QtCore.pyqtSlot(name='on_push_btn_4_pressed') def on_button_pressed(self): self.findChild(QtWidgets.QPlainTextEdit, 'text_edt' ).setPlainText('===> {!s} pressed'.format( self.sender().text() ) ) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) mwin = Scroll_probe() mwin.show() sys.exit(app.exec_())
Запись от iamvic размещена 26.03.2024 в 17:10
-
Ну и чтоб не забылось, ввиду непотребляемости
, опишу как это делалось в Qt Designer:
1. в диалоге New Form (Новая форма) выбрать Widget в templates/forms и нажать кнопку Создать (Create)
2. перетащить на форму виджет Containers/Scroll Area
3. растянуть его под разиер формы
4. перетащить на Scroll Area четыре виджета Buttons/Push Button и один виджет Spacers/Vertical Spacer
5. выстроить их в колонку в правой половине Scroll Area сверху вниз по порядку:
6. выбрать вышеуказанные виджеты для проведения групповой операции (щелчок ЛКМ на первом виджете, затем Ctrl+щелчок ЛКМ на остальных)
7. вызвать выпадающее меню щелчком ПКМ на любом из выбранных виджетов
8. в пункте меню Компоновка (Layout) выбрать пункт Скомпоновать по вертикали (Lay Out Vertically)
9. перетащить на Scroll Area виджет Inputs Widgets/Plain Text Edit на свободное место слева
10. выбрать виджеты QPlainTextEdit и QVBoxLayout для проведения групповой операции
11. скомпоновать их по горизонтали
12. выбрать вмджет QScrollArea и скомпоновать его по вертикали
13. выбрать корневой виджет Form(QWidget) и скомпоновать его по горизонтали
14. попробовать поменять размеры, потянув за правый нижний уголок формы
15. если поведение соответствует ожиданиям - сохранить, иначе повторить сначала
Вот теперь уже можно и тексты на кнопках поправить и прочие украшательства понавешать
Возможен ещё экономичный вариант (пункты с 1 по 9 не меняются):
10. выбрать виджет QScrollArea и скомпоновать его по горизонтали
11. выбрать корневой виджет Form(QWidget) и скомпоновать его по вертикали
12. попробовать поменять размеры, потянув за правый нижний уголок формы
13. если поведение соответствует ожиданиям - сохранить, иначе повторить сначала
Но возникают некие сомнения, что его удастся преобразовать в программерское решение без излишнего использования явного указания родителей при создании экземпляров классов. В первом-то случае, это удалось сделать, а тут непонятно...Запись от iamvic размещена 27.03.2024 в 22:20
-
Тем не менее эти варианты исполнения являются полными функциональными аналогами, в чём можно убедиться после очередной переделки многострадального scroll_probe_ui.py, что позволит выбрать один из вариантов файла GUI при запуске приложения:
Python 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
#!/usr/bin/python3 # -*- coding: utf-8 -*- # # Scroll Probe with UI file import os, sys from PyQt5 import ( QtCore, QtWidgets, uic ) class Scroll_probe(QtWidgets.QWidget): def __init__(self, parent=None): super(Scroll_probe, self).__init__(parent) f_ui = ('scroll_probe_eco.ui' if len(sys.argv) > 1 else 'scroll_probe.ui') uic.loadUi(f_ui, self) self.findChild(QtWidgets.QPlainTextEdit, 'plainTextEdit' ).setPlainText(f_ui) @QtCore.pyqtSlot(name='on_pushButton_pressed') @QtCore.pyqtSlot(name='on_pushButton_2_pressed') @QtCore.pyqtSlot(name='on_pushButton_3_pressed') @QtCore.pyqtSlot(name='on_pushButton_4_pressed') def on_button_pressed(self): self.findChild(QtWidgets.QPlainTextEdit, 'plainTextEdit' ).setPlainText('===> {!s} pressed'.format( self.sender().text() ) ) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) if 'Windows' in QtWidgets.QStyleFactory.keys(): app.setStyle(QtWidgets.QStyleFactory.create('Windows')) f_qss = 'scroll_probe_ui.qss' if (os.path.exists(f_qss) and os.path.isfile(f_qss)): t = '' with open(f_qss, 'r') as _from: t = _from.read() app.setStyleSheet(t) mwin = Scroll_probe() mwin.show() sys.exit(app.exec_())
Вариант используемого файла GUI определяется наличием параметров в командной строке. Если параметры отсутствуют
то используется уже существующий файл GUI scroll_probe.ui.Bash 1
py scroll_probe_ui.py
А если есть хотя бы один дополнительный параметр, например,
то используется экономичный вариант:Bash 1
py scroll_probe_ui.py 1Запись от iamvic размещена 29.03.2024 в 20:32
-
scroll_probe_eco.ui
XML 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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>Form</class> <widget class="QWidget" name="Form"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>392</width> <height>265</height> </rect> </property> <property name="windowTitle"> <string>Form</string> </property> <layout class="QVBoxLayout" name="verticalLayout_2"> <item> <widget class="QScrollArea" name="scrollArea"> <property name="widgetResizable"> <bool>true</bool> </property> <widget class="QWidget" name="scrollAreaWidgetContents"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>376</width> <height>249</height> </rect> </property> <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QPlainTextEdit" name="plainTextEdit"/> </item> <item> <layout class="QVBoxLayout" name="verticalLayout"> <item> <widget class="QPushButton" name="pushButton"> <property name="text"> <string>Push Button 1</string> </property> </widget> </item> <item> <widget class="QPushButton" name="pushButton_2"> <property name="text"> <string>Push Button 2</string> </property> </widget> </item> <item> <widget class="QPushButton" name="pushButton_3"> <property name="text"> <string>Push Button 3</string> </property> </widget> </item> <item> <spacer name="verticalSpacer"> <property name="orientation"> <enum>Qt::Vertical</enum> </property> <property name="sizeHint" stdset="0"> <size> <width>20</width> <height>18</height> </size> </property> </spacer> </item> <item> <widget class="QPushButton" name="pushButton_4"> <property name="text"> <string>Push Button 4</string> </property> </widget> </item> </layout> </item> </layout> </widget> </widget> </item> </layout> </widget> <resources/> <connections/> </ui>
Запись от iamvic размещена 29.03.2024 в 20:34
-
Стилист тоже выполнил свою часть работы, разукрасив всё "под старину"
scroll_probe_ui.qss
Результат налицоCSS 1 2 3 4 5 6 7
Scroll_probe {background-color: palegoldenrod;} QScrollArea#scrollArea {border: 4px solid maroon; border-radius: 8;} QWidget#scrollAreaWidgetContents {background-color: darkseagreen;} QPlainTextEdit#plainTextEdit {background-color: lightgoldenrodyellow; font: bold; color: dimgrey;} QPushButton {background-color: palegoldenrod; font: bold;} QScrollBar {background-color: maroon; color: yellow;}

Состав пакета:
scroll_probe_ui.py
scroll_probe.ui
scroll_probe_eco.ui
scroll_probe_ui.qss
Запись от iamvic размещена 29.03.2024 в 20:43


.

