GUI - PROGRAMMIERUNG



 

Professionelle Programme verfügen meist über eine grafische Benutzeroberfläche (GUI) mit Textfeldern, Schaltflächen (Buttons) und Menüleisten. Da der Grafik-Support für TigerJython auf der Swing-Library von Java basiert und für Python2.7 (Geany, PyCharm, etc.) die Grafik-Bibliothek von PyQt4 verwendet wird, ist der Source-Code unterschiedlich. Im Folgenden finden Sie einige Beispiele mit ähnlicher Aufgabenstellung in beiden Versionen.

Das Konzept des Grafikfensters von GPanel mit dem automatischen Rendern hat sich in vielen Zusammenhängen, insbesondere in der Informatik-Grundausbildung, bewährt. Es ist daher naheliegend, eine Version davon als GUI-Komponente (Widget) zu definieren, die wie jede andere GUI-Komponente in ein Grafik-Fenster eingebaut werden kann. Dies ist in TigerJython wie in Python2.7/PyQt4 gleichermassen möglich.

 

 

Buttons mit PyQt4

 

Mit den Buttons Red, Green und Black kann die Linienfarbe gewählt werden. Mit Klick auf den Button Go wird die Grafik gezeichnet. Zudem ist zu Demonstrationszwecken auch eine Menüleiste mit der einzigen Option File vorhanden.

Grundsätzliches zum Programmaufbau:
Mit PyQt4 fasst man das Grafikfenster als Instanz einer eigenen Klasse MyDialog auf, die man von QDialog ableitet. Im Konstruktor wird wie üblich zuerst die Basisklasse initialisiert. Damit die Grafik wie in einem GPanel animiert aufgebaut werden kann (jede Grafik-Operation wird automatisch gerendert), erzeugt man eine Instanz p = gpanel.GPane() aus dem Modul gpanel.py. Über p stehen alle Grafikoperation von GPanel zur Verfügung.

 

Die Widgets werden als Instanzen der betreffenden GUI-Klassen von PyQt4 erzeugt. Hier sind es Buttons aus der Klasse QPushButton.

Wie üblich werden die Widget mit einem Layout-Manager im Dialog dimensioniert und positioniert. (auch das GPane ist ein Widget).

Wir verwenden dazu einen QVBoxLayout für den ganzen Dialog und einen QHBoxLayout für die horizontal angeordneten Buttons und fügen die Widgets mit addWidget() zum Layout.
Die Registrierung der Callbacks für die Buttonklicks werden mit der connect()-Methode von QDialog vorgenommen.

Um Notifikationen an den Benutzer  zu ermöglichen,  ist eine Statusbar eingebaut
Die Initialisierung des GUI wird in der Methode initUI() der  Klasse MyWindow vorgenommen, die aus QMainWindow abgeleitet ist.

Programm: [►]

#GuiEx1.py

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import gpanel

# -------------- Class MyDialog -----------------------
class MyDialog(QDialog):

    def __init__(self):
        super(MyDialog, self).__init__()

        # Create widgets
        self.p = gpanel.GPane()
        redBtn = QPushButton("Red")
        greenBtn = QPushButton("Green")
        blackBtn = QPushButton("Black")
        goBtn = QPushButton("Go")

        # Layout of whole dialog
        dlgLayout = QVBoxLayout()

        # Layout of buttons
        btnLayout = QHBoxLayout()

        # Add buttons to button layout
        btnLayout.addWidget(redBtn)
        btnLayout.addWidget(greenBtn)
        btnLayout.addWidget(blackBtn)
        btnLayout.addWidget(goBtn)

        # Add button layout to dialog layout
        dlgLayout.addLayout(btnLayout)

        # Add gpane to dialog layout
        dlgLayout.addWidget(self.p)       

        # Set main layout
        self.setLayout(dlgLayout)

        # Register callbacks
        self.connect(redBtn, SIGNAL("clicked()"), self.onRed)
        self.connect(greenBtn, SIGNAL("clicked()"), self.onGreen)
        self.connect(blackBtn, SIGNAL("clicked()"), self.onBlack)
        self.connect(goBtn, SIGNAL("clicked()"), self.onGo)  

    def onRed(self):
        self.statusBar.showMessage("Pen color set to red")
        self.p.setPenColor([255, 0, 0])

    def onGreen(self):
        self.statusBar.showMessage("Pen color set to green")
        self.p.setPenColor([0, 255, 0])

    def onBlack(self):
        self.statusBar.showMessage("Pen color set to black")
        self.p.setPenColor([0, 0, 0])

    def onGo(self):
        self.statusBar.showMessage("Drawing Moire with n = " + str(n))
        self.p.clear()
        self.moire()
        self.statusBar.showMessage("Done")

    def moire(self):
        for i in range(n + 1):
            for k in range(n + 1):
                self.p.line(1.0*i / n, 0, 1.0*k / n, 1)
        for i in range(n + 1):
            for k in range(n + 1):
                self.p.line(0, 1.0*i / n, 1, 1.0*k / n)

    def setStatusBar(self, statusBar):
        self.statusBar = statusBar


