PySide Intermediate Examples/de

Einleitung

Diese Seite behandelt Beispiele auf mittlerem Niveau für die PySide-GUI-Verwaltung (die begleitenden Seiten behandeln Aspekte, die weniger oder weiter fortgeschritten sind, PySide-Beispiele für Anfänger und PySide-Beispiele für Fortgeschrittene). Auf dieser Seite wird ein Beispielprogramm verwendet, um die verschiedenen PySide-Themen abzudecken. Es ist beabsichtigt, etwas funktionalen PySide-Code vorzustellen, so dass jeder, der PySide benutzen muss, den entsprechenden Abschnitt herauskopieren, modifizieren und an seine eigenen Zwecke anpassen kann.

Hinweise

Codebasierte Diskussion - Vereinbarungsteil

Das "Beispielprogramm" ist eigentlich eine große Klassendefinition, die Festlegung einer PySide-GUI-Klasse, und hat über 150 Zeilen Code (einschließlich Kommentaren). Die Klasse bzw. ihr Verhalten dienen keinem funktionalen Zweck, der einzige Zweck ist, mögliche GUI-Aktionen darzustellen und etwas Code zu präsentieren, der hoffentlich von anderen FreeCAD-Benutzern verwendet werden kann.

Die Klassendefinition und die geringe Anzahl von Codezeilen, die aufgerufen werden, werden in der Reihenfolge beschrieben, in der sie in der Datei vorkommen. Diese Reihenfolge basiert auf dem Bildschirmlayout, das ziemlich willkürlich ist und ausschließlich der Darstellung von Anzeigeelementen dient. Dies ist der modale GUI-Bildschirm, den die PySide-Klasse erzeugt:

Das Meiste des Restes dieses Abschnitts beschreibt den Inhalt der Klassendefinition, die am Ende dieses Abschnitts erscheint. Zuerst behandeln wir die Vereinbarungselemente (declarative elements), die festlegen wie die Dinge funktionieren und wie die Benutzeroberfläche (GUI) aufgebaut ist; dann behandeln wir die operativen Abschnitte (d.h. den Code, der ausgeführt wird, wenn Interaktionen des Benutzers erfolgen). Dieses Fenster basiert auf der Klasse QDialog und ist daher modal, was bedeutet, dass keine Aktivitäten außerhalb des Fensters erfolgen können, solange es geöffnet ist.

Import-Anweisung

Die zwingend erforderliche Import-Anweisung:

from PySide import QtGui, QtCore

Diese wird am besten am Anfang der Python-Datei eingesetzt.

Klassendefinition

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

Dieser Code wird am besten Wort für Wort kopiert und dann angepasst. Die Idee dieses Codes ist, dass wir eine Unterklasse (sub-class) der Klasse QDialog von PySide erstellen. Beim Übernehmen des Codes wird der Name der Klasse zu "ExampleModalGuiClass" geändert; dabei ist darauf zu achten ihn an beiden Stellen zu ändern (im Beispiel in Zeile 1 und 4).

Fenster-Rückgabestatus

self.result = userCancelled

Dieser ist nicht zwingend erforderlich sondern eher guter Programmierstil, er gibt einen Return-Status für das Fenster vor, der vorhandenen ist, egal, was der Anwender ausführt. Dieser kann im Verlauf des Codes durch den Python-Code zum Anzeigen von verschiedenen Optionen, die der Benutzer ausgewählt haben könnte, angepasst werden.

Fenstererstellung

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

Da die Bildschirmabmessungen von der oberen linken Ecke aus gemessen werden, beziehen sich die Werte in der dritten Zeile auf:

Der Titel des Fensters ist festgelegt, und die letzte Zeile bedeutet lediglich, dass dieses Fenster niemals von einem anderen Fenster verdeckt wird. Wenn dies nicht gewünscht ist, setzt man einfach ein Python-Kommentarzeichen ('#') als erstes Zeichen der Zeile.

Label-Erstellung

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

In PySide dienen Labels zwei Zwecken: als statische Labels (wie der Name schon sagt) sowie als schreibgeschützte (d. h. nur zur Anzeige bestimmte) Textfelder. So können sowohl unveränderliche Anweisungen an den Benutzer wie "Drücken Sie nicht den roten Knopf" als auch dynamische Berechnungsergebnisse wie "42" an den Benutzer übermittelt werden. In der zweiten Zeile wird ein Label deklariert und sein Anfangswert festgelegt (in diesem Fall leer). Die dritte Zeile legt die Schriftart fest. Es kann jede beliebige Schriftart (auf dem System) angegeben werden. Wenn keine Angabe erfolgt, wird die Standardschriftart verwendet. In diesem Fall wird eine nicht proportionale Schriftart angegeben. Das Label wird an seine Position im Fenster verschoben – seine Koordinaten geben seine Position in Bezug auf das Fenster (nicht den Bildschirm) an.

Erstellen von Checkboxen

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

