Worum geht es hier?
Die Aufgaben im Praktikum sollen in der Programmiersprache Python bearbeitet werden. Diese Sprache wird in der Grundvorlesung "Einführung in die Programmierung" eingeführt. Dieses Kapitel dient dazu, einige nützliche Bibliotheken für Python kennenzulernen, die wir im Praktikum ausgiebig nutzen werden. Für Quereinsteiger gibt es auch nochmal eine kurze Zusammenfassung einiger wichtiger Konzepte sowie Links zu verschiedenen Python-Tutorials.
Selbstverständlich ist es auch möglich, die Aufgaben im Praktikum in einer anderen Sprache umzusetzen, z.B. mit JAVA oder C++. Hierfür können wir aber leider keinen Support (Ratschläge, Musterlösungen, Frameworks, etc.) garantieren.
Python und die Bibliotheken
Python ist eine interpretierte Hochsprache). Sie kombiniert Ideen dynamisch typisierter, objektorientierter Sprachen (insbesondere inspiriert vom Klassiker Smalltalk) mit einer leicht zu lesenden Syntax und vielen Features, die die Benutzung der Sprache und das Programmieren damit angenehmer machen. Besonders berühmt ist die Sprache für ihr reichhaltiges Ökosystem an Bibliotheken. Es gibt für fast alles eine Lösung, und die Kultur der Python-Community betont gute Dokumentation und leichte Handhabung. Im wissenschaftlichen Umfeld erfreuen sich die SciPy und die SciKit Bibliotheken besonderer Beliebtheit; zwei davon (NumPy, MatPlotLib) werden wir uns hier anschauen, viele andere (z.B. scikit-learn) laufen Ihnen im weiteren Studiums mit Sicherheit auch noch über den Weg.
In Python geschriebene Programme kann man sowohl in der Konsole ablaufen lassen, als auch mit interaktiven graphischen Benutzerschnittstellen ausstatten. Letzteres ist für unsere Veranstaltung wichtig, da wir uns graphische Objekte interaktiv anschauen wollen (daher müssen wir uns die dazu nötigen Bibliotheken näher anschauen; wir werden hier Qt für Python benutzen, welches sehr mächtig, ausgereift und weit verbreitet ist).
In diese Kapitel wollen wir uns insbesondere die Python-Bibliotheken anschauen, die für unsere Veranstaltung besonders wichtig sind. Dies umfasst vor allem die Bibliotheken Numpy (für effiziente Numerik), Matplotlib (zum Plotten von Graphen und zur Visualisierung von Daten) und, wie gesagt, die GUI Bibliothek Qt, hier in Form von PyQt5.
Alle diese Bibliotheken sind schon einzeln sehr umfangreich; unser kurzes Kapitel gibt nur einen kurzen Überblick mit dem Wichtigsten, um die Aufgaben bearbeiten zu können. Es gibt natürlich noch viel mehr Möglichkeiten - nehmen Sie ruhig die offiziellen Dokumentationen oder die verlinkten Tutorials / Quick-References zur Hand.
Anmerkung: Wir konzentrieren uns hier auf die aktuell neueste Version von Python (python3.6). Falls Sie bereits mit python2.7 vertraut sind (eine eigentlich überholte Version, welche aber von manchen Python Anwendern immer noch bevorzugt wird), können Sie auch diese Version verwenden (sprechen Sie dies aber kurz mit Ihrem Übungsleiter ab).
Los geht es mit Python. Zunächst: In dem Kasten unten gibt es einige Links und Quellen zum Lernen oder Auffrischen von Python Grundlagen. Für alle, die schon einmal eine objektorientierte Sprache benutzt haben, sollte es sehr einfach sein, Python zu lernen.
import numpy as np
np
ist hierbei nur ein Name, den Sie selbst wählen können. Nachdem Sie Numpy importiert haben, können Sie Numpy-Funktionen mit dem Prefix np
benutzen. Falls Sie as np
weglassen wird numpy
selbst als Name gewählt. Im Folgenden wird davon ausgegangen, dass Sie Python unter dem Namen np
importiert haben.np.sin
(die Sinus-Funktion in der Numpy-Umgebung) oder np.exp
häufig verwenden werden. In dem Fall können Sie auch eine Kurzform hierfür anlegen:from numpy import sin, exp
# nutzt eigentlich np.sin bzw. np.exp
print(sin(0) + exp(0))
from numpy import sin as sinus, exp
# nutzt eigentlich np.sin bzw. np.exp
print(sinus(0) + exp(0))
math
-Paket in Python eine sin
-Funktion. Auch wenn der Import ohne Suffix bequem ist, kann dies in größeren Programmen zu Problemen führen. Verwenden Sie daher diese Komfortfunktionen mit Bedacht; das kann sonst zu einer lästigen Fehlersuche führen. Bei nicht-trivialen Programmen ist es in der Regel sauberer, auf Umbenennung bzw. unqualifizierten Import (ohne Prefix) zu verzichten.
.py
-Datei schreiben:# erst nötige Pakete
import math
# Hilfsmethoden
def function1(a):
return a, a**2, a**3
# Main-Methode (wird direkt Ausgeführt)
test = 42
print(function1(test))
# erst nötige Pakete
import math
# Hilfsmethoden
def function1(a):
return a, a**2, a**3
# Main-Methode (wird nur Ausgeführt, wenn Script direkt ausgeführt wird)
if __name__ == "__main__":
test = 42
print(function1(test))
main
-Methode definieren und diese explizit aufrufen.# Erst die nötigen Pakete
import math
import sys
# Hilfsmethoden
def function1(a):
return a, a**2, a**3
# Der Name main ist dabei beliebig. (`argv} ist dabei die Liste aller
# Kommandozeilenargumente an das Skript; der Name ist auch beliebig).
# Der zusätzliche Parameter argv ist selbstverständlich optional -
# es können auch andere Parameter definiert werden.
def main(argv):
print(argv)
test = 42
print(function1(test))
# Main-Methode (wird nur Ausgeführt, wenn Script direkt ausgeführt wird)
if __name__ == "__main__":
main(sys.argv)
Für das erste Blatt benötigen wir jedoch schon Zufallszahlen, die sich sehr bequem mit Numpy abfragen lassen. Daher soll an dieser Stelle nur ein kleiner Teil dieses Abschnittes lesbar sein.
from math import sin
# normale Python-Liste
a = [1,2,3,5,10,20]
# a^2
aSqr = [ i**2 for i in a ]
# sin(a^3) + a^2
a2 = [ sin(i**3) + i**2 for i in a ]
Und nun der entsprechende Code ohne Python-for
-Schleifen mithilfe von Numpy:# normale Python-Liste
a = [1,2,3,5,10,20]
# konvertieren zu einem Numpy-Array
b = np.array(a)
# alternativ: b = np.array([1,2,3,5,10,20])
# b^2
bSqr = np.square(b)
# oder
bSqr = b**2
# b^3 + b^2
b2 = np.sin(b**3) + b**2
ADA
/C
/C++
/Pascal
/Rust
/Haskell
und zu einem gewissen Grade auch JAVA
/C#
, sowie viele andere, vermeiden dies durch statische Typisierung. Beim Übersetzen steht schon genau fest, welche Datentypen verwendet werden, wieviel Speicher diese belegen und meistens auch genau, welche Operationen wie durchgeführt werden sollen. Das ist sehr unflexibel, erlaubt aber weitgehende Optimierungen.np.array(...)
verwenden, versucht Numpy daraus zu schließen, welcher Datentyp gemeint ist. Die Datentypen, die in unserem Zusammenhang interessant sein werden sind: np.int64
, np.float64
, np.complex128
, np.bool
. Wichtig ist an dieser Stelle nur, dass Sie sich im Vorfeld bewusst machen wofür Sie die Variable benutzen werden.np.float32
(einfache Genauigkeit, 32bit) oder np.float64
(doppelte Genauigkeit, 64bit - Standard). Bei Fließkommazahlen muß man immer im Kopf haben, daß Ergebnisse von Fließkommaoperationen in der Regel nur approximativ sind und schnell zu numerischen Fehlern führen. Das heißt z.B., daß es fast nie Sinn macht, Fließkommazahlen auf Gleichheit (==
) zu prüfen; statt dessen sollte man prüfen, ob diese stärker voneinander abweichen (Differenz kleiner als \(\epsilon\), z.B. mit \(\epsilon=10^{-6}\)) (wie groß die Toleranz \(\epsilon\) sein kann, hängt vom Kontext ab; oft ist die wahl solcher Parameter gar nicht so einfach).
float
zu int
wird abgerundet, int/float
zu complex
wird nur reell, Zahl zu Bool
wird True
, falls ein Bit nicht 0
ist (also für alle Zahlen ungleich 0
).a = np.array([0,1,2])
a.dtype # enthält den typ np.int64
# typ ändern
a.astype(np.float64)
# typ kann sich bei bestimmten Rechnungen ändern
(a/2).dtype # = float64
(a//2).dtype # = int64 (gerundet)
a = np.array(range(8))**3
# Eingabe # Ausgabe
# ---------------------- # -----------------------------------------------
a # array([ 0, 1, 8, 27, 64, 125, 216, 343])
len(a) # 10
a[4] # 27
a[2:4] # array([ 8, 27]) (Beachte: rechts-exklusiv)
a[2:4] = [-10,2] #
a[5:10] = np.sin(a[4:9]) # es muss nur die Länge übereinstimmen
a # array([ 0, 1,-10, 2, 64, 7, 13, 22, 35, 52])
a[[0,5,2]] # array([ 0, 125, 8]) (Dies kopiert die Einträge des Arrays!!!)
cond = [False, True, True, False, False, True, True, False]
a[cond] # array([ 1, 8, 125, 216]) (Dies kopiert die Einträge des Arrays!!!)
bool
-Liste in die Listenklammern setzen.a = np.array(range(8))**3
# = array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729])
quersumme = a//100 + (a%100)//10 + a%10
# = array([ 0, 1, 8, 9, 10, 8, 9, 10, 8, 18])
# wir wollen die Zahlen aus a, die eine gerade quersumme haben
a[quersumme % 2 == 0]
# = array([ 0, 8, 64, 125, 343, 512, 729])
# sie können natürlich auch den gefilterten Teil wie oben manipulieren
a[quersumme % 2 == 0] = 0
in-place
auf dem ursprünglichen Objekt agieren! Betrachten Sie dieses Beispiel:a = np.array(range(8))**3
# = array([ 0, 1, 8, 27, 64, 125, 216, 343, 512, 729])
# wir manipulieren immer einen Teil von a!
b = a[0:4] # = array([0, 1, 8, 27])
b[0] = 100
b # = array([100, 1, 8, 27])
a # = array([100, 1, 8, 27, 64, 125, 216, 343, 512, 729])
# dies gilt auch für die X=-Operatoren
b *= 2
b += 2
# ...
# Dies ändert a jedoch nicht. Warum?
b = b*1 # alternativ: b = a[0:4]*1 oder b = a[0:4]+0
b[0] = 123
a = ... # wie vorher
b = np.array(a) # vollständige Kopie!
# falls b schon existiert und die selbe Größe hat wie a wird
np.copyto(a,b)
# schneller sein
# es funktioniert zwar wie oben gezeigt auch
b = 1*a
# Beachten Sie, dass arithmetische Operationen zur Änderung des internen
# Datentyps führen können. Für beliebige Datentypen von a sollten Sie also diese
# Option der Erweiterbarkeit Ihres Codes halber nicht verwenden!
a = ... # wie vorher
np.delete(a,3) # löscht das Element an der 3. Stelle (ist eine Array Kopie!)
a = np.array([[1,2],[3,4],[5,6]])
a
# = array([[1, 2],
# [3, 4],
# [5, 6]])
a.shape # gibt die Form von a aus
# = array([3, 2])
# Ändern der Form
a.reshape([2,3])
# = array([[1, 2, 3],
# [4, 5, 6]])
# Zusammenpressen zu einem Vektor
a.reshape([6])
# oder
a.reshape([-1])
# oder
b = a.reshape(-1)
# = array([[1, 2, 3, 4, 5, 6]])
# reshape kann man immer Rückgängig machen!
b.reshape([3,2]) # == a
# -1 in Reshape gibt an, welche Dimension "passend" gestreckt werden soll.
# beide Arrays sind nach der Operation gleich:
a.reshape([-1, 3])
a.reshape([2, -1])
# Rechenoperationen funktionieren Komponentenweise wie vorher auch:
b = a**5 + a**2
# ...
a[2,1] # = 6
a[:,0] # = array([1,3,5])
a[:,1] # = array([2,4,6])
a[0,:] # = array([1,2])
a[1,:] # = array([3,4])
a[2,:] # = array([5,6])
a[:,:] # wieder das ganze Array (jedoch keine Kopie!)
a = np.array([[1,2],[3,4],[5,6]])**3
# = array([[ 1, 8],
# [ 27, 64],
# [125, 216]])
quersumme = a//100 + (a%100)//10 + a%10
a[quersumme % 2 == 0]
# = array([ 8, 64, 125])
# Beachten Sie, dass diese Operation immer einen Vektor ergibt.
# Absolutbetrag
np.abs(a)
# Vorzeichen
np.sign(a) # -1 for a[i]<0, sonst 1 (0 für a[i]==0)
# Runden
np.round(x) # zum nächsten Integer
np.floor(x) # zum nächsten niedrigen Integer
np.ceil(x) # zum nächsten höheren Integer
# Trigonometrische Funktionen
np.sin(a)
np.cos(a)
# Exponentialfunktion & Logarithmus
np.exp(a)
np.log(a)
np.exp2(a) # Komponentenweises 2^a
# Summen
np.sum(a) # Gesamt-Summe (ergibt eine Zahl)
np.sum(a,axis=0) # Spalten-Summe (ergibt einen Vektor)
np.sum(a,axis=1) # Zeilen-Summe (ergibt einen Vektor)
# Produkte
np.prod(a,...) # dasselbe gilt auch für das Produkt
äußere Produkt
definiert sich als die zwei-dimensionale Liste/die Matrix aller Produkte des einen Arrays mit den Elementen des anderen Arrays.np.outer(v1,v2)
# z.B. für len(v1)==3 und len(v2)==3
# = array([[v1[0]*v2[0], v1[0]*v2[1], v1[0]*v2[2]],
# [v1[1]*v2[0], v1[1]*v2[1], v1[1]*v2[2]],
# [v1[2]*v2[0], v1[2]*v2[1], v1[2]*v2[2]]])
shape
) des Arrays auch zur Initialisierung verwenden:# mit Nullen gefüllt:
np.zeros([10,2]) # zweidimensionales Array mit zehn Zeilen und zwei Spalten
# mit Einsen gefüllt:
np.ones([10,2]) # zweidimensionales Array mit zehn Zeilen und zwei Spalten
# mit beliebiger anderer Zahl füllen
np.zeros([10,2]) + 1234
np.arange([start,] stop, [schritt]
a = np.arange(7) # a = array([0, 1, 2, 3, 4, 5, 6])
b = np.arange(2,8) # b = array([2, 3, 4, 5, 6, 7])
c = np.arange(2,10,3) # c = array([2, 5, 8])
# 1-D Gitter (gleichmäßig Abgetastet: von a bis b mit c Stützstellen (b enthalten)
a = np.linspace(a,b,c)
# 1-D Gitter (gleichmäßig Abgetastet: von a bis b mit c Stützstellen (b nicht enthalten)
a = np.linspace(a,b,c, endpoint=False)
# 1-D Gitter (gleichmäßig Abgetastet: von a bis b mit Abstand c
a = np.arange(a,b,c)
# 1-D Gitter (gleichmäßig Abgetastet: von a bis b mit Abstand 1
a = np.arange(a,b)
a,b # jeweils 1-D Gitter
# Matrix aller kombinatorischen Summen von Elementen aus a mit denen aus b
# (für nähere Erklärungen: suchen Sie nach Numpy-Broadcasting)
a.reshape([1,-1]) + b.reshape([-1,1])
# Matrix aller kombinatorischen Summen, wobei der zweite Teil Imaginär ist
# (entspricht 2-D Gitter auf der komplexen Zahlenebene)
a.reshape([1,-1]) + b.reshape([-1,1])*1j
# Zufallszahl zwischen 0 und 1
afloat = np.random.rand()
# ganze Zufallszahl zwischen 5 und 10 (nicht eingeschlossen!)
aint = np.random.randint(5,10)
# Anzahl zu erzeugender Zufallszahlen
num = 10
afloat = np.random.rand(num)
aint = np.random.randint(5,10, num)
datensatz = ... # mehrdimensionales Array (ein Datenpunkt pro Zeile)
# Zufallige Indices bestimmen
indices = np.random.permutation(len(datensatz))
vermischt = datensatz[indices]
,
) als Werte-Trenner (Delimiter), und den Zeilenumbruch (\n
) als Zeilentrenner. Zum Beispiel könnte eine CSV-Datei (mit dem Namen test.csv
) so aussehen.Name, Wert2, Wert3, Wert4
abc, 0.33, 0.5, 1234
abc, 0.04, 1.6, 461
abc, 0.1, 9.2, 873
...
.tsv
).# Einlesen der Datei mithilfe von Numpy
array2d = np.loadtxt("dateiname.txt", ...)
# für ... können wir die folgenden optionalen Parameter einsetzen
dtype = np.int64 # für Ganzzahlen oder etwa np.float64 für Fließkommazahlen
skiprows = 12 # Überspringe 12 Zeilen bevor der eigentliche Datensatz anfängt
delimiter = "," # (Standardmäßig auf alle Whitespace-Zeichen gestellt)
comments = "#" # Symbol, das angibt, wie Kommentare eingeleitet werden sollen
Matplotlib ist eine Python-spezifische (gibt es nur für Python) Bibliothek um schnell einfache Graphen und Plots von Daten und Funktionen anzufertigen.
Mit Matplotlib können sehr viele verschiedene Arten von Diagrammen erstellt werden - der Einstellbarkeit sind kaum Grenzen gesetzt - für unsere Anwendungen werden wir jedoch nur auf eine kleine Teilmenge der Funktionen zurückgreifen.
import numpy as np
import matplotlib.pyplot as plt
plot
. Mit ihr können wir schnell eine Menge von Punkte-Paaren darstellen lassen.# x- und y-Koordinaten
X = [0,1,5,10,3,6]
Y = [2,2,8,2,3,0]
# Zeichnen und Anzeige
plt.plot(X, Y)
plt.show()
plot
erwartet dabei einer Liste von x-Koordinaten, das zweite Argument die zugehörigen y-Koordinaten.
X = np.linspace(0,10, 250)
Y = np.cos(X)*np.cos(2*X)+np.sin(X)
# Zeichnen und Anzeige
plt.plot(X, Y)
plt.show()
t = np.linspace(0,100, 2500)
# Parametrische Kurve.
a = np.exp(np.cos(t))-2*np.cos(4*t)-np.sin(t/12)**5
X = np.sin(t)*a
Y = np.cos(t)*a
# Zeichnen und Anzeige
plt.plot(X, Y)
plt.show()
plot
erlaubt ein weiteres (optionales) Argument, das die Farbe und die Form der Punkte bestimmt....
plt.plot(X, Y, '.') # Punkte, nicht verbunden
plt.plot(X, Y, 'b.') # Punkte, nicht verbunden, blau
plt.plot(X, Y, 'ro') # große Punkte, nicht verbunden, rot
plt.plot(X, Y, 'k--') # gestrichelte Linie, schwarz
...
plot
können Sie mithilfe der Tabellen alle Kombinationen von Darstellungen und Farben entnehmen, die sie dem dritten Argument übergeben können. Weitere nützliche benannte Argumente sind auch unter dem angegebenen Link erklärt: linestyle
(Linientyp), linewidth
(Linienbreite), markerfacecolor
(Punktfarbe), marker
(Punktstil), markersize
(Punktgröße), color
(Farbe), alpha
(Transparenz).(rot,grün,blau)
bzw. 4er Tupel (rot,grün,blau,transparenz)
angeben:plt.plot(X, Y, color='#eceffa')
plt.plot(X, Y, color=(0.5,0.2,1)) # Werte jeweils zwischen 0 und 1
plt.plot(X, Y, color=(0.5,0.2,1,0.5)) # ... halb durchsichtig
...
xlim
und ylim
den Abschnitt des Koordinatensystems angeben....
plt.plot(x, y, 'o')
plt.xlim([-5,3]) # zeichne nur Punkte zwischen -5 und 3 (x-Achse)
plt.xlabel('x-Achse') # Achsen-Name
plt.ylim([0,40]) # zeichne nur Punkte zwischen 0 und 40 (y-Achse)
plt.ylabel('y-Achse') # Achsen-Name
plt.show()
axhline
und axvline
können Sie horizontale bzw. vertikale Linien Ihrem Plot hinzufügen.X = np.linspace(0,10, 250)
Y = np.cos(X)*np.cos(2*X)+np.sin(X)
plt.plot(X, Y, 'o')
plt.axhline(0.5, color="b", alpha=0.5)
plt.axvline(0.5, color="b", alpha=0.5)
plt.show()
x = np.linspace(0,2*np.pi, 250)
y = np.cos(x)
y2 = np.sin(np.cos(x)*x)
# Zeichnen und Anzeige
plt.plot(x, y, linewidth=3, label="das ist cos(x)")
plt.plot(x, y2, label="das ist sin(cos(x)*x)")
plt.plot(x, y2,"b.",markersize=1)
plt.legend()
plt.show()
x = ...
y = ...
y2 = ...
# erste Achse
fig, ax1 = plt.subplots()
# statt plt.plot nun ax1.plot
ax1.plot(x, y, 'b-')
# aus xlabel und xlim wird set_xlabel und set_xlim
ax1.set_xlabel('Punkte')
# Färben der y-Achse
ax1.tick_params('y', colors='b')
# zweite y-Achse (mit der gleichen x-Achse)
ax2 = ax1.twinx()
ax2.plot(x, y2, 'r.')
subplots
erweitert dabei drei Ziffern; die erste gibt die Anzahl der Reihen an, die zweite die Anzahl der Spalten, und die letzte den Index des aktuellen Subplots im so definierten Gitter.x = np.linspace(0,2*np.pi, 250)
y = np.cos(x)
y2 = np.sin(np.cos(x)*x)
# Zeichnen und Anzeige
fig = plt.figure()
ax_obenlinks = fig.add_subplot(321)
ax_mitterechts = fig.add_subplot(324)
ax_obenlinks.plot(x, y)
ax_obenlinks.plot(x, x)
ax_mitterechts.plot(x, y2)
plt.show()
import matplotlib.pyplot as plt
from PIL import Image
# Bild einlesen (bild.png ist im selben verzeichnis, wie diese Datei enthalten)
# convert("L") übersetzt das bild in ein Graustufenbild (nur ein Kanal, statt dreien)
image = Image.open("bild.png").convert("L")
# konvertieren in ein numpy-array
img = np.asarray(image)
# Anzeigen (in Graustufen)
fig, ax = plt.subplots()
ax.imshow(img, cmap='gray')
plt.show()
from numpy import sin, cos
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig, ax = plt.subplots()
ax.set_xlim([-5,5])
ax.set_ylim([-5,5])
# folgende objekte sollen wie gezeichnet werden
curve, = ax.plot([],[],"-", color="blue")
origin = ax.plot(0,0,"x", color="red")
pos, = ax.plot([],[],"o", color="black")
text = ax.text(0.1, 0.2, '', transform=ax.transAxes)
# wir merken uns alle positionen
curve_pos = [[],[]]
# berechnnung der neuen position
t = np.linspace(0,100, 5000)
a = np.exp(np.cos(t))-2*np.cos(4*t)-np.sin(t/12)**5
curve_pos = [np.sin(t)*a, np.cos(t)*a] #x- und zugehörige y-Werte
# initialisierung der animation (startbild)
def init():
curve.set_data(curve_pos[0], curve_pos[1])
pos.set_data([],[])
text.set_text('')
return pos,text
# animationsschritt
def step(i):
pos.set_data([curve_pos[0][i]],[curve_pos[1][i]])
text.set_text("Schritt "+str(i))
return pos,text
# interval gibt an, wie lange gewartet wird in ms
# blit=True (nicht alles wird sets neu gezeichnet)
# drittes argument gib an wie sich der index i in step(i) verhält
ani = animation.FuncAnimation(fig, step, np.arange(1, len(t)),
interval=25, blit=True, init_func=init)
# als film speichern (mit 15 fps)
# ani.save('butterfly.mp4', fps=15)
# anzeigen
plt.show()
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
# from PyQt5 import QtCore as qc
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
import matplotlib.pyplot as plt
import numpy as np
class PlotWindow(qw.QDialog):
def __init__(self, parent=None):
super(PlotWindow, self).__init__(parent)
# das Diagramm auf dem wir zeichnen
self.figure, self.axis = plt.subplots()
# FigueCanvas ist ein qt-Widget, das das Diagramm anzeigen kann
self.canvas = FigureCanvas(self.figure)
# die Matplotlib-NavigationsLeiste
self.toolbar = NavigationToolbar(self.canvas, self)
# Layout (wie Sie es bereits kennen)
self.button = qw.QPushButton('Plot')
self.button.clicked.connect(self.plot)
layout = qw.QVBoxLayout()
layout.addWidget(self.toolbar)
layout.addWidget(self.canvas)
layout.addWidget(self.button)
self.setLayout(layout)
# Die Plot-Funktion kann nun wie vorher definiert werden:
def plot(self):
x = np.linspace(0,10, 250)
y = np.cos(x)*np.cos(2*x)+np.sin(x)
# Zeichnen und Anzeige
self.axis.plot(x, y)
# Achtung: keine plt.show!
# (Neu-)Zeichnen des Canva
self.canvas.draw()
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
main = PlotWindow()
main.show()
sys.exit(app.exec_())
Qt ist ein Toolkit, das dabei hilft, Programme (und insbesondere Programmoberflächen) plattformübergreifend zu entwickeln. Bei der richtigen Verwendung von Qt kann Ihr Programm nicht nur auf Windows und gängigen POSIX-Systemen, sondern mittlerweile auch auf Android, iOS, Windows Phone und eingebetteten Systemen ausgeführt werden.
In dieser Übersicht soll ein kleiner Teil von Qt (in seiner aktuellen Version Qt5.8) vorgestellt werden, der für die Lösung der Aufgaben ausreichen sollte.
Anmerkung: Qt ist eigentlich ein C\+\+-Framework. Hier verwenden wir zwar die Verbindung zu Python (PyQt), Dokumentationen existieren leider vorwiegend für C++. Die Verwendung ist aber bis auf die Sprach-Syntax konsistent. Falls Sie also die Funktionalität einer Klasse genauer inspizieren möchten, können Sie die offizielle Qt-Dokumentation zur Hand nehmen.
QGui
gespeichert sind, finden Sie die Widgets in PyQt5 in QWidgets
.QtCore
enthält alle nicht-GUI-Klassen, die auch von den anderen PyQt-Modulen verwendet werden.QtWidgets
enthält GUI-Komponenten, um klassische UI-Fenster zu strukturieren. (Buttons, Fenster, Textfelder, etc.)QtGui
enthält auch GUI-Komponenten, jedoch auf einer etwas niedriger liegenden Ebene: Bilder, 2D-Graphik, Schrift, Event-Handlingimport sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
# Verwendung später:
qw.QKlasseAusQtWidgets(...)
qg.QKlasseAusQtGui(...)
qw.QKlasseAusQtCore(...)
Alternativ können Sie perimport sys
from PyQt5.QtWidgets import QKlasseAusQtWidgets
from PyQt5.QtGui import QKlasseAusQtGui
from PyQt5.QtCore import QKlasseAusQtCore
# Verwendung später:
QKlasseAusQtWidgets(...)
QKlasseAusQtGui(...)
QKlasseAusQtCore(...)
qw.
, qg.
und qc.
).QApplication
-Objekt erstellen. Dieses kümmert sich darum, dass Kommandozeilenargumente an die GUI Ihres Programms richtig übergeben werden, Events/Interaktiv bearbeitet werden, Ihr Programm richtig initialisiert wird, uvm.; strukturieren Sie also Ihr PyQt-Programm mit# Initialisierung des Programms und Ihrer GUI
app = qw.QApplication(sys.argv)
# ...
# Ihr Programmcode
# ...
# Starten der Ereignisschleife (engl. event loop; Abfangen von Klicken,
# Tippen, etc.) und beenden des gesamten Programms beim Beenden des Qt-Teils
# (dies geschieht standardmäßig beim Schließen aller Fenster).
# Diese beiden Schritte zu trennen ist innerhalb dieses Praktikums nicht nötig.
sys.exit(app.exec_())
QApplication
zu initialisieren und am Ende auszuführen (app.exec_()
). Falls Sie es vergessen, werden Sie unter Umständen ein schwarzes Fenster sehen, auf jeden Fall aber sind dann Ihre Interaktionsmöglichkeiten mit der Anwendung beschränkt.import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
w = qw.QWidget()
b = qw.QLabel(w)
b.setText("Hallo Welt!")
w.setGeometry(120,120,200,50)
b.move(100,200)
w.setWindowTitle("Tadaa, ein Fenster")
w.show()
sys.exit(app.exec_())
QWidget()
ein leeres Fenster.import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
fenster1 = qw.QWidget()
fenster2 = qw.QWidget()
fenster3 = qw.QWidget()
% w.setGeometry(120,120,200,50)
# Fensternamen
fenster1.setWindowTitle("Das ist Fenster 1") # angezeigter Fenstername
fenster2.setWindowTitle("Das ist Fenster 2 (maximiert)")
fenster3.setWindowTitle("Das ist Fenster 3 (minimiert)")
# typische Fenstermanipulation
fenster1.resize(300, 500) # Größe festlegen
fenster1.move(10,10) # auf dem Bildschirm verschieben
# Anzeigen der Fenster
fenster1.show()
fenster2.showMaximized()
fenster2.showMinimized()
# Zentrieren auf dem Bildschirm
fg = fenster4.frameGeometry()
centrum = QDesktopWidget().availableGeometry().center()
fg.moveCenter(center)
fenster4.move(fg.topLeft())
QWidget
-Klasse (oder auch von der QMainWindow
-Klasse - siehe später) ableiten, um eigene Widgets zu erstellen.import sys
from PyQt5 import QtWidgets as qw
class MyGUIProgram(qw.QWidget):
# statische (optionale) Methode zum Starten
# des GUI-Programms
def spawn():
app = qw.QApplication(sys.argv)
MyGUIProgram()
sys.exit(app.exec_())
# Konstruktor
def __init__(self):
# Konstruktor von QWidget
super().__init__()
# gute Idee: Lagern Sie Code aus, um Ihren Code
# übersichtlich zu halten
self.setWindowTitle('SnakeGame')
self.init_mainWindow() #ausgelagerte GUI-Erstellung
self.show()
if __name__ == "__main__":
MyGUIProgram.spawn()
QApplication
zu initialisieren und am Ende auszuführen (app.exec_()
). Falls Sie es vergessen, werden Sie unter Umständen nur ein schwarzes Fenster sehen.b = qw.QPushButton("drück mich!")
b.resize(150,200)
b.show()
btn = qw.QPushButton("drück mich!")
btn.setDefault() # Standard-Button
btn.setEnabled(False) # ausgegraut
btn.clicked.connect(fn) # fn wird bei Klick ausgeführt
# (es sind "echte" Funktionen oder lambda-Ausdrücke möglich, z.B.)
btn.clicked.connect(lambda: print("hey"))
def clicked():
print("ho")
btn.clicked.connect(clicked)
btn.pressed.connect(fn) # sobald Maus gedrückt wird
btn.released.connect(fn)# sobald Maus losgelassen wird
lbl = qw.QLabel("Ha! Du kannst mich nicht ändern!")
lbl2 = qw.QLabel()
lbl2.setText("nachträglich verändert")
lbl2.setAlignment(qc.Qt.AlignRight) # Ausrichtung
le = qw.QLineEdit()
le2 = qw.QLineEdit("Initialtext")
# Einstellungen
le.setMaxLength(10) # Maximallänge
le.setAlignment(qc.Qt.AlignRight) # Ausrichtung
le.setFont(qg.QFont("Arial", 20)) # Schriftart (Arial, Größe 20)
# direkte Validierung
le2.setValidator(QIntValidator()) # nur Ganzzahlen
le2.setValidator(QDoubleValidator()) # nur Fließkommazahlen
le2.setValidator(QDoubleValidator(0.99, 99.99, 2)) # (festes Format)
# signals (wie bei QPushButton)
le2.cursorPositionChanged.connect(fn) # Parameter: (int alt, int neu)
le2.editingFinished.connect(fn)
le2.selectionChanged.connect(fn)
le2.textChanged.connect(fn) # parameter: QString
le2.textEdited.connect(fn) # parameter: QString
cb = qw.QCheckBox("Possibility 1")
cb.setChecked(True) # Standardmäßig an oder aus?
cb.setText("abc")
cb.text() # getter (hier: "abc")
cb.isChecked()
# Funktionsaufruf bei Statusänderung
cb.toggled.connect(fn)
parent
. Durch Setzen dessen auf ein anderes Widget, wird das neu erstellte als Teil des Widgets parent
dargestellt.# Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
win = qw.QWidget()
win.setGeometry(10, 30, 300, 200) # Position: (10,30) auf dem Bildschirm
# Größe: (300,200)
btn = qw.QPushButton("Jim",win)
btn.move(20,20) # Position (20,20) in parent=win
btn2 = qw.QPushButton("Jim2",parent=win)
btn2.move(120,120) # Position (120,120) in parent=win
# Anzeigen und Starten
win.show()
app.exec_()
win.setLayout(layout)
zugewiesen werden. # Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
win = qw.QWidget()
btn1 = qw.QPushButton("Jim")
btn2 = qw.QPushButton("Benjamin")
lbl = qw.QLabel("Entscheide dich:")
btn3 = qw.QPushButton("blau")
btn4 = qw.QPushButton("rot")
vbox = qw.QVBoxLayout()
vbox.addWidget(btn1) # erst btn1
vbox.addWidget(btn2) # darunter btn2
vbox.addStretch() # skalierter leerer Platz
vbox.addWidget(lbl) # darunter ein Label
hbox = qw.QHBoxLayout()
vbox.addLayout(hbox) # darunter ein neues Layout (als Widget)
hbox.addWidget(btn3) # links: btn3
hbox.addStretch() # Mitte: gestreckte leere Fläche
hbox.addWidget(btn4) # rechts: btn4
win.setLayout(vbox)
win.show()
app.exec_()
# Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
win = qw.QWidget()
form = qw.QFormLayout()
form.addRow(qw.QLabel("test"), qw.QLineEdit())
form.addRow(qw.QLabel("name"), qw.QPushButton())
hbox = qw.QHBoxLayout()
hbox.addWidget(qw.QCheckBox("check1"))
hbox.addWidget(qw.QCheckBox("check2"))
hbox.addWidget(qw.QCheckBox("check3"))
form.addRow(qw.QLabel("check"), hbox)
win.setLayout(form) # Wichtig! Erst hier wird das Layout an win angefügt
win.show()
app.exec_()
# Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
win = qw.QWidget()
grid = qw.QGridLayout()
# einzelne Zellen
for i in range(1,4):
for j in range(1,3):
grid.addWidget(qw.QPushBu("Knopf"+str(i)+"/"+str(j)), i,j) # Position (i,j)
# mehrere Zellen
grid.addWidget(qw.QPushBut("Knopf Quad"), 1,3, 2, 2) # Position (1,3), Größe (2,2)
grid.addWidget(qw.QPushBut("Knopf Lang"), 1,5, 4, 1)
grid.addWidget(qw.QPushBu("Knopf Breit"), 4,1, 1, 2)
# Layouts selbst können auch hinzugefügt werden
grid.addLayout(...)
win.setLayout(grid)
win.show()
app.exec_()
# Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
win = qw.QWidget()
form = qw.QFormLayout()
testlbl = qw.QLabel("test")
testlbl.setToolTip("Informationen zum Label Test")
le = qw.QLineEdit()
# Der Text kann dabei auch formattiert werden
le.setToolTip("<u>Hier</u> muss <b>eine Eingabe</b> vorgenommen werden.")
form.addRow(testlbl, le)
win.setLayout(form) # Wichtig! Erst hier wird das Layout an win angefügt
win.show()
app.exec_()
QFileDialog
) oder Sie müssen dem Benutzer etwas mitteilen. Durch einen Dialog kann dies erreicht werden. Die folgenden Beispiele sollten bei der Erstellung Ihrer Benutzeroberflächen als Anreiz reichen. (Eine weitere, hier nicht aufgeführte, Dialogklasse ist QMessageBox
)def dialog():
d = qw.QDialog()
d.setWindowTitle("Dialogtitel")
# kann der Dialog umgangen werden um andere Fenster zu bedienen?
d.setWindowModality(qc.Qt.ApplicationModal) # nein (Standard)
# d.setWindowModality(qc.Qt.WindowModal) # ja
# ... wie QWidget verwendbar
btnok = qw.QPushButton("Ok, lass mich in ruhe.",d)
btnno = qw.QPushButton("Nein.",d)
hbox = qw.QHBoxLayout()
hbox.addWidget(btnok)
hbox.addWidget(btnno)
d.setLayout(hbox)
# akzeptieren / ablehnen
btnok.clicked.connect(lambda: d.accept())
btnno.clicked.connect(lambda: d.reject())
# bei Fehler / Erfolg an Hauptfenster melden
d.rejected.connect(lambda: b.setText("Oh, wie schade!"))
d.accepted.connect(lambda: b.setText("Erfolg!"))
# Extras
btnok.setDefault(True) # btn wird bei Enter gedrückt
d.exec_() # Fenster ausführen
w = qw.QWidget()
b = qw.QPushButton("Die Entscheidung",w)
b.clicked.connect(dialog)
w.show()
def fdialog():
d = qw.QFileDialog()
d.setWindowTitle("Dialogtitel")
# bei Auswahl einer Datei
d.fileSelected.connect(lambda file: lbl.setText(file))
d.exec_() # Fenster ausführen
w = qw.QWidget()
hbox = qw.QHBoxLayout()
lbl = qw.QLineEdit("standarddateiname.txt")
hbox.addWidget(lbl)
b = qw.QPushButton("Dateiauswahl")
hbox.addWidget(b)
b.clicked.connect(fdialog)
w.setLayout(hbox)
w.show()
timer = QTimer()
timer.setInterval(300) # Timer-Reset alle 300ms
timer.timeout.connect(fn) # fn sei die Funktion, die aufgerufen werden soll
timer.start(ms) # ms: Anzahl der Millisekunden, nach denen der Timer die
# Ausführung von fn erstmals startet. (Standardmäßig ms=0)
timer.stop() # Timer anhalten
timer.setSingleShot(True) # einmalige Ausführung von fn
timer.isActive()
QPixmap
und QImage
.off-screen
Bild, auf dem mithilfe der Klasse QPainter
gemalt werden kann. QPainter
kann dabei alles, was ein Zeichenprogramm wie Paint auch kann: Quadrate, Kreise, Text zeichnen und dabei eine Auswahl an Pinseln verwenden.QPixmap
/QPainter
anzusehen.QLabel
diese Möglichkeit nach, indem man einem diesem ein QPixmap als anzuzeigendes Objekt übergibt. Gehen Sie davon aus, dass die Konvertierung von QImage
zu QPixmap
schnell von Statten geht. Erinnern Sie sich daran, dass ein Pixmap fest ist und daher beim Neuzeichnen erneut erstellt werden muss. Im folgenden Beispiel gehen wir davon aus, dass sich ein Bild mit dem Namen bild.png
in demselben Ordner befindet, in dem auch das Skript gestartet wird.import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
# in QLabel wird das Bild angezeigt
display = qw.QLabel()
# Laden der Datei in eine Pixmap (nicht direkt bearbeitbar)
bild = qg.QPixmap("bild.png")
display.setPixmap(bild)
# Alternativ: QImage (muss zu QPixmap konvertiert werden)
bild = qg.QImage("bild.png")
display.setPixmap(qg.QPixmap.fromImage(bild))
# Fenster anzeigen
display.show()
app.exec_()
QLabel
) festzumachen, aber auch die Größe direkt festzulegen.# Möglichkeit 1
display = qw.QLabel()
display.resize(200,300)
pixmap = ... # wie oben qg.QPixmap(...) oder qg.QPixmap.fromImage(...)
scaledpixmap = pixmap.scaled(display.size(), Qt.KeepAspectRatio)
display.setPixmap(scaledpixmap)
# Möglichkeit 2
display = qw.QLabel()
pixmap = ... # wie oben qg.QPixmap(...) oder qg.QPixmap.fromImage(...)
scaledpixmap = pixmap.scaled(200,300, Qt.KeepAspectRatio)
display.setPixmap(scaledpixmap)
# wir erstellen zuerst das Bild
breite, hoehe = 400, 300
# QImage-Formate: http://doc.qt.io/qt-5/qimage.html#Format-enum
img = qg.QImage(breite, hoehe, qg.QImage.Format_RGBA8888)
img.fill(qc.Qt.white) # Bild mit Standardwerten füllen (Wichtig!)
# das Format der Pixelfarbe lautet 0xRRGGBBAA, wobei
# RR, GG, BB jeweils die Hex-Darstellung der Farbwerte für
# Rot, Grün und Blau zwischen 0 und 255 ist (d.h. 195 entspräche C3).
# 0 bedeutet, dass die Farbe nicht vorhanden ist,
# 255 ist der Maximalwert jeder Farbe.
# AA beschreibt die Transparenz (0 für Transparent, 255 für Sichtbar)
img.setPixel(10,20, 0x00ff00ff) # Grün
# das Setzen von Hex-Werten bedarf einiger Übung und Gewöhnung.
# Glücklicherweise ist es auch möglich, die Farbwerte im 10er-System
# anzugeben (Werte jeweils zwischen 0 und 255).
img.setPixelColor(20,40,qg.QColor(20,30,155))
# Anzeigen in QLabel
display.setPixmap(qg.QPixmap.fromImage(img))
# neue Manipulation ist erst beim Neu-Zeichnen sichtbar!
img.setPixelColor(80,80,qg.QColor(20,30,155))
# erneut Zeichnen
display.setPixmap(qg.QPixmap.fromImage(img))
breite, hoehe = 400, 300
npbild = np.zeros([hoehe,breite,4], dtype=np.uint8) # RGBA (A: Transparenz)
npbild += 255 # nun ist das Bild weiß
npbild[20,30,:] = [135,23,53,255] # setzen eines Farbpixels im Numpy-Array
npbild[20,30,0] = 255 # setzen des Rotwertes eines anderen Pixels
img = QImage(npbild, breite, hoehe, QImage.Format_RGBA8888)
# wir erstellen zuerst das Bild
breite, hoehe = 400, 300
img = qg.QImage(breite, hoehe, qg.QImage.Format_RGBA8888)
img.fill(qc.Qt.white) # Bild mit Standardwerten füllen (Wichtig!)
# Ändern von Pixelinformationen
img.setPixel(10,20, 0x00ff00ff) # Grün
img.save("neues_bild.png")
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
app = QApplication(sys.argv)
# Qlabel zum Anzeigen des Bildes
display = QLabel()
display.setGeometry(QRect(0,0,400, 600))
# unsere Hauptzeichenfläche
canvas = QImage(400,600, QImage.Format_RGBA8888)
painter = QPainter(canvas)
# Linien
linienbreite = 2
painter.setPen(QPen(Qt.blue, linienbreite, Qt.DotLine, Qt.SquareCap, Qt.BevelJoin))
painter.drawLine(50,50,50,100)
painter.setPen(QPen(QColor(100,200,300,50), 5*linienbreite, Qt.SolidLine, Qt.RoundCap, Qt.BevelJoin))
painter.drawLine(70,50,70,100)
# Rechtecke (beachten Sie die Verwendung von Stiften (Pen) und Pinseln (Brush)
painter.fillRect(100,50,150,200,Qt.white)
painter.setPen(QPen(Qt.red, linienbreite, Qt.DashLine, Qt.SquareCap, Qt.BevelJoin))
painter.drawRect(120,70,170,220)
painter.setPen(Qt.NoPen)
painter.setBrush(Qt.green)
painter.drawRect(220,170,270,320)
# Ellipsen
painter.setBrush(Qt.NoBrush)
painter.setPen(QPen(QColor(0,150,150,100), 20))
painter.drawEllipse(QPoint(230,180),30,50)
# Text
painter.setPen(Qt.black)
groesse = 20
painter.setFont(QFont("Helvetica", groesse));
painter.drawText(100,300, "Hallo Welt!")
display.setPixmap(QPixmap.fromImage(canvas))
display.show()
sys.exit(app.exec_())
# Qlabel zum Anzeigen des Bildes
display = QLabel()
display.setGeometry(QRect(0,0,width*zoom, height*zoom))
# unsere Hauptzeichenfläche
canvas = QPixmap(1000,500) # oder QImage
painter = QPainter(canvas)
# Hintergrund Ebene
ebene1 = QPixmap(1000,500) # oder QImage
painter1 = QPainter(ebene1)
# ... langsamer code, komplexes Zeichnen der Ebene
# Objekt auf eigener Ebene
ebene2 = QPixmap(200,300) # oder QImage
painter2 = QPainter(canvas)
# langsamer code, komplexes Zeichnen der Ebene
# ... in der redraw-Schleife
# weiß färben
painter.fillRect(0,0,1000,500,Qt.white)
# Hintergrund hinzufügen, (0,0) ist die Startposition
painter.drawPixmap(0,0,ebene1) # bei QImage: .drawImage(...)
# Objekt hinzufügen
painter.drawPixmap(234,345,ebene2) # bei QImage: .drawImage(...)
# skalieren auf die Größe der Zeichenfläche
# für Canvas vom Typ: QPixmap
display.setPixmap(canvas)
# für canvas vom Typ: QImage
display.setPixmap(QPixmap.fromImage(canvas))
QMainWindow
gibt bereits eine GUI-Struktur vor, die bei der GUI-Erstellung häufig verwendet wird: Statusleiste, Menü, anheftbare Widgets (hier nicht behandelt).# Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
main = qw.QMainWindow()
main.resize(500,600)
main.setWindowTitle("Hauptfenster")
# Zentrale Teil kann wie Üblich gefüllt werden
win = qw.QWidget()
main.setCentralWidget(win)
hbox = qw.QHBoxLayout()
win.setLayout(hbox)
hbox.addWidget(qw.QPushButton("links"))
hbox.addWidget(qw.QPushButton("Mitte"))
hbox.addWidget(qw.QPushButton("rechts"))
# MainWindow hat auch eine Statusleiste
# (diese kann selbst auch Widgets anzeigen)
stat = main.statusBar()
# stat.addWidget(qw.QLabel("Statustext")) # linksbündig, wird evtl. von showMessage überblendet
stat.addPermanentWidget(qw.QLabel("Statustext2")) # rechtsbündig
stat.showMessage("wird 2s lang eingeblendet", 2000)
# stat.showMessage("wird angezeigt, bis gelöscht wird")
# stat.clearMessage() # löschen angezeigter Nachrichten
# ... und auch eine Menüleiste
menu = main.menuBar()
menu.setNativeMenuBar(False) # optional: OS X stellt das Menü anders dar
m1 = menu.addMenu("Datei")
m1.addAction("Neu")
# Menüeintrag mit Tastenkürzel
m1.addAction("Öffnen")
m1sub = m1.addMenu("Untermenü")
m1sub.addAction("Versteckt")
m1.addSeparator()
close = qw.QAction("Beenden", main)
close.setShortcut("Ctrl+Q")
close.setStatusTip("Schließt das Programm")
close.triggered.connect(lambda: main.close())
m1.addAction(close)
m2 = menu.addMenu("Bearbeiten")
for i in range(3):
for j in range(4):
m2.addSeparator()
m2.addAction("Platzhalter")
menu.addSeparator()
def opendialog(clickable):
dialog = qw.QDialog()
dialog.resize(200,150)
qw.QLabel("Programmversion 3.1415",dialog)
dialog.exec_()
m3 = menu.addMenu("Info")
info = m3.addAction("Info")
info.triggered.connect(opendialog)
main.show()
app.exec_()
keyPressEvent
), die vorgehensweise beim Event fürs Loslassen der Taste (keyReleaseEvent
) ist dabei ganz analog.connect
die aufzurufende Funktion an das Objekt gebunden haben, können Sie bei den hier aufgeführten Events die entsprechende Funktion überschreiben. In dem folgenden Besipiel wird jedes mal, wenn Sie eine beliebige Taste drücken, dies in der Statusleiste kenntlich gemacht. Im Spezialfall der Escape-Taste wird das Fenster geschlossen.# Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
app = qw.QApplication(sys.argv)
# Einfaches Layout (nur eine leere Fläche)
win = qw.QMainWindow()
win.setGeometry(10, 30, 300, 200) # Position: (10,30) auf dem Bildschirm
# Größe: (300,200)
# Tastendruck in QMainWindow abfangen
def fn(e):
win.statusBar().showMessage("Taste mit key-code "+str(e.key())+" gedrückt", 1000)
if e.key() == qc.Qt.Key_Escape:
win.close()
win.keyPressEvent = fn
# Anzeigen und Starten
win.show()
app.exec_()
QWidget
geerbte Funktion definieren.# Initialisierung wie üblich
import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
class TastenTest(qw.QWidget):
# einfaches Layout
def __init__(self):
super().__init__()
self.setGeometry(10, 30, 300, 200)
self.show()
# Überladen der leeren Standardfunktion
def keyPressEvent(self, e):
self.statusBar().showMessage("Taste mit key-code "+str(e.key())+" gedrückt", 1000)
if e.key() == qc.Qt.Key_Escape:
win.close()
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
ex = TastenTest()
sys.exit(app.exec_())
qc.Qt.Key_Escape
qc.Qt.Key_Return
qc.Qt.Key_Print
(Drucken-Taste)qc.Qt.Key_Left
, qc.Qt.Key_Right
, qc.Qt.Key_Up
, qc.Qt.Key_Down
(Pfeiltasten)qc.Qt.Key_F1
, qc.Qt.Key_F2
, qc.Qt.Key_F...
, qc.Qt.Key_F35
(Funktionstasten)qc.Qt.Key_0
, qc.Qt.Key_1
, qc.Qt.Key_...
, qc.Qt.Key_9
(Zifferntasten)qc.Qt.Key_A
, qc.Qt.Key_B
, qc.Qt.Key_...
, qc.Qt.Key_Z
e
) vorliegt.import sys
from PyQt5 import QtWidgets as qw
from PyQt5 import QtGui as qg
from PyQt5 import QtCore as qc
class MausTest(qw.QMainWindow):
# einfaches Layout
def __init__(self):
super().__init__()
self.setGeometry(10, 30, 300, 200)
self.show()
# Mouse-Events werden sonst nur aufgerufen, wenn eine Maustaste gedrückt wird.
self.setMouseTracking(True)
# Überladen der leeren Standardfunktion
def mouseMoveEvent(self, e):
self.statusBar().showMessage("Maus-Position ("+str(e.x())+","+str(e.y())+")", 1000)
if e.button() == qc.Qt.LeftButton:
self.statusBar().showMessage("Maus-Position ("+str(e.x())+","+str(e.y())+") + Linke Maustaste", 1000)
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
ex = MausTest()
sys.exit(app.exec_())
resize Event
, leaveEvent
, ...). Entnehmen Sie die genauere Funktionsweise bitte der Dokumentation - im Moment sind diese für die Bearbeitung der Übungsaufgaben nicht nötig.