PySide Intermediate Examples/pl

Wprowadzenie

Ta strona przedstawia przykłady o średnim poziomie zaawansowania menedżera GUI PySide (strony towarzyszące obejmują zagadnienia mniej lub bardziej zaawansowane: Podstawowe przykłady PySide oraz Zaawansowane przykłady PySide). Na tej stronie wykorzystano przykładowy program do omówienia różnych tematów związanych z PySide. Celem jest przedstawienie działającego kodu PySide, tak aby każdy, kto potrzebuje korzystać z PySide, mógł skopiować odpowiednią część, zmodyfikować ją i dostosować do własnych potrzeb.

Uwagi

Dyskusja oparta na kodzie - część deklaratywna

"Przykładowy program" jest w rzeczywistości dużą definicją klasy, definicją klasy GUI PySide, i zawiera ponad 150 linii kodu (łącznie z komentarzami). Klasa ani jej zachowanie nie mają funkcjonalnego celu; jedynym celem jest pokazanie możliwych działań GUI oraz przedstawienie kodu, który, miejmy nadzieję, może być wykorzystany przez innych użytkowników FreeCAD.

Definicja klasy oraz niewielka liczba linii kodu, które ją wywołują, są opisane w kolejności, w jakiej występują w pliku. Kolejność ta opiera się na układzie ekranu, który jest dość dowolny i ma jedynie na celu pokazanie funkcji. Oto modalny ekran GUI generowany przez klasę PySide:

Większa część pozostałej części tej sekcji opisuje zawartość definicji klasy, która znajduje się na końcu tej sekcji. Najpierw omówimy elementy deklaratywne, które definiują sposób działania i sposób składania GUI, a następnie przejdziemy do sekcji operacyjnych (czyli kodu, który zostanie wykonany w trakcie interakcji użytkownika). To okno opiera się na klasie QDialog, więc jest modalne – co oznacza, że nie można wykonywać żadnych działań poza oknem, dopóki jest ono otwarte.

Określenie importu

Obowiązkowe określenie importu

from PySide import QtGui, QtCore

Najlepiej to umieścić w górnej części pliku Pythona.

Definicja klasy

class ExampleModalGuiClass(QtGui.QDialog):
	""""""
	def __init__(self):
		super(ExampleModalGuiClass, self).__init__()
		self.initUI()
	def initUI(self):

Ten kod najlepiej skopiować w całości i następnie zmodyfikować. Istota kodu polega na tym, że tworzymy podklasę klasy QDialog z PySide. Przy dostosowywaniu tego kodu warto zmienić nazwę klasy "ExampleModalGuiClass" – upewnij się, że zmienisz ją w obu miejscach (np. linie 1 i 4).

Status zwracany przez okno

self.result = userCancelled

Nie jest to obowiązkowe, lecz stanowi dobrą praktykę programistyczną - ustawia domyślny status zwracany przez okno, który będzie obecny niezależnie od działań użytkownika. Później w kodzie status ten może zostać zmieniony przez kod Pythona, aby wskazać różne opcje wybrane przez użytkownika.

Tworzenie okna

# 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)

Mając na uwadze, że wymiary ekranu mierzone są od lewego górnego rogu, w 3. linii wartości odnoszą się do:

Tytuł okna jest ustawiony, a ostatnia linia oznacza po prostu, że to okno nigdy nie zostanie zasłonięte przez inne okno - jeśli nie jest to pożądane, wystarczy umieścić znak komentarza Pythona ('#') jako pierwszy znak tej linii.

Tworzenie etykiety

# 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)

W PySide etykiety pełnią dwie funkcje: statyczne etykiety (jak sama nazwa wskazuje) oraz pola tekstowe tylko do odczytu (czyli wyłącznie do wyświetlania). Dzięki temu można przekazywać użytkownikowi zarówno niezmienne instrukcje, takie jak "Nie naciskaj czerwonego przycisku", jak i dynamiczne wyniki obliczeń, np. "42". Druga linia deklaruje etykietę i ustawia jej wartość początkową (w tym przypadku pustą). Trzecia linia określa czcionkę - można podać dowolną czcionkę dostępną w systemie, a jeśli nie zostanie określona, używana jest czcionka domyślna. W tym przypadku czcionka jest ustawiona jako nieproporcjonalna. Etykieta jest następnie przesuwana do swojej pozycji w oknie - jej współrzędne określają pozycję względem okna, a nie ekranu.

