deutsch     english     français     Drucken

 

3.13 WIDGETS

 

 

EINFÜHRUNG

 

Dir bekannte Programme besitzen meist eine grafische Benutzeroberfläche, ein GUI (Graphics User Interface). Du erkennst gewöhnlich eine Menüleiste, Eingabefelder und  Schaltflächen (Buttons). GUI-Komponenten, auch Widgets genannt, werden als Objekte aufgefasst, so wie du sie aus dem Kapitel Turtleobjekte bereits kennst. Willst du Programme mit einer modernen Benutzeroberfläche schreiben, so ist es darum unumgänglich,  dass du die grundlegenden Konzepte der Objektorientierten Programmierung (OOP) kennst [mehr...Die Konzepte der OOP werden vertieft im Kapitel Spielprogrammierung behandelt].

Die Widgets werden in verschiedene Klassen gemäss folgende Liste eingeteilt:

Widget Klasse
Schaltflächen (Buttons) JButton
Beschriftungen (Labels) JLabel
Eingabefelder (Textfelder) JTextField
Menüleiste (MenuBar) JMenuBar
Menüeintrag JMenuItem
Menü mit Menüeinträgen JMenu

So wie du eine Turtle mit dem Aufruf des Konstruktors der Klasse Turtle erzeugt hast, musst du eine GUI-Komponente durch den Aufruf des entsprechenden Klassenkonstruktors erzeugen. Oft besitzen die Konstruktoren Parameter, mit denen du bestimmte Eigenschaften des Widgets festlegen kannst. Beispielsweise erzeugst du ein Eingabefeld der Länge 10 Zeichen mit tf = JTextField(10).

Beim Aufruf des Konstruktors wir auch eine Variable definiert, mit der du nachher auf das Objekts zugreifst. Beispielsweise liefert tf.getText() den Text, der im Textfeld steht.

Um ein Widget in einem GPanel sichtbar zu machen, verwendest du die Funktion addComponent() und übergibst ihr die Objektvariable. Dabei werden die Widgets automatisch in der Reihenfolge der Aufrufe im oberen Teil des GPanel-Fensters plaziert [mehr...Für ein allgemeineres Layout müsste man einen Layout-Manager verwenden, was später behandelt wird].

PROGRAMMIERKONZEPTE: Grafische Benutzeroberfläche, GUI-Komponente, GUI-Callback

 

 

π MIT DER REGENTROPFEN-SIMULATION

 

Du hast bereits gelernt, wie man mit der Monte-Carlo-Simulation eine Fläche bestimmen kann. Stell dir vor, dass du in einem Quadrat mit Seitenlänge 1 einen Viertelkreis mit Radius 1 einzeichnest. Wenn du nun n Regentropfen gleichmässig auf das Quadrat fallen lässt, so kannst du dir leicht überlegen, wie viele davon in Mittel auf den Viertelkreis fallen. Da die Viertelkreisfläche

  S =  (1)/ 4 * r2* π = (π)/ 4

ist und das Quadrat die Fläche 1 besitzt, sind es nämlich

  k = n  *  (π)/ 4  

Tropfen. Lässt du also in einer Computersimulation n Tropfen fallen und zählst k, so kriegst du einen Näherungswert für π aus

  π = (4 * k)/   n  
 

Das GUI besteht aus zwei Labels, zwei Textfeldern und einem Button. Nach ihrer Erzeugung fügst du sie mit addComponent() ins GPanel ein.

Es versteht sich von selbst, dass das Klicken auf den OK-Button als Event aufgefasst wird. Der Callback wird über den benannten Parameter actionListener im Konstruktor von JButton registriert. Du hast sicher nicht vergessen, dass du in einem Callback keinen lang dauernden Code ausführen solltest. Darum rufst du im Callback lediglich  wakeUp() auf, wodurch das mit putSleep() in der while-Schleife angehaltene Programm aufgeweckt wird und es die Simulation ausführt.

from gpanel import *
from random 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 doIt(n):
    hits = 0
    for i in range(n):
        zx = random()
        zy = 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 = doIt(n)
    pi =  4 * k / n
    tf2.setText(str(pi))
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Widgets sind Objekte der Swing-Klassenbibliothek. Sie werden mit dem Konstruktor, der den Namen der Klasse hat, erzeugt. Beim Aufruf des Konstruktors wird eine Variable definiert, mit der du auf das Objekt zugreifen kannst. Um das Widget im GPanel anzuzeigen, rufst du die Funktion addComponent() auf und übergibst ihr die Widget-Variable.

Nachdem du alle Widgets zum GPanel hinzugefügt hast, solltest du validate() aufrufen, damit das Fenster mit Sicherheit mit den eingefügten Widgets neu aufgebaut wird.

