Введение
На этой странице представлены примеры среднего уровня сложности работы с графическим фреймворком PySide (на сопутствующих страницах рассматриваются аспекты более или менее сложные, Примеры PySide для начинающих и Примеры PySide для продвинутых пользователей). На этой странице используется пример программы для демонстрации различных тем PySide. Цель состоит в том, чтобы представить функциональный код PySide, чтобы любой, кому необходимо использовать PySide, мог скопировать соответствующий раздел, изменить и адаптировать его для своих целей.
Примечания
- Эта страница не предназначена для описания языка Python или в качестве инструкции по Python.
- Названия переменных не являются описательными, но расположены в определенной последовательности для лучшей организации пояснений.
- Существует множество соглашений об именовании компонентов графического интерфейса, ни одно из которых не является «правильным» или «неправильным».
- Существует множество различных последовательностей объявления виджетов, сигналов, методов, и опять же, ни одна из них не является «правильной» или «неправильной».
- Стоит помнить, что PySide работает со строками при обработке пользовательского ввода; то, что отображается на экране как число, на самом деле является текстовым представлением числа.
Обсуждение кода — Декларативная часть
"Пример программы" на самом деле представляет собой большое определение класса, определение класса фреймворка PySide, и содержит более 150 строк кода (включая комментарии). Класс и его поведение не имеют функционального назначения, их единственная цель — продемонстрировать возможные действия графического интерфейса и представить код, который, как мы надеемся, смогут использовать другие пользователи FreeCAD.
Определение класса и небольшое количество вызываемых им строк кода описаны в том порядке, в котором они встречаются в файле. Этот порядок основан на расположении элементов на экране, которое произвольно и предназначено исключительно для демонстрации возможностей. Вот модальный графический интерфейс, который генерирует класс PySide:
В оставшейся части этого раздела будет описано содержимое определения класса, которое находится в конце этого раздела. Сначала мы рассмотрим декларативные элементы, определяющие принцип работы и структуру фреймворка, затем перейдем к оперативным разделам (т. е. к коду, который будет выполняться при взаимодействии с пользователем - 'интерактивный код'). Это окно основано на классе QDialog и поэтому является модальным — это означает, что за пределами открытого окна нельзя совершать никаких действий.
Объявление импорта
Обязательный импорт
from PySide import QtGui, QtCore
Лучше всего разместить этот код в самом начале файла Python.
Определение класса
class ExampleModalGuiClass(QtGui.QDialog):
""""""
def __init__(self):
super(ExampleModalGuiClass, self).__init__()
self.initUI()
def initUI(self):
Этот код лучше всего скопировать дословно и внести изменения. Суть этого кода в том, что мы создаём подкласс класса QDialog из PySide. При адаптации этого кода вам потребуется изменить имя класса "ExampleModalGuiClass" — обязательно измените его в обоих местах (например, в строках 1 и 4).
Статус возврата окна
self.result = userCancelled
Это не обязательное требование, а скорее хорошая практика программирования: устанавливается статус возврата по умолчанию для окна, который будет сохраняться независимо от действий пользователя. Позже в коде это может быть изменено с помощью кода Python для указания различных вариантов, которые мог выбрать пользователь.
Создание окна
# create our window
# define window xLoc,yLoc,xDim,yDim
self.setGeometry( 250, 250, 400, 350)
self.setWindowTitle("Our Example Program Window")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
Помните, что размеры экрана измеряются от верхнего левого угла, в 3-й строке значения относятся к:
- количество пикселей, на которое верхний левый угол будет находиться правей от левого края экрана (250)
- количество пикселей, на которое верхний левый угол будет находиться ниже верхнего края экрана (250)
- ширине экрана в пикселях (400)
- высоте экрана в пикселях (350)
Заголовок окна задан, а последняя строка просто означает, что это окно никогда не будет закрыто другим окном — если это не требуется, просто поместите символ комментария Python ('#') в качестве первого символа строки.
Создание надписей
# create some Labels
self.label1 = QtGui.QLabel(" ", self)
self.label1.setFont('Courier') # set to a non-proportional font
self.label1.move(20, 20)
self.label2 = QtGui.QLabel("sample string number two", self)
self.label2.move(20, 70)
self.label3 = QtGui.QLabel(" ", self)
self.label3.setFont('Courier') # set to a non-proportional font
self.label3.move(20, 120)
self.label4 = QtGui.QLabel("can you see this?", self)
self.label4.move(20, 170)
В PySide лабели выполняют две функции: статические лабели (как следует из названия), а также текстовые поля только для чтения (т.е. только для отображения). Таким образом, пользователю могут передаваться как статичные неизменяемые строки, например, «Не нажимайте красную кнопку», так и динамические результаты вычислений, например, «42». Вторая строка объявляет лабель и задает ее начальное значение (в данном случае пустое). Третья строка указывает шрифт; можно указать любой шрифт (из системы, т. е. если он установлен); если он не указан, используется шрифт по умолчанию. В данном случае шрифт задается как непропорциональный. Лабель перемещается в свое местоположение в окне — ее координаты определяют ее положение относительно окна (не экрана).
Создание элементов типа CheckBox
# checkboxes
self.checkbox1 = QtGui.QCheckBox("Left side", self)
self.checkbox1.clicked.connect(self.onCheckbox1)
#self.checkbox1.toggle() # will set an initial value if executed
self.checkbox1.move(210,10)
#
self.checkbox2 = QtGui.QCheckBox("Right side", self)
self.checkbox2.clicked.connect(self.onCheckbox2)
self.checkbox2.move(210,30)
Флажки (чекбоксы) могут быть включены и выключены в любой комбинации (в отличие от переключателей (radio button)). Строка 2 объявляет флажок и задает его начальное значение. Строка 3 указывает, какой метод будет выполнен при щелчке по флажку (в данном случае метод 'onCheckBox1'). Если бы в 4-й строке не было символа комментария Python ('#') в качестве первого символа, то она бы выполнилась и отметила флажок как отмеченный. Наконец, 5-я строка перемещает флажок в нужное положение.
Создание селективных кнопок (Radio Button)
# radio buttons
self.radioButton1 = QtGui.QRadioButton("random string one",self)
self.radioButton1.clicked.connect(self.onRadioButton1)
self.radioButton1.move(210,60)
#
self.radioButton2 = QtGui.QRadioButton("owt gnirts modnar",self)
self.radioButton2.clicked.connect(self.onRadioButton2)
self.radioButton2.move(210,80)
Создание переключателей очень похоже на создание флажков. Единственное существенное отличие заключается в поведении переключателей: одновременно может быть активирован только один из них.
Создание Всплывающего Меню
# set up lists for pop-ups
self.popupItems1 = ("pizza","apples","candy","cake","potatoes")
# set up pop-up menu
self.popup1 = QtGui.QComboBox(self)
self.popup1.addItems(self.popupItems1)
self.popup1.setCurrentIndex(self.popupItems1.index("candy"))
self.popup1.activated[str].connect(self.onPopup1)
self.popup1.move(210, 115)
Во второй строке формируется список вариантов выбора пользователя. Альтернативный вариант — создать словарь, но использовать только ключи для списка пунктов меню. В четвертой строке создается всплывающее меню (в PySide оно называется ComboBox), варианты выбора пользователя добавляются в пятой строке.
В качестве дополнительной заметки, если бы использовался словарь, то строки выглядели бы следующим образом:
self.popupItems1 = OrderedDict([("2","widget"),("pink","foobar"),("4","galopsis")])
self.popup1.addItems(self.popupItems1.keys())
Возвращаясь к основному примеру кода для этого раздела, строка 6 устанавливает выбор по умолчанию; эту строку можно опустить, также значение выбора по умолчанию можно загрузить в соответствующую метку (опять же, если это необходимо). И наконец, перемещение на позицию в строке 8.
Создание Кнопок Часть 1
# toggle visibility button
pushButton1 = QtGui.QPushButton('Toggle visibility', self)
pushButton1.clicked.connect(self.onPushButton1)
pushButton1.setAutoDefault(False)
pushButton1.move(210, 165)
Кнопка создается во второй строке с указанием ее имени, обработчик сигнала при нажатии указывается в третьей строке. Четвертая строка предназначена для предотвращения превращения кнопки в «кнопку по умолчанию» — кнопку, которая будет нажата, если пользователь просто нажмет клавишу Return. И перемещение на позицию завершает сегмент кода.
Создание Кнопок Часть 2
# cancel button
cancelButton = QtGui.QPushButton('Cancel', self)
cancelButton.clicked.connect(self.onCancel)
cancelButton.setAutoDefault(True)
cancelButton.move(150, 280)
# OK button
okButton = QtGui.QPushButton('OK', self)
okButton.clicked.connect(self.onOk)
okButton.move(260, 280)
Обе кнопки создаются с именем (которое будет отображаться в качестве их лабели), связанным с методом, который будет выполняться при нажатии на них и перемещении в нужное положение. Единственное исключение — строка 4, в которой кнопка «Отмена» указана как кнопка по умолчанию — это означает, что она будет «нажата», если пользователь нажмет клавишу Return.
Создание текстового поля ввода
# text input field
self.textInput = QtGui.QLineEdit(self)
self.textInput.setText("cats & dogs")
self.textInput.setFixedWidth(190)
self.textInput.move(20, 220)
Виджет QLineEdit, вероятно, является наиболее распространенным для ввода текста пользователем. В этом примере следующий фрагмент кода создаст контекстное меню для работы с ним. Этот фрагмент кода создает (строка 2), устанавливает начальное значение (строка 3), задает ширину поля (строка 4) и перемещает виджет на нужное место (строка 5).
Создание QuantitySpinBox
# QuantitySpinBox
from FreeCAD import Units
ui = FreeCADGui.UiLoader()
quantityInput = ui.createWidget("Gui::QuantitySpinBox")
self.quantityInput.setProperty( 'minimum', 0.0)
potential = 2.87
unit = "V"
# only set the value
self.quantityInput.setProperty('rawValue', potential )
# set quantity (value + unit)
quantity = Units.Quantity("{} {}".format(potential , unit))
self.quantityInput.setProperty('value', quantity)
# read value from the spinbox
quantity = self.quantityInput.property('value')
Виджет Gui::QuantitySpinBox — это специальный виджет FreeCAD, предназначенный для отображения и обработки значений вместе с units. Он является производным от класса QAbstractSpinBox из Qt [1]. Список всех его свойств можно найти в исходном коде QuantitySpinBox.h.
Создание контекстного меню
# set contextual menu options for text editing widget
# set text field to some dogerel
popMenuAction1 = QtGui.QAction(self)
popMenuAction1.setText("load some text")
popMenuAction1.triggered.connect(self.onPopMenuAction1)
# make text uppercase
popMenuAction2 = QtGui.QAction(self)
popMenuAction2.setText("uppercase")
popMenuAction2.triggered.connect(self.onPopMenuAction2)
# menu dividers
popMenuDivider = QtGui.QAction(self)
popMenuDivider.setText('---------')
popMenuDivider.triggered.connect(self.onPopMenuDivider)
# remove all text
popMenuAction3 = QtGui.QAction(self)
popMenuAction3.setText("clear")
popMenuAction3.triggered.connect(self.onPopMenuAction3)
# define menu and add options
self.textInput.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.textInput.addAction(popMenuAction1)
self.textInput.addAction(popMenuAction2)
self.textInput.addAction(popMenuDivider)
self.textInput.addAction(popMenuAction3)
Этот код содержит множество повторений, поскольку одно и то же действие выполняется с разными значениями — это одна из причин, почему код фреймворка такой длинный (независимо от системы). Сначала создаётся объект QAction — это пара (или связь) текста, который пользователь увидит в качестве выбираемого варианта, и метода, который будет выполняться при выборе этого варианта. По сути, это сопоставление выбора пользователя с фрагментом кода. Строка 3 создаёт его, строка 4 определяет выбор пользователя (как он его увидит), а строка 5 указывает, какой фрагмент кода Python будет выполняться.
Переходя к строке 19 (строка с "self.textInput.setContextMenuPolicy"), создаётся ActionsContextMenu, который служит хранилищем для всех отдельных связей QAction между выбором пользователя и выполняемым кодом. Каждый виджет может иметь только одно контекстное меню (т.е. меню, связанное с щелчком правой кнопкой мыши), поэтому строка 19 определяет это меню. Следующие 4 строки добавляют связи, созданные в начале этого фрагмента кода. Порядок здесь важен: пользователь увидит пункты меню в том порядке, в котором они добавлены. Обратите внимание, что третий пункт меню на самом деле представляет собой нечто незначительное, его код равен null, но он служит для разделения двух групп пунктов в контекстном меню.
Создание числового ввода
# numeric input field
self.numericInput = QtGui.QLineEdit(self)
self.numericInput.setInputMask("999")
self.numericInput.setText("000")
self.numericInput.setFixedWidth(50)
self.numericInput.move(250, 220)
Создание поля для ввода числовых данных фактически повторяет создание поля для ввода текста, описанное ранее. Фактически код идентичен, за исключением 3-й и 4-й строк. В 3-й строке устанавливается маска, определенная PySide, которая в данном случае задает до 3 цифр (включая 0). Полный список кодов InputMask можно найти по адресу QLineEdit InputMask
Отображение окна
# now make the window visible
self.show()
Там всего одна строка, и она приводит к отображению интерфейса после завершения настройки.
Обсуждение кода — Оперативная часть
Теперь перейдём к оперативной части определения интерфейса (GUI), то есть к коду, который выполняется в ответ на взаимодействие пользователя с GUI. Порядок групп операторов не имеет большого значения — с оговоркой, что что-либо должно быть объявлено до того, как на это можно будет сослаться. Некоторые люди помещают все обработчики определённого типа (например, обработчики для кнопок) в одну группу, другие перечисляют обработчики в алфавитном порядке. В конкретных приложениях может существовать причина, связанная с проблемой, по которой все обработчики, относящиеся к определённому аспекту, должны быть собраны вместе.
Между обработчиками наблюдается высокая степень сходства. Большинство из них не получают параметров, фактически единственным параметром (или сигналом) является сам факт их выполнения. Другие, такие как "onPopup1" и "mousePressEvent", принимают параметр.
Между обработчиками, указанными в декларативном разделе, и обработчиком, объявленным в этом, оперативном разделе, должно существовать однозначное соответствие. Могут быть объявлены дополнительные обработчики, которые никогда не вызываются, но при этом не должно быть отсутствующих.
Универсальный обработчик
В этом примере кода обработчики событий используют универсальные методы:
- onCheckbox1
- onCheckbox2
- onRadioButton1
- onRadioButton2
- onPushButton1
- onPopMenuAction1
- onPopMenuAction2
- onPopMenuDivider
- onPopMenuAction3
- onCancel
- onOk
Общий формат обработчиков следующий:
def handlerName(self):
lineOfCode1
lineOfCode2
В первой строке находится ключевое слово "def", за которым следует имя обработчика. Имя обработчика должно точно совпадать с именем из предыдущего раздела декларативного описания. Параметр "self" является частью стандартного синтаксиса, как и заключающая его скобка и символ двоеточия в конце. После завершения первой строки дальнейший код не требует изменений, он является чисто специфичным для приложения.
Обработчик всплывающего меню
def onPopup1(self, selectedText):
Обработчик всплывающего меню аналогичен универсальному обработчику, за исключением того, что в него передается второй параметр — текст, выбранный пользователем. Помните, что все данные поступают из всплывающего меню в виде текста, и даже если пользователь выбрал число 3, оно будет передано в виде строки "3".
Обработчик событий мыши
def mousePressEvent(self, event):
# print mouse position, X & Y
print("X = ", event.pos().x())
print("Y = ", event.pos().y())
#
if event.button() == QtCore.Qt.LeftButton:
print("left mouse button")
if self.label1.underMouse():
print("over the text '"+self.label1.text()+"'")
Обработчик событий мыши аналогичен универсальному обработчику, за исключением того, что в него передается второй параметр — событие мыши (например, щелчок левой кнопкой мыши, щелчок правой кнопкой мыши) от пользователя. Имя обработчика, "mousePressEvent", зарезервировано, и если оно изменено, обработчик больше не будет получать события от нажатий мыши.
Координаты X и Y момента нажатия кнопки мыши задаются с помощью ссылок "event.pos().x()" и "event.pos().y()". Константы "QtCore.Qt.LeftButton" и "QtCore.Qt.RightButton" используются для определения того, какая кнопка мыши была нажата.
Ссылка на виджет может иметь вид "self.widgetName.underMouse()", который вернет true или false в зависимости от того, находится ли курсор мыши над виджетом "widgetName". Хотя обработчик "underMouse()" представлен в том же фрагменте кода, он не привязан к обработчику "mousePressEvent" и может быть использован в любое время.
Обсуждение кода — Основная рутина
Большая часть кода находится в определении класса графического интерфейса, в основной процедуре его немного.
# Constant definitions
global userCancelled, userOK
userCancelled = "Cancelled"
userOK = "OK"
Строки 2, 3 и 4 отвечают за координацию статуса взаимодействия пользователя с графическим интерфейсом — например, «Отменено», «ОК» или любой другой статус, определенный приложением. Обработчики «onCancel» и «onOk», описанные ранее, также устанавливают эти статусы.
form = ExampleGuiClass()
form.exec_()
if form.result==userCancelled:
pass # steps to handle user clicking Cancel
if form.result==userOK:
# steps to handle user clicking OK
localVariable1 = form.label1.text()
localVariable2 = form.label2.text()
localVariable3 = form.label3.text()
localVariable4 = form.label4.text()
Строки 1 и 2 показывают способ вызова графического интерфейса пользователя (GUI). Для программы может быть несколько определений GUI, и GUI не обязательно вызывать первым в файле Python, его можно вызвать в любой момент. Имя класса GUI указывается в строке 1 (в данном случае "ExampleGuiClass"), но остальные 2 строки следует скопировать дословно.
В строках 4 и 6 поле результата используется для определения соответствующего действия. Последние 4 строки просто показывают копирование данных из объекта графического интерфейса в переменные, локальные для выполняемой основной процедуры.
Полный пример кода модального окна
Это полный пример кода (разработанный в FreeCAD версии 0.14):
# import statements
from PySide import QtGui, QtCore
# UI Class definitions
class ExampleModalGuiClass(QtGui.QDialog):
""""""
def __init__(self):
super(ExampleModalGuiClass, self).__init__()
self.initUI()
def initUI(self):
self.result = userCancelled
# create our window
# define window xLoc,yLoc,xDim,yDim
self.setGeometry( 250, 250, 400, 350)
self.setWindowTitle("Our Example Modal Program Window")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
# create some Labels
self.label1 = QtGui.QLabel(" ", self)
self.label1.setFont('Courier') # set to a non-proportional font
self.label1.move(20, 20)
self.label2 = QtGui.QLabel("sample string number two", self)
self.label2.move(20, 70)
self.label3 = QtGui.QLabel(" ", self)
self.label3.setFont('Courier') # set to a non-proportional font
self.label3.move(20, 120)
self.label4 = QtGui.QLabel("can you see this?", self)
self.label4.move(20, 170)
# checkboxes
self.checkbox1 = QtGui.QCheckBox("Left side", self)
self.checkbox1.clicked.connect(self.onCheckbox1)
#self.checkbox1.toggle() # will set an initial value if executed
self.checkbox1.move(210,10)
#
self.checkbox2 = QtGui.QCheckBox("Right side", self)
self.checkbox2.clicked.connect(self.onCheckbox2)
self.checkbox2.move(210,30)
# radio buttons
self.radioButton1 = QtGui.QRadioButton("random string one",self)
self.radioButton1.clicked.connect(self.onRadioButton1)
self.radioButton1.move(210,60)
#
self.radioButton2 = QtGui.QRadioButton("owt gnirts modnar",self)
self.radioButton2.clicked.connect(self.onRadioButton2)
self.radioButton2.move(210,80)
# set up lists for pop-ups
self.popupItems1 = ("pizza","apples","candy","cake","potatoes")
# set up pop-up menu
self.popup1 = QtGui.QComboBox(self)
self.popup1.addItems(self.popupItems1)
self.popup1.setCurrentIndex(self.popupItems1.index("candy"))
self.popup1.activated[str].connect(self.onPopup1)
self.popup1.move(210, 115)
# toggle visibility button
pushButton1 = QtGui.QPushButton('Toggle visibility', self)
pushButton1.clicked.connect(self.onPushButton1)
pushButton1.setAutoDefault(False)
pushButton1.move(210, 165)
# text input field
self.textInput = QtGui.QLineEdit(self)
self.textInput.setText("cats & dogs")
self.textInput.setFixedWidth(190)
self.textInput.move(20, 220)
# set contextual menu options for text editing widget
# set text field to some dogerel
popMenuAction1 = QtGui.QAction(self)
popMenuAction1.setText("load some text")
popMenuAction1.triggered.connect(self.onPopMenuAction1)
# make text uppercase
popMenuAction2 = QtGui.QAction(self)
popMenuAction2.setText("uppercase")
popMenuAction2.triggered.connect(self.onPopMenuAction2)
# menu dividers
popMenuDivider = QtGui.QAction(self)
popMenuDivider.setText('---------')
popMenuDivider.triggered.connect(self.onPopMenuDivider)
# remove all text
popMenuAction3 = QtGui.QAction(self)
popMenuAction3.setText("clear")
popMenuAction3.triggered.connect(self.onPopMenuAction3)
# define menu and add options
self.textInput.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.textInput.addAction(popMenuAction1)
self.textInput.addAction(popMenuAction2)
self.textInput.addAction(popMenuDivider)
self.textInput.addAction(popMenuAction3)
# numeric input field
self.numericInput = QtGui.QLineEdit(self)
self.numericInput.setInputMask("999")
self.numericInput.setText("000")
self.numericInput.setFixedWidth(50)
self.numericInput.move(250, 220)
# cancel button
cancelButton = QtGui.QPushButton('Cancel', self)
cancelButton.clicked.connect(self.onCancel)
cancelButton.setAutoDefault(True)
cancelButton.move(150, 280)
# OK button
okButton = QtGui.QPushButton('OK', self)
okButton.clicked.connect(self.onOk)
okButton.move(260, 280)
# now make the window visible
self.show()
#
def onCheckbox1(self):
text = self.label1.text()
if text[0]==' ':
self.label1.setText('left'+text[4:])
else:
self.label1.setText(' '+text[4:])
def onCheckbox2(self):
text = self.label1.text()
if text[-1]==' ':
self.label1.setText(text[:-5]+'right')
else:
self.label1.setText(text[:-5]+' ')
def onRadioButton1(self):
self.label2.setText(self.radioButton1.text())
def onRadioButton2(self):
self.label2.setText(self.radioButton2.text())
def onPopup1(self, selectedText):
if self.label3.text().isspace():
self.label3.setText(selectedText)
else:
self.label3.setText(self.label3.text()+","+selectedText)
def onPushButton1(self):
if self.label4.isVisible():
self.label4.hide()
else:
self.label4.show()
def onPopMenuAction1(self):
# load some text into field
self.textInput.setText("Lorem ipsum dolor sit amet")
def onPopMenuAction2(self):
# set text in field to uppercase
self.textInput.setText(self.textInput.text().upper())
def onPopMenuDivider(self):
# this option is the divider and is really there as a spacer on the menu list
# consequently it has no functional code to execute if user selects it
pass
def onPopMenuAction3(self):
# clear the text from the field
self.textInput.setText('')
def onCancel(self):
self.result = userCancelled
self.close()
def onOk(self):
self.result = userOK
self.close()
def mousePressEvent(self, event):
# print mouse position, X & Y
print("X = ", event.pos().x())
print("Y = ", event.pos().y())
#
if event.button() == QtCore.Qt.LeftButton:
print("left mouse button")
if self.label1.underMouse():
print("over the text '"+self.label1.text()+"'")
if self.label2.underMouse():
print("over the text '"+self.label2.text()+"'")
if self.label3.underMouse():
print("over the text '"+self.label3.text()+"'")
if self.label4.underMouse():
print("over the text '"+self.label4.text()+"'")
if self.textInput.underMouse():
print("over the text '"+self.textInput.text()+"'")
if event.button() == QtCore.Qt.RightButton:
print("right mouse button")
# Class definitions
# Function definitions
# Constant definitions
userCancelled = "Cancelled"
userOK = "OK"
# code ***********************************************************************************
form = ExampleModalGuiClass()
form.exec_()
if form.result==userCancelled:
pass # steps to handle user clicking Cancel
if form.result==userOK:
# steps to handle user clicking OK
localVariable1 = form.label1.text()
localVariable2 = form.label2.text()
localVariable3 = form.label3.text()
localVariable4 = form.label4.text()
#
#OS: Mac OS X
#Word size: 64-bit
#Version: 0.14.3703 (Git)
#Branch: releases/FreeCAD-0-14
#Hash: c6edd47334a3e6f209e493773093db2b9b4f0e40
#Python version: 2.7.5
#Qt version: 4.8.6
#Coin version: 3.1.3
#SoQt version: 1.5.0
#OCC version: 6.7.0
#
Лучший способ использовать этот код — скопировать его в редактор или файл макроса FreeCAD и поэкспериментировать с ним.
Обсуждение кода - Пример немодального кода
Все виджеты, специфичные для предыдущего примера с модальным окном, переносятся для использования в немодальном окне. Главное отличие заключается в том, что немодальное окно не ограничивает взаимодействие пользователя с другими окнами. По сути, немодальное окно — это окно, которое можно открыть и оставить открытым столько, сколько необходимо, без каких-либо ограничений для других окон приложения. Между двумя вариантами есть небольшое количество различий в коде, которые будут выделены, поэтому этот пример кода довольно краткий. Все, что совпадает с предыдущим примером с модальным окном, будет опущено в целях краткости обзора. Это немодальный графический интерфейс, который генерирует класс PySide:
Import Statement
Обязательный импорт
from PySide import QtGui, QtCore
This is best placed at the top of the Python file.
Class Definition
class ExampleNonmodalGuiClass(QtGui.QMainWindow):
""""""
def __init__(self):
super(ExampleNonmodalGuiClass, self).__init__()
self.initUI()
def initUI(self):
Этот код лучше всего скопировать дословно и внести изменения. Суть этого кода в том, что мы создаём подкласс класса QDialog из PySide. При адаптации этого кода вам потребуется изменить имя класса "ExampleNonmodalGuiClass" — обязательно измените его в обоих местах (например, в строках 1 и 4).
Создание окна
# create our window
# define window xLoc,yLoc,xDim,yDim
self.setGeometry( 250, 250, 400, 150)
self.setWindowTitle("Our Example Nonmodal Program Window")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.setMouseTracking(True)
Очевидно, что размеры окна и заголовок у нас разные. Главное, на что следует обратить внимание, это последняя строка, которая сообщает PySide, что нужно отправлять события положения мыши по мере их возникновения. Обратите внимание, что эти события не будут отправляться, когда курсор мыши находится над виджетом, например, кнопкой, поскольку виджет перехватит события.
Mouse Move Event Handler
def mouseMoveEvent(self,event):
self.label6.setText("X: "+str(event.x()) + " Y: "+str(event.y()))
Этот обработчик получает событие перемещения мыши и отображает его в отформатированном виде. Проверьте, что происходит, когда курсор находится над виджетами или за пределами окна.
Invoking the Window
form = ExampleNonmodalGuiClass()
Еще одно отличие от предыдущего примера заключается в запуске окна. На этот раз для запуска графического интерфейса требуется всего одна строка кода.
Полный пример кода не модального окна
from PySide import QtGui, QtCore
# UI Class definitions
class ExampleNonmodalGuiClass(QtGui.QMainWindow):
""""""
def __init__(self):
super(ExampleNonmodalGuiClass, self).__init__()
self.initUI()
def initUI(self):
self.result = userCancelled
# create our window
# define window xLoc,yLoc,xDim,yDim
self.setGeometry( 250, 250, 400, 150)
self.setWindowTitle("Our Example Nonmodal Program Window")
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.setMouseTracking(True)
# create Labels
self.label4 = QtGui.QLabel("can you see this?", self)
self.label4.move(20, 20)
self.label5 = QtGui.QLabel("Mouse position:", self)
self.label5.move(20, 70)
self.label6 = QtGui.QLabel(" ", self)
self.label6.move(135, 70)
# toggle visibility button
pushButton1 = QtGui.QPushButton('Toggle visibility', self)
pushButton1.clicked.connect(self.onPushButton1)
pushButton1.setMinimumWidth(150)
#pushButton1.setAutoDefault(False)
pushButton1.move(210, 20)
# cancel button
cancelButton = QtGui.QPushButton('Cancel', self)
cancelButton.clicked.connect(self.onCancel)
cancelButton.setAutoDefault(True)
cancelButton.move(150, 110)
# OK button
okButton = QtGui.QPushButton('OK', self)
okButton.clicked.connect(self.onOk)
okButton.move(260, 110)
# now make the window visible
self.show()
#
def onPushButton1(self):
if self.label4.isVisible():
self.label4.hide()
else:
self.label4.show()
def onCancel(self):
self.result = userCancelled
self.close()
def onOk(self):
self.result = userOK
self.close()
def mouseMoveEvent(self,event):
self.label6.setText("X: "+str(event.x()) + " Y: "+str(event.y()))
# Class definitions
# Function definitions
# Constant definitions
global userCancelled, userOK
userCancelled = "Cancelled"
userOK = "OK"
# code ***********************************************************************************
form = ExampleNonmodalGuiClass()
#
#OS: Mac OS X
#Word size: 64-bit
#Version: 0.14.3703 (Git)
#Branch: releases/FreeCAD-0-14
#Hash: c6edd47334a3e6f209e493773093db2b9b4f0e40
#Python version: 2.7.5
#Qt version: 4.8.6
#Coin version: 3.1.3
#SoQt version: 1.5.0
#OCC version: 6.7.0
Разные дополнительные темы
В графическом интерфейсе (GUI) пространство экрана определяется тремя понятиями:
- физическое пространство на экране
- рамка
- геометрия
В программном обеспечении все эти параметры измеряются в пикселях. PySide имеет функцию измерения в реальных единицах измерения, но эти данные ненадежны, поскольку производители не имеют стандартов для размера пикселя или соотношения сторон.
Размер рамки (Frame) — это размер окна, включая боковые панели, верхнюю панель (возможно, с меню) и нижнюю панель. Геометрия (Geometry) — это пространство внутри рамки, поэтому она всегда меньше или равна размеру рамки. В свою очередь, размер рамки всегда меньше или равен доступному размеру экрана.
Доступные размеры экрана
# get screen dimensions (Available Screen Size)
screenWidth = QtGui.QDesktopWidget().screenGeometry().width()
screenHeight = QtGui.QDesktopWidget().screenGeometry().height()
# get dimensions for available space on screen
availableWidth = QtGui.QDesktopWidget().availableGeometry().width()
availableHeight = QtGui.QDesktopWidget().availableGeometry().height()
Как правило, значение параметра "availableHeight" должно быть меньше значения "screenHeight" на высоту строки меню. Эти 4 значения зависят от аппаратной среды и могут различаться на разных компьютерах. Они не зависят от размера окна какого-либо приложения.
(Since Python 3.9 this warning appears when the above code is executed: DeprecationWarning: QDesktopWidget.screenGeometry(int screen) const is deprecated. A replacement seems to be needed from Python 3.10 onwards.)
Размер и геометрия рамки
# set up a variable to hold the Main Window to save some typing...
mainWin = FreeCAD.Gui.getMainWindow()
mainWin.showFullScreen() # no menu bars, every last pixel is given over to FreeCAD
mainWin.geometry()
mainWin.frameSize()
mainWin.frameGeometry()
mainWin.showMaximized() # show maximised within the screen, window edges and the menu bar will be displayed
mainWin.geometry()
mainWin.frameSize()
mainWin.frameGeometry()
mainWin.showNormal() # show at the last non-maximised or non-minimised size (and location)
mainWin.geometry()
mainWin.frameSize()
mainWin.frameGeometry()
mainWin.setGeometry(50, 50, 800, 800) # specifically set FreeCAD main window's size and location, this will become the new setting for 'showNormal()'
mainWin.showMinimized() # FreeCAD will disappear from view after this command...
mainWin.geometry()
mainWin.frameSize()
mainWin.frameGeometry()
Эти же команды можно выполнить в окне, созданном пользователем, синтаксис при этом не меняется.
Эта страница получена от https://wiki.freecad.org/PySide_Intermediate_Examples