Tworzenie przycisku wyboru

# 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)

Przyciski wyboru mogą być włączone lub wyłączone w dowolnej kombinacji (w przeciwieństwie do przycisków radiowych). Linia 2 deklaruje pole wyboru i ustawia jego wartość początkową. Linia 3 określa, która metoda zostanie wywołana po kliknięciu pola wyboru (w tym przypadku metoda 'onCheckBox1'). Gdyby linia 4 nie zaczynała się od znaku komentarza Pythona ('#'), zostałaby wykonana i zaznaczyłaby pole wyboru. Wreszcie, linia 5 ustawia pozycję pola wyboru w oknie.

Tworzenie przycisku radiowego

# 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)

Tworzenie przycisków radiowych jest bardzo podobne do przycisków wyboru. Jedyną istotną różnicą jest ich zachowanie - w danym momencie może być włączony tylko jeden przycisk radiowy.

Tworzenie wyskakującego menu

# 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)

W linii 2 tworzona jest lista opcji, z których użytkownik będzie mógł wybierać. Alternatywnie można utworzyć słownik, wykorzystując jednak wyłącznie klucze jako listę opcji menu. Linia 4 tworzy menu rozwijane (w PySide znane jako ComboBox), a linia 5 dodaje do niego opcje użytkownika.

Jako uwaga dodatkowa, gdyby użyto słownika, linie wyglądałyby następująco:

self.popupItems1 = OrderedDict([("2","widget"),("pink","foobar"),("4","galopsis")])

self.popup1.addItems(self.popupItems1.keys())

Wracając do głównego przykładu kodu w tej sekcji, linia 6 ustawia wartość domyślną - można ją pominąć, a wartość domyślnego wyboru można również wczytać do odpowiadającej etykiety (jeśli jest to stosowne). Wreszcie, linia 8 ustawia pozycję elementu w oknie.

Tworzenie przycisku część 1

# toggle visibility button
pushButton1 = QtGui.QPushButton('Toggle visibility', self)
pushButton1.clicked.connect(self.onPushButton1)
pushButton1.setAutoDefault(False)
pushButton1.move(210, 165)

Przycisk jest tworzony w linii 2 wraz z jego nazwą, natomiast obsługa sygnału po kliknięciu określona jest w linii 3. Linia 4 zapobiega ustawieniu przycisku jako 'domyślnego' - czyli takiego, który zostanie kliknięty, gdy użytkownik naciśnie klawisz Powrót. Na końcu element jest przenoszony do odpowiedniej pozycji w oknie, co kończy segment kodu.

Tworzenie przycisku część 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)

Oba przyciski są tworzone z nazwą (która pojawi się jako ich etykieta), powiązane z metodą, która zostanie wykonana po kliknięciu, i przeniesione do odpowiedniej pozycji. Wyjątkiem jest linia 4, która ustawia przycisk 'Anuluj' jako przycisk domyślny – oznacza to, że zostanie "kliknięty", jeśli użytkownik naciśnie klawisz Powrót.

Tworzenie pola tekstowego

# text input field
self.textInput = QtGui.QLineEdit(self)
self.textInput.setText("cats & dogs")
self.textInput.setFixedWidth(190)
self.textInput.move(20, 220)

Widżet QLineEdit jest prawdopodobnie najczęściej używanym do wprowadzania tekstu przez użytkownika. W tym przykładzie kolejna sekcja kodu ustawi dla niego menu kontekstowe. Ta sekcja kodu: tworzy widżet (linia 2), ustawia wartość początkową (linia 3), określa szerokość pola (linia 4) i przenosi widżet na właściwe miejsce (linia 5).