Button-Callbacks kannst du mit dem benannten Parameter actionListener registrieren. Denke daran, dass ein Callback nie lange dauernden Code ausführen sollte.

 

 

MENÜS (Nichts zum Essen)

 

Viele Bildschirmfester besitzen eine Menüleiste mit mehreren Menüeinträgen (Items). Beim Klicken auf einen Menüeintrag, kann auch ein Untermenü geöffnet werden, das wiederum Menüeinträge enthält. Menüs und Menüobjekte werden ebenfalls als Objekte aufgefasst.  Die Auswahl einer Menüoption löst einen Event aus, der über einen Callback behandelt wird.

Ein Menü baust du so  auf, dass du ein Objekt von JMenuBar() erzeugst und diesem mit add()  Objekte von JMenuItem hinzufügst. Du kannst aber auch ein Untermenü hinzufügen. Dazu erstellst du ein Objekt von JMenu, und fügst ihm Objekte von JMenuItem hinzu. Ein Menü ist also hierarchisch aufgebaut.

Um den Code etwas zu vereinfachen, kannst du für alle Menüoptionen den gleichen Callback actionCallback() verwenden. Du registriert ihn bei jedem Konstruktor von JMenuItem mit dem benannten Parameter actionPerformed. Im Callback kannst du dann mit getSource() bestimmen, durch welche Menüoption der Callback ausgelöst wurde.

 
from gpanel import *
from javax.swing import *

def actionCallback(e):
    if e.getSource() == goItem:
        wakeUp()
    if e.getSource() == exitItem:
        dispose()
    if e.getSource() == aboutItem:
        msgDlg("Pyramides Version 1.0")

def doIt():
    clear()
    for i in range(1, 30):
        setColor(getRandomX11Color())
        fillRectangle(i/2, i - 0.35, 30 - i/2, i + 0.35)

fileMenu = JMenu("File")
goItem = JMenuItem("Go", actionPerformed = actionCallback)
exitItem = JMenuItem("Exit", actionPerformed = actionCallback)
fileMenu.add(goItem)
fileMenu.add(exitItem)

aboutItem = JMenuItem("About", actionPerformed = actionCallback)

menuBar = JMenuBar()
menuBar.add(fileMenu)
menuBar.add(aboutItem)

makeGPanel(menuBar, 0, 30, 0, 30)

while not isDisposed():
    putSleep()
    if not isDisposed():
        doIt()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Du hältst dich an die Regel, dass in Callbacks kein lange dauernder Code ausgeführt werden soll. Du führst das Zeichnen also im Hauptblock durch.

Damit das Programm mit Sicherheit beendet wird, wenn du  den Close-Button des Fensters oder die Exit-Option klickst, testest du mit isDisposed(), ob das Fenster geschlossen wurde [mehr...Beim Schiessen des Fensters bzw. beim Aufruf von dispose() wird automatisch putSleep() verlassen].

 

 

EIN SEPARATES DIALOGFENSTER BENUTZEN

 

TigerJython stellt dir Werkzeuge zur Verfügung, mit denen sich eigenständige Dialogfenster ohne viel Aufwand realisieren lassen. Dabei werden die klassischen Bedienungselemente wie Textfelder, Schaltflächen (Buttons), Markierungsfelder (Checkboxen), Optionsfelder (Radiobuttons) und Schieberegler (Sliders) als Python-Objekte modelliert, die sich in Bereichen (Panes) eines Bildschirmfensters befinden, das während der Programmausführung ständig sichtbar bleibt. Ein solches Fenster nennt man auch einen nicht-modalen Dialog.

Das Dialogfenster wird durch ein Objekt der Klasse EntryDialog() erzeugt, wo  die einzelnen Bereiche als Objekte der Klasse EntryPane() in der Reihenfolge der Parameter untereinander eingefügt sind. Die EntryPanes enthalten die Bedienungselemente als Objekte der Klassen ButtonEntry, RadioEntry, CheckEntry, IntEntry, FloatEntry, LongEntry, StringEntry und SliderEntry. (Du kannst dich in der APLU-Dokumentation genauer orientieren.)

Dein Programm simuliert die  Bewegung einer Masse, die an einer Feder befestigt ist und eine geschwindigkeitsproportionale Reibung erfährt. Es könnte sich um die Aufhängung eines Autorads handeln, das mit einem Stossdämpfer versehen ist.

Gemäss dem Newtonschen Bewegungsgesetz berechnet sich die Beschleunigung aus a = F/m, wo F die Kraft und m die Masse sind. Das Feder- und das Reibungsgesetz liefern F = -k*x - r*v, wo k die Federkonstante und r der Reibungskoeffizient sind. Die Lösung erfolgt iterativ in kleinen Zeitschritten dt: Die neue Geschwindigkeit ist v  = v + a*dt und die neue y-Koordinate y = y + v*dt.