Checkboxen können in beliebiger Kombination aktiviert und deaktiviert werden (im Gegensatz zu Radiobuttons). Zeile 2 deklariert eine Checkbox und legt ihren Anfangswert fest. Zeile 3 gibt an, welche Methode ausgeführt wird, wenn die Checkbox angeklickt wird (in diesem Fall die Methode 'onCheckBox1'). Wenn die vierte Zeile nicht mit dem Python-Kommentarzeichen ('#') beginnen würde, würde sie ausgeführt werden und die Checkbox als aktiviert markieren. Die fünfte Zeile schließlich verschiebt die Checkbox an ihre Position.

Erstellen von Radiobuttons

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

Die Erstellung der Radiobuttons ähnelt stark der Erstellung von Checkboxen. Der einzige Unterschied besteht darin, dass bei Radiobuttons jeweils nur einer aktiviert sein kann.

Pop-Up Menu Creation

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

In Zeile 2 wird eine Liste mit den Auswahlmöglichkeiten des Benutzers erstellt. Eine Alternative besteht darin, ein Wörterbuch zu erstellen, aber nur die Schlüssel für die Liste der Menüoptionen zu verwenden. In Zeile 4 wird das Popup-Menü (in PySide als ComboBox bezeichnet) erstellt, die Benutzeroptionen werden in Zeile 5 hinzugefügt.

Nebenbei bemerkt, wenn das Wörterbuch verwendet worden wäre, würden die Zeilen wie folgt erscheinen:

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

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

Returning to the main code sample for this section, line 6 sets the default choice, this line may be omitted, also the value of the default choice could be loaded into the corresponding Label (once again if appropriate). And finally the move into position at line 8.

Erstellung von Schaltflächen Teil 1

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

The button is created in line 2 with it's name, the handler for it's signal when clicked is specified in line 3. Line 4 is there to prevent the button from becoming the 'default button' - the button that will be clicked if the user simply presses the Return key. And a move to position finished up the code segment.

Erstellung von Schaltflächen Teil 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)

Beide Schaltflächen werden mit einem Namen (der als Beschriftung angezeigt wird) erstellt, der mit einer Methode verknüpft ist, die beim Klicken ausgeführt wird, und an die richtige Position verschoben. Die einzige Ausnahme ist Zeile 4, in der die Schaltfläche 'Abbrechen' als Standardschaltfläche festgelegt ist. Das bedeutet, dass sie "angeklickt" wird, wenn der Benutzer die Taste Return drückt.

Text Input Creation

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

The QLineEdit widget is probably the most common for user textual input, in this example the code section after this one will set up a contextual menu to operate on it. This code section creates (line 2), sets an initial value (line 3), sets a width to the field (line 4) and moves the widget into place (line 5).

QuantitySpinBox Creation

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

The Gui::QuantitySpinBox widget is a FreeCAD-special, designed to display and handle values together with their units. It is derived from Qt's QAbstractSpinBox class. For all its properties see the list in the source code file QuantitySpinBox.h

Contextual Menu Creation

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

This code has numerous repetitions as the same action is performed with different values - this is part of what makes GUI code so lengthy (no matter what the system). First a QAction is created - it is a pairing (or linkage) of the text that the user will see as their selectable option along with the method that will execute if they select that option. It is basically a pairing of a user choice with a piece of code. Line 3 creates it, line 4 defines the user option (as they will see it) and line 5 specifies which piece of Python code will execute.

Skipping to line 19 (the line with "self.textInput.setContextMenuPolicy") a ActionsContextMenu is created which is holder for all the separate QAction linkages between user choice and code to execute. Each widget can only have a single Contextual Menu (i.e. the menu associated with the right-click) so line 19 defines that menu. The following 4 lines add the linkages created at the beginning of this code section. Order is significant here, the user will see the menu options in the order they are added. Notice that the 3rd menu option is really a bit of nothing, it's code is null but it serves to separate 2 groups of options on the Contextual Menu.

Numeric Input Creation

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

The creation of the field for numeric input really follows that for Text Input earlier. In fact the code is identical with exception of the 3rd and 4th lines. The 3rd line sets the Mask as defined by PySide, which in this case specifies up to 3 digits (which may include 0). A full list of the InputMask codes can be found at QLineEdit InputMask

Window Display

# now make the window visible
self.show()

There is only one line and it causes the GUI to be displayed after the setup.

Code Based Discussion - Operative Portion

We now move onto the operative portion of the GUI definition which is the code that executes in response to user interactions with the GUI. The order of statement groups is not very relevant - with the caveat that something must be declared before it can be referenced. Some people put all the handlers of a certain type (e.g. handlers for buttons) in one group, others list the handlers alphabetically. For specific application there may be a problem related reason that all handlers relating to a specific aspect be gathered together

There is a high degree of similarity between the handlers. Most do not receive a parameter, the fact they are executing is realy the only parameter (or signal) they get. Others like "onPopup1" and "mousePressEvent" accept a parameter.

There must be a one to one correspondance between the handlers specified in the declarative section and the handler declared in this, the operative section. There may be extra handlers declared which are never invoked but there may not be any missing.

Generische Handler

In diesem Codebeispiel behandeln generische Handler die folgenden Ereignisse:

Die allgemeine Form für die Handler lautet:

def handlerName(self):
	lineOfCode1
	lineOfCode2