Tworzenie 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')

Widżet Gui::QuantitySpinBox jest specjalnym elementem FreeCAD, zaprojektowanym do wyświetlania i obsługi wartości wraz z ich jednostkami. Pochodzi on z klasy Qt QAbstractSpinBox class. Aby zobaczyć wszystkie jego właściwości, sprawdź listę w pliku źródłowym QuantitySpinBox.h.

Tworzenie menu kontekstowego

# 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)

Ten kod zawiera liczne powtórzenia, ponieważ ta sama akcja jest wykonywana dla różnych wartości - to jedna z przyczyn, dla których kod GUI jest tak obszerny (bez względu na system). Najpierw tworzony jest obiekt QAction - jest to powiązanie tekstu, który użytkownik zobaczy jako opcję do wyboru, z metodą, która zostanie wykonana, jeśli użytkownik wybierze tę opcję. W praktyce jest to sparowanie wyboru użytkownika z fragmentem kodu. Linia 3 tworzy obiekt, linia 4 definiuje opcję użytkownika (tak jak ją zobaczy), a linia 5 określa, który fragment kodu Pythona zostanie wykonany.

Przechodząc do linii 19 (tej z "self.textInput.setContextMenuPolicy"), tworzony jest ActionsContextMenu, który jest kontenerem dla wszystkich oddzielnych powiązań QAction między wyborem użytkownika a kodem do wykonania. Każdy widżet może mieć tylko jedno menu kontekstowe (czyli menu wywoływane prawym przyciskiem myszy), więc linia 19 definiuje to menu. Następne 4 linie dodają powiązania utworzone na początku tej sekcji kodu. Kolejność ma tutaj znaczenie - użytkownik zobaczy opcje menu w kolejności, w jakiej zostały dodane. Zauważ, że trzecia opcja menu jest właściwie niczym - jej kod jest pusty, ale służy do oddzielenia dwóch grup opcji w menu kontekstowym.

Tworzenie pola liczbowego

# 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)

Tworzenie pola do wprowadzania danych liczbowych w zasadzie przebiega tak samo, jak w przypadku pola tekstowego omówionego wcześniej. W rzeczywistości kod jest identyczny, z wyjątkiem linii 3 i 4. Linia 3 ustawia Maskę zdefiniowaną przez PySide, która w tym przypadku określa maksymalnie 3 cyfry (w tym również 0). Pełną listę kodów InputMask można znaleźć na stronie QLineEdit InputMask.

Wyświetlanie okna

# now make the window visible
self.show()

Jest tylko jedna linia i powoduje, że GUI jest wyświetlane po konfiguracji.

Dyskusja oparta na kodzie - część operacyjna

Przechodzimy teraz do części operacyjnej definicji GUI, czyli kodu, który jest wykonywany w odpowiedzi na interakcje użytkownika z GUI. Kolejność grup instrukcji nie jest zbyt istotna - z zastrzeżeniem, że coś musi zostać zadeklarowane, zanim będzie można się do tego odwołać. Niektórzy umieszczają wszystkie obsługi konkretnego typu (np. obsługę przycisków) w jednej grupie, inni wymieniają je alfabetycznie. W konkretnych zastosowaniach może istnieć uzasadniona przyczynowo potrzeba zebrania wszystkich obsług obsługujących dany aspekt w jednym miejscu.

Istnieje duże podobieństwo między poszczególnymi handlerami. Większość z nich nie przyjmuje żadnego parametru – sam fakt wywołania stanowi w zasadzie jedyny parametr (lub sygnał), jaki otrzymują. Inne, takie jak "onPopup1" czy "mousePressEvent", przyjmują parametr.

Musi istnieć dokładne odwzorowanie jeden do jednego między handlerami określonymi w części deklaratywnej a handlerami zadeklarowanymi w tej, operacyjnej. Mogą być zadeklarowane dodatkowe handlery, które nigdy nie są wywoływane, ale nie może brakować żadnego z tych, które powinny być powiązane.

