FreeCAD Logo FreeCAD 1.0
  • Английский Африкаанс Арабский Белорусский Каталанский Чешский Немецкий Греческий Испанский Испанский Баскский Финский Филиппинский Французский Галисийский Хорватский Венгерский Индонезийский Итальянский Японский Кабильский Корейский Литовский Нидерландский Норвежская букмол Польский Португальский Португальский Румынский Русский Словацкий Словенский Сербский Шведский Турецкий Украинский Валенсианский Вьетнамский Китайский Китайский
  • Возможности
  • Скачать
  • Блог
  • Документация
    Индекс документации Приступая к работе Пользовательская документация Руководство по FreeCAD Документация верстаков Документация по кодированию на Python Документация по коду C++ Уроки Часто задаваемые вопросы Политика конфиденциальности О программе FreeCAD
  • Внести вклад
    Как помочь Sponsor Сообщить об ошибке Сделать запрос Задачи и финансирование Руководство по участию в разработке Руководство для разработчиков Переводы
  • Сообщество
    Кодекс поведения Форум The FPA GitHub GitLab Codeberg Mastodon Matrix IRC IRC via Webchat Gitter Discord Reddit Twitter Facebook LinkedIn Календарь
  • ♥ Donate

Donate

$
Информация о SEPA
Пожалуйста, настройте банковский перевод SEPA:
Beneficiary: The FreeCAD project association
IBAN: BE04 0019 2896 4531
BIC/SWIFT: GEBABEBBXXX
Банковское агентство: BNP Paribas Fortis
Адрес: Rue de la Station 64, 1360 Perwez, Belgium

While Stripe doesn't support monthly donations, you can still become a sponsor! Simply make a one-time donation equivalent to 12 months of support, and you'll gain access to the corresponding sponsoring tier. It's an easy and flexible way to contribute.

If you are not sure or not able to commit to a regular donation, but still want to help the project, you can do a one-time donation, of any amount.

Choose freely the amount you wish to donate one time only.

You can support FreeCAD by sponsoring it as an individual or organization through various platforms. Sponsorship provides a steady income for developers, allowing the FPA to plan ahead and enabling greater investment in FreeCAD. To encourage sponsorship, we offer different tiers, and unless you choose to remain anonymous, your name or company logo will be featured on our website accordingly.

from 1 USD / 1 EUR per month. You will not have your name displayed here, but you will have helped the project a lot anyway. Together, normal sponsors maintain the project on its feet as much as the bigger sponsors.

from 25 USD / 25 EUR per month. Your name or company name is displayed on this page.

from 100 USD / 100 EUR per month. Your name or company name is displayed on this page, with a link to your website, and a one-line description text.

from 200 USD / 200 EUR per month. Your name or company name and logo displayed on this page, with a link to your website and a custom description text. Companies that have helped FreeCAD early on also appear under Gold sponsors.

Instead of donating each month, you might find it more comfortable to make a one-time donation that, when divided by twelve, would give you right to enter a sponsoring tier. Don't hesitate to do so!

Choose freely the amount you wish to donate each month.

Please inform your forum name or twitter handle as a notein your transfer, or reach to us, so we can give you proper credits!

Введение

На этой странице представлены примеры среднего уровня сложности работы с графическим фреймворком 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

Свяжитесь с нами!
Forum GitHub Mastodon Matrix IRC Gitter.im Discord Reddit Twitter Facebook LinkedIn

©Команда FreeCAD. Авторы изображений (сверху вниз): ppemawm, r-frank, epileftric, regis, regis, rider_mortagnais, bejant.

Этот проект поддерживается: , KiCad Services Corp. и другие спонсоры

GitHubУлучшить эту страницу на GitHub