The first line has the keyword "def" followed by the handler name. The handler name must match the name from the earlier declarative section exactly. The parameter "self" is part of the standard syntax as are the enclosing parenthesis and the final colon character. Once the first line is finished then there are no requirements of the following code, it is purely application specific.

Popup-Menü-Handler

def onPopup1(self, selectedText):

The Pop-Up menu handler is the same as the generic handler with exception that a second parameter, the text selected by the user, is passed in. Remember that everything is text coming from the Pop-Up menu and even if the user has selected the number 3, it will be passed in as the string "3".

Mausereignis-Handler

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

The Mouse Event handler is the same as the generic handler with exception that a second parameter, the mouse event (e.g. left-click, right-click) from the user is passed in. The name of the handler, "mousePressEvent", is reserved and if it is changed then the handler will no longer receive the event from the mouse presses.

The X and Y coordinates of the mouse press are given by the reference "event.pos().x()" and "event.pos().y()". The constants "QtCore.Qt.LeftButton" and "QtCore.Qt.RightButton" are used to determine which mouse button was pressed.

A reference to a widget can be made of the form "self.widgetName.underMouse()" which will return true or false as to whether the mouse cursor is over the widget "widgetName". Although presented in the same code excerpt the "underMouse()" handler is not tied to the "mousePressEvent" handler and can be used at any time.

Code Based Discussion - Main Routine

Most of the volume of code is in the GUI Class definition, there is not much in the main procedure.

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

Die Zeilen 2, 3 und 4 befassen sich mit der Koordinierung des Status der Benutzerinteraktion mit der GUI – z. B. „Abgebrochen“, „OK“ oder einem anderen von der Anwendung definierten Status. Die zuvor genannten Handler-Routinen „onCancel“ und „onOk“ legen ebenfalls diese Stati fest.

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

Lines 1 and 2 show the method for invoking the GUI. There may be multiple GUI definitions for a program and also the GUI need not be invoked as the first thing in the Python file, it may be invoked at any point. The Name of the GUI Class is specified in line 1 ("ExampleGuiClass" in this case) but the rest of the 2 lines are to be copied verbatim.

Lines 4 and 6 use the result field to determine the appropriate action. The last 4 lines simply show the copying of the data in the GUI object to variables local to the executing main procedure.

Complete Modal Code Example

This is the complete code example (developed on 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
#

The best way to use this code is to copy it into an editor or FreeCAD macro file and play around with it.

Code Based Discussion - Nonmodal Code Example

All of the widget specific from the previous modal example transfer to use in a nonmodal window. The main difference is that the nonmodal window does not restrict the user from interacting with other windows. Basically, a nonmodal window is one that can be opened and left open for as long as needed without it placing any restrictions on other application windows. There are a small number of code differences between the two which will be highlighted, consequently this code example is quite brief. Anything that is the same as the previous modal example will be left out in the interests of keeping this overview brief. This is the nonmodal GUI screen the PySide Class generates:

Import-Anweisung

Die zwingend erforderliche Import-Anweisung

from PySide import QtGui, QtCore

Diese wird am besten am Anfang der Python-Datei eingesetzt.

Klassendefinition

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

This code is best copied out verbatim and altered. The gist of the code is that we are sub-classing the QMainWindow Class of PySide. In adapting this code you will want to change the class name "ExampleNonmodalGuiClass" - make sure to change it in both locations (e.g. lines 1 & 4).

Erstellung von Fenstern

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

Obviously our window dimensions and title are different. The main point to note is the last line which lets PySide know that it is to send out mouse position events as they happen. Note that these events will not be sent out when the mouse is over a widget like a button as the widget will capture the events.

Mouse Move Event Handler

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

This handler receives the event of a Mouse Move and displays the formatted form of it. Test what happens when it is over widgets or outside of the window.

Invoking the Window

form = ExampleNonmodalGuiClass()

Invoking the window is another area of difference from the previous example. This time only 1 line is needed for invoking the GUI.

Complete Nonmodal Code Example

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

Misc Additional Topics

There are 3 concepts to the screen real estate in a GUI environment:

Within the software all are measured in pixels. PySide has function to measure in real world units but these are undependable as the manufacturers have no standard for pixel size or aspect ratio.

The Frame is the size of a window including it's side bars, top bar (possibly with a menu in it) and bottom bar. The Geometry is the space lying within the Frame and so is always less than or equal to the Frame. In turn the Frame is always less than or equal to the available screen size.

Verfügbare Bildschirmgrößen

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

Generally the "availableHeight" should be less than the "screenHeight" by the height of the menu bar. These 4 values are based on the hardware environment and will change from computer to computer. They are not dependent on any application window size.

(Seit Python 3.9 erscheint bei Ausführung des obigen Codes folgende Warnung: DeprecationWarning: QDesktopWidget.screenGeometry(int screen) const ist veraltet. Ab Python 3.10 scheint ein Ersatz erforderlich zu sein.)

Frame Size and Geometry

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

Dieselben Befehle können in einem vom Benutzer erstellten Fenster ausgeführt werden, die Syntax ändert sich dabei nicht.