Ogólny handler

W tym przykładzie kodu, ogólne handlery obsługują następujące zdarzenia:

Ogólna forma handlera to:

def handlerName(self):
	lineOfCode1
	lineOfCode2

Pierwsza linia zawiera słowo kluczowe "def" oraz nazwę handlera. Nazwa handlera musi dokładnie odpowiadać nazwie z wcześniejszej części deklaratywnej. Parametr "self" jest częścią standardowej składni, podobnie jak nawiasy otaczające i końcowy dwukropek. Po zakończeniu pierwszej linii nie ma żadnych wymagań co do kolejnego kodu - jego zawartość jest w pełni specyficzna dla aplikacji.

Handler menu wyskakującego

def onPopup1(self, selectedText):

Handler menu wyskakującego działa tak samo jak ogólny handler, z tą różnicą, że przekazywany jest drugi parametr - tekst wybrany przez użytkownika. Należy pamiętać, że wszystko pochodzi jako tekst z menu wyskakującego i nawet jeśli użytkownik wybierze liczbę 3, zostanie ona przekazana jako ciąg znaków "3".

Handler akcji myszki

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()+"'")

Handler zdarzeń myszy działa tak samo jak ogólny handler, z tą różnicą, że przekazywany jest drugi parametr - zdarzenie myszy (np. kliknięcie lewym lub prawym przyciskiem) wywołane przez użytkownika. Nazwa handlera "mousePressEvent" jest zarezerwowana i jeśli zostanie zmieniona, handler przestanie otrzymywać zdarzenia związane z kliknięciami myszy.

Współrzędne X i Y kliknięcia myszy można uzyskać za pomocą odwołań "event.pos().x()" i "event.pos().y()". Stałe "QtCore.Qt.LeftButton" i "QtCore.Qt.RightButton" służą do określenia, który przycisk myszy został naciśnięty.

Odwołanie do widżetu można utworzyć w postaci "self.widgetName.underMouse()", które zwróci PRAWDA lub FAŁSZ, w zależności od tego, czy kursor myszy znajduje się nad widżetem "widgetName". Chociaż przedstawiono to w tym samym fragmencie kodu, handler "underMouse()" nie jest powiązany z handlerem "mousePressEvent" i może być używany w dowolnym momencie.

Dyskusja oparta na kodzie - główna procedura

Większość kodu znajduje się w definicji klasy GUI, natomiast w głównej procedurze nie ma go wiele.

# Constant definitions
global userCancelled, userOK
userCancelled = "Cancelled"
userOK = "OK"

Linie 2, 3 i 4 zajmują się koordynowaniem statusu interakcji użytkownika z GUI - np. Anulowano, OK lub innym statusem zdefiniowanym przez aplikację. Wcześniejsze procedury handlerów "onCancel" i "onOk" również ustawiają te statusy.

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()

Linie 1 i 2 pokazują metodę wywołania GUI. W programie może istnieć wiele definicji GUI, a GUI nie musi być wywoływane jako pierwsze w pliku Pythona - można je uruchomić w dowolnym momencie. Nazwa klasy GUI jest określona w linii 1 ("ExampleGuiClass" w tym przypadku), natomiast pozostałą część tych dwóch linii należy skopiować dosłownie.

Linie 4 i 6 wykorzystują pole result do określenia odpowiedniego działania. Ostatnie 4 linie pokazują po prostu kopiowanie danych z obiektu GUI do zmiennych lokalnych w wykonywanej głównej procedurze.

Pełny przykład kodu modalnego