Du verwendest ein Dialogfenster mit einem Schieberegler für den Reibungskoeffizenten, einer Statuszeile, um Rückmeldungen an den Benutzer zu geben, und einem Start-Button, um die Simulation zu starten. Es werden drei Objekte friction, status und btn erzeugt, die du je in eine EntryPane pane1, pane2, pane3 fügst. Diese übergibst du dem EntryDialog, der sie in  der Parameterreihenfolge darstellt.

from gpanel import *
import math

def doIt():
    clear()
    drawGrid(0, 200, -100, 100, "seagreen")
    m = mass.getValue() / 1000 # Mass (kg)
    k = spring.getValue() / 1000 # Spring const (N/kg)
    t = 0; y = 50; v = 0 # Initial conditions
    r = 0.7       # Coefficient of friction (N/m/s)
    d = 200; dt = 0.01
    status.setValue("m = %6.2f kg, k = %6.2f N/kg" % (m, k))
    move(t, y)  # Initial cursor position
    while t < d:
        draw(t, y)      # Draw segment
        F = -k*y - r*v  # Force
        a = F/m         # Acceleration
        v = v + a*dt    # New velocity
        y = y + v*dt    # New position
        t = t + dt      # New time
    T = 2 * math.pi * math.sqrt(m / k)
    status.setValue("Done")

mass = SliderEntry(0, 10000, 10000, 1000, 500)
pane1 = EntryPane("Mass (g)", mass)
spring = SliderEntry(0, 1000, 500, 100, 50)
pane2 = EntryPane("Spring constant (mN/kg)", spring)
goBtn = ButtonEntry("Go")
pane3 = EntryPane(goBtn)
status = StringEntry("")
pane4 = EntryPane("Status", status)
dlg = EntryDialog(850, 150, 
        pane1, pane2, pane3, pane4)

makeGPanel()
window(-20, 220, -110, 110)
drawGrid(0, 200, -100, 100, "seagreen")
title("Harmonic Oscillation")
status.setValue("Press Go to start")
while not dlg.isDisposed():
    if isDisposed():
        dlg.dispose()
        break
    if goBtn.isTouched():
        doIt()
dispose()
Programmcode markieren (Ctrl+C kopieren, Ctrl+V einfügen)

 

 

MEMO

 

Damit das Programm bei Klicken auf den Close-Button der Titelleiste beendet wird, verwendest du in einer while-Schleife die Bedingung isDisposed(), die wahr wird, sobald der Close-Button gedrückt wird. In doIt() frägst die den Zustand der Bedienungselemente mit getValue() ab. Mit isTouched() findest du heraus, ob du mit der Maus auf ein Bedienungselement geklickt hast, hier zum Beispiel auf den Start-Button.

Durch einige Versuche findest du heraus, dass sich die Schwingungsdauer mit zunehmender Dämpfung wenig ändert und dass es einen Grenzfall gibt, wo keine Schwingung mehr auftritt. Dieser asymptotische Grenzfall wird bei Stossdämpfern von Autos angewendet, damit das Auto möglichst wenig schwingt.

 

 

AUFGABEN

 

1.


Gehe vom Programm Moiré im Kapitel 3.2 aus und füge ein Textlabel, ein Eingabefeld für die Verzögerungszeit und einen OK-Button hinzu. Beim Klicken des OK-Buttons wird die Grafik mit der eingegebenen Verzögerungszeit (in Millisekunden) immer wieder neu erstellt.


2.

Gehe vom Programm unter "Elegante Fadengrafik-Algorithmen" im Kapitel 3.8 aus und füge ihm folgendes Menü hinzu: Der Menüeintrag "Options" soll ein Untermenü mit den Einträgen "Red", "Green" und "Blue" enthalten. Der Menüeintrag "Go" soll die Fadengrafik mit der unter Options ausgewählten Farbe zeichnen. Ist noch keine Farbe gewählt, so wird mit schwarzer Farbe gezeichnet.


3*.

Nehme eines deiner Lieblingsprogramme zur GPanel-Grafik und füge ihm einige sinnvolle Widgets hinzu.

4*.

Führe eine Simulation einer ungedämpften Federschwingung aus, bei der die Simulationsdauer und der Zeitschritt mit Eingabefeldern, sowie die Masse, Federkonstante und Amplitude mit Schiebereglern eingegeben werden. Nach der Simulation soll zudem die theoretisch zu erwartende Schwingungsdauer

in der Statuszeile ausgeschrieben werden, damit diese mit der Simulation verglichen werden kann.