class MyWindow(QMainWindow):

    def __init__(self, dialog):
        super(MyWindow, self).__init__()
        self.initUI(dialog)

    def initUI(self, dialog):
        exitAction = QAction(QIcon('exit.png'), '&Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(qApp.quit)

        self.statusBar().showMessage('Ready')
        self.setCentralWidget(dialog)
        dialog.setStatusBar(self.statusBar())
        self.setWindowTitle('Moire Generator')
        menuBar = self.menuBar()
        fileMenu = menuBar.addMenu('&File')
        fileMenu.addAction(exitAction)
        self.show()

n = 10
app = QApplication(sys.argv)
dlg = MyDialog()
ex = MyWindow(dlg)
dlg.onGo()
sys.exit(app.exec_())
Progammcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

  Erklärungen zum Programmcode
 

 Das Hauptprogramm erzeugt eine Instanz app von QApplication() und nachher Instanzen von MyDialog() und  MyWindow(). Dann wird ein erstes Moire gezeichnet. Durch den Aufruf von sys.exit(app.exec_()) bleibt das Programm aktiv, bis das Fenster mit dem Close-Button geschlossen wird.

 

 

Buttons mit TigerJython

 

Mit den Buttons Red, Green und Blue soll auch hier die Linienfarbe gewählt werden. Mit Klick auf den Button GO wird die Grafik gezeichnet.

Mit TigerJython werden die Buttons als Grafik-Komponenten mit addComponent() in das GPanel-Fenster eingebettet Dabei werden die Buttons automatisch in der Reihenfolge der Aufrufe im oberen Teil des GPanel-Fensters plaziert (ähnlich dem Java FlowLayout)

Ein Klicken auf  einen Button wird als Event aufgefasst . Der Callback wird über den benannten Parameter actionListener im Konstruktor von JButton registriert. Hier wird derselbe Callback für alle Buttons verwendet und mit dem Parameter e entschieden, welcher Button geklickt wurde.


 

Programm:

# GuiEx2.py

from gpanel import *
import random
from javax.swing import *

def actionCallback(e):
    global c
    if e.getSource() == btn4:
        setColor(c)
        moire()
    if e.getSource() == btn1:
        c = "red"
    if e.getSource() == btn2:
        c = "green"
    if e.getSource() == btn3:
        c = "blue"
    
def createGUI():
    addComponent(btn1)
    addComponent(btn2)
    addComponent(btn3)
    addComponent(btn4)
    validate()

def moire():
    for i in range(11):
        for k in range (11):
            line(i, 0, k, 10)
    
    for i in range(11):
        for k in range (11):
            line(0, i, 10, k)
 
btn1 = JButton("Red", actionListener = actionCallback)
btn2 = JButton("Green", actionListener = actionCallback)
btn3 = JButton("Blue", actionListener = actionCallback)
btn4 = JButton("GO", actionListener = actionCallback)

makeGPanel(0, 10, 0, 10.5)
createGUI()
c = "black"
Progammcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

  Erklärungen zum Programmcode
 

Nachdem man alle GUI-Komponenten mit addComponent() zum GPanel hinzugefügt hat, sollte man validate() aufrufen, damit das Fenster mit Sicherheit mit den eingefügten Komponenten neu aufgebaut wird.

 

 

Textfelder und Labels mit PyQt4

 

Das Beispiel demonstriert die bekannte Berechnung der Zahl Pi nach der Monte-Carlo-Methode. Die Anzahl der Zufallspunkte kann in einem Textfeld eingegeben werden. Mit dem Button Go wird die Simulation gestartet. Das Resultat wird in einem Textfeld ausgeschrieben

Das Vorgehen ist ähnlich wie oben. Es werden Text-Widgets der Klassen QLabel und QLineEdit verwendet, um Messages auszuschreiben und Text einzulesen.
 

Programm: [►]

# GuiEx3.py

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import gpanel
import random

# --------------- Class MyDialog ---------------------- 
class MyDialog(QDialog):

    def __init__(self):
        super(MyDialog, self).__init__()

        # Create widgets
        self.p = gpanel.GPane(-0.1, 1.1, -0.1, 1.1)
        lbl1 = QLabel("Number of drops:")
        lbl2 = QLabel("Pi =")
        self.le1 = QLineEdit("10000")
        self.le2 = QLineEdit()
        goBtn = QPushButton("Go")

        # Layout of whole dialog
        dlgLayout = QVBoxLayout()  

        # Layout of buttons
        btnLayout = QHBoxLayout() 
        
        # Add buttons to button layout
        btnLayout.addWidget(lbl1) 
        btnLayout.addWidget(self.le1) 
        btnLayout.addWidget(goBtn) 
        btnLayout.addWidget(lbl2) 
        btnLayout.addWidget(self.le2) 
        
        # Add button layout to dialog layout
        dlgLayout.addLayout(btnLayout)
       
        # Add gpane to dialog layout
        dlgLayout.addWidget(self.p)
        
        # Set main layout 
        self.setLayout(dlgLayout)

        # Register callbacks
        self.connect(goBtn, SIGNAL("clicked()"), self.onGo)
        self.setWindowTitle("Raindrop Simulation")

    def init(self):
        self.le2.setText("")
        self.p.clear()
        self.p.setPenColor("black")
        self.p.pos(0.5, 0.5)
        self.p.rectangle(1, 1)
        self.p.pos(0, 0)
        self.p.arc(1, 0, 90)

    def onGo(self):
        global hits
        try:
            n = int(self.le1.text())
            if n < 10 or n > 100000:
                self.le2.setText("n not in 10...100000")
                return
        except:
            self.le2.setText("n not in 10...100000")
            return
        self.init()
        hits = 0
        for i in range(n):
            self.le1.setText(str(i + 1))
            zx = random.random()
            zy = random.random()
            if zx * zx + zy * zy < 1:
                hits += 1
                self.p.setPenColor("red")
            else:
                self.p.setPenColor("green")
            self.p.point(zx, zy)
        pi =  4.0 * hits / n
        self.le2.setText(str(pi))

# --------------- Main ---------------------- 
app = QApplication(sys.argv)
dlg = MyDialog()
dlg.show()
app.exec_()
Progammcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

  Erklärungen zum Programmcode
 

Es wird auf die Definition der Klasse MyWindow verzichtet, was den Code etwas verkürzt.

 

 

Textfelder und Labels mit TigerJython

 


Auch dieses Beispiel demonstriert die Berechnung von Pi nach der Monte-Carlo-Methode.

Das GUI besteht aus zwei Labels aus der Klasse JLabel, zwei Textfeldern aus der Klasse JTextField und einem Button aus der Klasse JButton. Nach ihrer Erzeugung fügt man diese mit addComponent() ins GPanel ein.
 

Programm:

# GuiEx4.py

from gpanel import *
import random
from javax.swing import *

def actionCallback(e):
    wakeUp()
    
def createGUI():
    addComponent(lbl1)
    addComponent(tf1)
    addComponent(btn1)
    addComponent(lbl2)
    addComponent(tf2)
    validate()

def init():
    tf2.setText("")
    clear()
    move(0.5, 0.5)
    rectangle(1, 1)
    move(0, 0)
    arc(1, 0, 90)

def go(n):
    hits = 0
    for i in range(n):
        zx = random.random()
        zy = random.random()
        if zx * zx + zy * zy < 1:
            hits = hits + 1
            setColor("red")
        else:
            setColor("green")
        point(zx, zy)
    return hits

lbl1 = JLabel("Number of drops: ")
lbl2 = JLabel("             PI = ")
tf1 = JTextField(6)
tf2 = JTextField(10)
btn1 = JButton("OK", actionListener = actionCallback)

makeGPanel("Monte Carlo Simulation", -0.1, 1.1, -0.1, 1.1)
createGUI()
tf1.setText("10000")
init()

while True:
    putSleep()
    init()
    n = int(tf1.getText())
    k = go(n)
    pi =  4 * k / n
    tf2.setText(str(pi))
Progammcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

  Erklärungen zum Programmcode
 

Wie oben wird der Button-Callback mit dem benannten Parameter actionListener registriert. Die Simulation darf nicht in einem Callback durchgeführt werden, da dort das Rendern der Grafik abgeschaltet ist. Sie muss entweder in einem eigenen Thread oder im Hauptthread stattfinden. Hier wird sie in der Funktion go() im Hauptthread durchgeführt Der Hauptthread bleibt durch Aufruf von putSleep() im Haltezustand, bist er durch Aufruf von wakeUp() im Button-Callback zum Weiterlaufen aufgefordert wird. Nach Ablauf der Simulation wird er wieder in den Haltezustand versetzt, bist mit dem Close-Button die Applikation beendet wird.