To kompletny przykład kodu (opracowany we FreeCAD v0.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
#

Najlepszym sposobem wykorzystania tego kodu jest skopiowanie go do edytora lub pliku makra FreeCAD i eksperymentowanie z nim.

Dyskusja oparta na kodzie - przykład kodu niemodalnego

Wszystkie widżety z poprzedniego przykładu modalnego można przenieść do okna niemodalnego. Główna różnica polega na tym, że okno niemodalne nie ogranicza użytkownika w interakcji z innymi oknami. W praktyce oznacza to, że okno niemodalne można otworzyć i pozostawić otwarte tak długo, jak jest to potrzebne, bez nakładania ograniczeń na inne okna aplikacji. Istnieje niewielka liczba różnic w kodzie między obiema wersjami, które zostaną tutaj podkreślone, dlatego przykład kodu jest dość krótki. Wszystko, co jest takie samo jak w poprzednim przykładzie modalnym, zostanie pominięte w celu zachowania zwięzłości przeglądu. Oto ekran GUI niemodalnego generowany przez klasę PySide:

Określenie importu

Obowiązkowe określenie importu

from PySide import QtGui, QtCore

Najlepiej to umieścić w górnej części pliku Pythona.

Definicja klasy

class ExampleNonmodalGuiClass(QtGui.QMainWindow):
	""""""
	def __init__(self):
		super(ExampleNonmodalGuiClass, self).__init__()
		self.initUI()
	def initUI(self):

Ten kod najlepiej skopiować w całości i następnie zmodyfikować. Istota kodu polega na tym, że tworzymy podklasę klasy QMainWindow z PySide. Przy dostosowywaniu tego kodu warto zmienić nazwę klasy "ExampleNonmodalGuiClass" - upewnij się, że zmienisz ją w obu miejscach (np. linie 1 i 4).

Tworzenie okna

# 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)

Oczywiście wymiary naszego okna i jego tytuł są inne. Głównym punktem, na który warto zwrócić uwagę, jest ostatnia linia, która informuje PySide, że ma wysyłać zdarzenia pozycji myszy w czasie rzeczywistym. Należy pamiętać, że zdarzenia te nie będą wysyłane, gdy kursor znajduje się nad widżetem, takim jak przycisk, ponieważ widżet przechwyci te zdarzenia.

Handler akcji poruszania myszką

def mouseMoveEvent(self,event):
	self.label6.setText("X: "+str(event.x()) + " Y: "+str(event.y()))

Ten handler odbiera zdarzenie poruszenia myszy i wyświetla jego sformatowaną postać. Przetestuj, co się dzieje, gdy kursor znajduje się nad widżetami lub poza oknem.

Wywoływanie okna

form = ExampleNonmodalGuiClass()

Wywołanie okna to kolejna różnica w porównaniu z poprzednim przykładem. Tym razem do uruchomienia GUI wystarczy tylko jedna linia.

Pełny przykład kodu niemodalnego

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

Różne tematy dodatkowe

W środowisku GUI wyróżnia się trzy pojęcia związane z przestrzenią ekranu:

W obrębie oprogramowania wszystkie są mierzone w pikselach. PySide posiada funkcje pozwalające mierzyć w jednostkach rzeczywistych, ale są one zawodowe, ponieważ producenci nie stosują standardów dotyczących rozmiaru piksela ani proporcji ekranu.

Ramka to rozmiar okna obejmujący jego paski boczne, górny pasek (ewentualnie z menu) oraz dolny pasek. Geometria to przestrzeń znajdująca się wewnątrz ramki, więc zawsze jest mniejsza lub równa ramce. Z kolei ramka nigdy nie przekracza dostępnego rozmiaru ekranu.

Dostępny rozmiar ekranu

# 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()

Zazwyczaj "availableHeight" jest mniejsze od "screenHeight" o wysokość paska menu. Te cztery wartości zależą od środowiska sprzętowego i mogą się różnić w zależności od komputera. Nie zależą od rozmiaru żadnego okna aplikacji.

(Od Pythona 3.9 przy wykonywaniu powyższego kodu pojawia się ostrzeżenie: DeprecationWarning: QDesktopWidget.screenGeometry(int screen) const is deprecated. Od Pythona 3.10 wydaje się konieczne zastosowanie alternatywnego rozwiązania.)

Rozmiar ramki i geometria

# 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()

Te same polecenia można wykonać na oknie utworzonym przez użytkownika - składnia pozostaje bez zmian.