• Keine Ergebnisse gefunden

Grafische Bedienoberflächen

N/A
N/A
Protected

Academic year: 2022

Aktie "Grafische Bedienoberflächen"

Copied!
19
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Grafische Benutzeroberflächen Zusammenfassung

Malte L. Jakob

20. Januar 2020

(2)

Inhaltsverzeichnis

1 Einführung in die GUI-Programmierung 3

1.1 Design Patterns . . . 3

1.1.1 Observer-Modell . . . 4

1.1.2 Factory . . . 4

1.1.3 Singleton . . . 4

1.2 Klassifikation von GUI-Komponenten . . . 5

2 JavaFX 6 2.1 Grundelegendes zu JavaFX . . . 6

2.1.1 Orgaisation . . . 6

2.1.2 Ereignisverarbeitung . . . 6

2.1.3 Die Startklasse . . . 7

2.2 Fenster und GUI-Komponenten . . . 7

2.2.1 Klasse Stage . . . 8

2.2.2 GUI-Elemente . . . 9

2.3 Effekte . . . 10

2.4 Animationen . . . 10

2.4.1 Transitions . . . 11

2.4.2 Timelines . . . 11

2.5 Layoutobjekte . . . 12

2.5.1 Spezielle Container . . . 13

2.6 Dialoge . . . 13

2.6.1 Eigenschaften von Dialogen . . . 13

2.7 Menüs . . . 14

2.8 Multimedia . . . 15

2.9 Threads . . . 15

2.9.1 Arbeiten mit Threads . . . 16

2.9.2 Interprozesskommunikation . . . 16

3 CSS 17 3.1 Syntax . . . 17

3.2 Beispiele . . . 18

4 Event Handling 18

(3)

1 Einführung in die

GUI-Programmierung

Während Programme früher gar nicht oder nur über Textbefehle gesteuert wurden, gibt es heutzutage Grafische Benutzeroberflächen (engl.: Graphical User Interface – GUI). Diese Oberflächen werden heutzutage mithilfe des Model, View, Controller – kurz MVC – Modells realisiert. Dieses Modell hat die Absicht Daten und Programmier- logik von der Darstellung zu trennen. Der Controller liegt zwischen diesen beiden Schichten und sollte so „dünn“ wie möglich sein.

Die Methodik zur Erstellung einer GUI kann entweder mithilfe einer Klassenbiblio- thek direkt im Programm erzeugt werden, oder über einen externen GUI-Designer der die Oberfläche textuell (meistens durch ein xml-Dokument) beschriebt. Diese Beschrei- bung wird dann ins Programm geladen und als GUI umgesetzt.

Da ein Programm von der Interaktion mit dem Benutzer lebt, handelt es sich bei Programmen mit GUIs meist umEreignisgetriebene Programmierung, bei der eine Än- derung in Daten oder Darstellung immer von Nutzer oder System angestoßen werden muss.

Ein Ereignis hat immer mindestens folgende Attribute:

• Zeitstempel des Erstellungszeitpunktes

• Auslösende Komponente bzw. Quelle des Ereignisses

• Typ des Ereignisses, der grob beschreibt, wovon dieses Ereignis handelt.

Abhängig vom Typ kann das Ereignis noch weitere Informationen enthalten; Beispiels- weise bei einem Mausklick, welche Maustaste wo betätigt wurde.

Ereignisse werden „blind“ an das System übergeben. Gewisse Programmteile, die auf dieses Ereignis reagieren wollen – sogenannte EventHandler – müssen sich beim System dafür registrieren, dass sie bei einem entsprechenden Ereignis informiert werden wollen. Hierbei sind Design Patterns von Nutzen.

1.1 Design Patterns

Design Patterns sind gewisse Klassenkonstruktionen, die einen bestimmten Zweck er- füllen sollen. Für unterschiedliche Zwecke gibt es unterschiedliche Design Patterns.

(4)

Weitere Zusammenfassungen von Malte Jakob gibt es unter i-malte.jimdofree.com

Observer Interface Deklariert Methoden

Implementiert Interface

Subjektinstanz Subjektklasse

Instanzerzeugung

registriert Liste

registrierter Observer

Nachricht über Statusänderung

Abbildung 1.1: Funktionsweise von Oberver und Subjekt

1.1.1 Observer-Modell

Beim Observer-Modell existieren zwei Objekte: Eines wird beobachtet und das andere beobachtet. Doch anstatt in einem festgelegten Intervall nachzufragen (sogenanntes Polling), ob etwas gewisses (z.B. Änderung einer Eigenschaft) passiert ist, registriert sich der Observer beim Subjekt. Dieses informiert ihn dann bei gewünschten Ereignis- sen mit einer Nachricht. Das Subjekt verwaltet die Observer intern in einer Liste, wie in Abbildung 1.1 dargestellt.

1.1.2 Factory

Ist das Instanziieren einer Gewissen Klasse sehr aufwändig, so kann man sich eine Factory bauen, die das instanziieren von Objekten übernimmt – auch ohne dasnew- Keyword. Eine Factory kann entweder durch verschiedene Factory-Methoden oder eine Factory-Klasse implementiert werden.

Bei der Implementierung durch Methoden werden durch statische Methoden einer nicht instanziierbaren Klasse immer die Selben Objekte erzeugt. Erzeugt man eine Factory-Klasse, so können auch Objekte unterschiedlicher Ausprägung erstellt werden.

1.1.3 Singleton

Ein Singleton ist eine Klasse, von der nur ein einziges Objekt erzeugt werden darf.

Dies wird ermöglicht, indem alle Konstruktoren versteckt werden und es eine Klassen- methode gibt, über die eine Referenz auf das Objekt erhalten werden kann. Ist zum

(5)

Zeitpunkt des Aufrufs noch kein Objekt vorhanden, so wird es zu diesem Zeitpunkt instanziiert.

1.2 Klassifikation von GUI-Komponenten

Eine Graphische Benutzeroberfläche besteht aus vielen verschiedenen, hierarchisch an- geordneten Komponenten. Die Wurzel und Voraussetzung einer GUI ist ein Fenster;

Dieses Fenster kann weitere Elemente beinhalten:

Container sind Behälter für weitere Elemente. Dies können entweder elementare Kom- ponenten, oder weitere Container sein.

Elementare Komponenten sind Komponenten, die keine weiteren Komponenten in sich Tragen können. Dies sind beispielsweise Textfelder, Slider, Buttons etc.

Zum erstellen einer GUI benötigt es die bereits erwähntenKlassenbibliotheken. Hierbei gibt es eine große Auswahl an verschiedenen libraries, wie die Java Foundation Classes (JFC) oder das Standard Widget Toolkit (SWT). In dieser Zusammenfassung wird jedoch die GUI-Programmierung mitJavaFX behandelt.

(6)

2 JavaFX

2.1 Grundelegendes zu JavaFX

2.1.1 Orgaisation

JavaFX arbeitet zur Abstraktion mit einer Theater-Metapher. In dieser Metapher gibt es Bühnen (Stages), die Anwendungsfenster darstellen sollen, in denen Szenen gespielt werden. Die Szenen sind die angezeigten GUI-Elemente. Die Szene innerhalb einer Bühne kann auch wechseln, jedoch kann immer nur eine Szene zur selben Zeit gezeigt werden.

Der Aufbau einer Szene wird in einem sogenannten Szenengraph beschrieben, der über XML-Sprachen, hauptsächlich FXML beschrieben wird. Solch ein Graph kann auch über externe GUI-Programme erstellt werden. In dem Graphen ist jedes Element (auch Container) der GUI ein Knoten des Graphen.

Das Look and Feel – also das Aussahen der GUI-Elemente – kann mit CSS beschrie- ben werden. So muss der Szenengraph bei Aussehens-Änderungen nicht bearbeitet werden. Auch HTML-Inhalte können einfach in die GUI eingefügt werden. Auf jeden Knoten des Graphen können Transformationen und Animationen angewandt werden.

Hat der Knoten Kinder, so werden die Selben Vorgänge auch auf diese angewandt.

2.1.2 Ereignisverarbeitung

In JavaFX wird das Event-handling in zwei Phasen vollzogen: DurchEventCapturing undEventBubbling.

EventCapturing wird mit Hilfe der Klasse EventFilter implementiert und be- schreibt, dass ein Event, das in einem Knoten ausgelöst wird, zuerst von der Wurzel des Graphen zum Knoten hinunter wandert. Auf dem Weg kann es von den höhergelegenen Knoten bereits abgefangen und behandelt werden.

EventBubling wird mit Hilfe der Klasse EventHandlerimplementiert und bedeutet das genaue Gegenteil. Das ausgelöste Event steigt wie eine Blase vom auslösenden Knoten bis zur Wurzel auf. Auch hier kann das Ereignis von tiefschichtigen Knoten behandelt und abgefangen werden.

Hat ein Knoten sowohl einen EventFilterals auch einenEvenHandler, so werden diese in dieser Reihenfolge ausgeführt, da das Ereignis zuerst zum Knoten hinabsteigen muss, um wieder nach oben zu gelangen.

GUI-Objekte können auch Eigenschaften, sogenannte Properties, besitzen. Möchte man auf eine Änderung dieser Eigenschaft reagieren, so verwendet man einenEvent Listener.

(7)

Properties von GUI-Elementen können auch direkt an die Eigenschaften des logi- schen Modells gebunden werden. Dies nennt sich binding und bewirkt, dass die Än- derung der einen Eigenschaft unweigerlich die Änderung der anderen Eigenschaft zur Folge hat.

2.1.3 Die Startklasse

Die Startklasse benötigt in JavaFX eine feste Struktur. Zum einen muss die Startklas- se von der Klasse javafx.application.Application erben, zum Anderen muss die besondere Bedeutung folgender Methodennamen beachtet werden:

main()

Sie ist der Startpunkt für die JVM und ist auch ohne JavaFX eine verpflichten- de Funktion für ein ausführbares Programm. Von hier aus wirdlaunch(args) aufgerufen, um die GUI zu starten.

init()

Diese Methode ist optional und kann verwendet werden, um die Anwendung zu initialisieren und wird somit vor allen anderen Funktionen aufgerufen.

start(Stage main-window)

Diese Methode ist der Einstiegspunkt in die GUI und muss somit ausprogram- miert werden.

stop()

Diese optionale Methode wird aufgerufen, wenn die Anwendung über Platform.exit()geschlossen wird.

Eine Anwendung kann auf unterschiedliche Arten geschlossen werden. Durch einen Aufruf vonPlatform.exit()wird diestop()-Funktion aufgerufen und somit können vor dem Schließen noch zusätzliche Aspekte bearbeitet werden. Hierbei werden bei einem Multithreading-Programm jedoch nicht alle Threads gestoppt.

System.exit()stoppt alle laufenden Java-Threads, ruft diestop()allerdings nicht auf.

2.2 Fenster und GUI-Komponenten

Wie bereits erwähnt wird ein Fenster von der Klasse Stage dargestellt, die von der KlasseWindowerbt. Eine Stage wird immer auf dem Desktop platziert und kann nicht in ein anderes Fenster geschachtelt werden.

Das Fenster, das von der start(Stage main-window)-Funktion erstellt wurde ist das Hauptfenster und die Wurzel des Graphen. Weitere Fenster kann man mit dem Befehl new Stage() erzeugen. Die so erzeugten Fenster haben keinen Elternknoten.

Möchte man sie jedoch als Kindfenster verwenden, so kann ihnen mit

<Stage>.initOwner(Window parent)ein Elternknoten zugewiesen werden.

(8)

Weitere Zusammenfassungen von Malte Jakob gibt es unter i-malte.jimdofree.com

Eigenschaften Methoden

Sichtbarkeit show(), hide() Veränderbarkeit der Fenstergröße setResizable()

Titel setTitle()

Icon Hintergrundfarbe

Transparenz

Tabelle 2.1: Eigenschaften und Methoden von Stages

2.2.1 Klasse Stage

Für jedes Fenster kann ein Stil für die Darstellung festgelegt werden. Hierfür gibt es folgende Auswahlmöglichkeiten:

DECORATED (Voreinstellung)

Das Fenster wird im Stil des Betriebssystems mit Fensterbalken, Rand und wei- ßem, undurchsichtigem Hintergrund dargestellt.

UNDECORATED

Das Fenster hat nur einen weißen Hintergrund.

TRANSPARENT

Dieses Fenster eignet sich als spezielles Kindfenster, denn es ist komplett trans- parent.

UTILITY

Dieses Fenster ist fast identisch mitDECORATED-Fenstern, nur dass es im Fenster- balken nur minimale Bedienelemente gibt. Dies eignet sich insbesondere für Dia- logfenster.

Zudem hat die Klasse einige wichtige Eigenschaften und Methoden, wie in Tabelle 2.1 gezeigt wird. Die Größe eines Fensters wird beim erstellen mittels

new Stage(root,height,width) im Konstruktor angegeben. Sie kann nachträglich noch durch die Funktionen setHeight(double) und setWidth(double) verändert werden. Die Fenstergröße ist der Platz, den das Fenster insgesamt benötigt (samt Inhalt und Rand) und ist unabhängig von der Größe der darzustellenden Szene. Möchte man die Größe an den Inhalt anpassen, so kann sizeToScene() verwendet werden.

Zusätzlich können Höhe und Breite noch Ober- und Untergrenze gesetzt werden. Dies geschieht durch die entsprechendensetMax-undsetMin-Funktionen.

Ein Fenster auf dem Desktop platziert über entsprechende <Stage>.setX(double) (bzw. Y)-Befehle. Wenn das Fenster in der Mitte des Bildschirms platziert werden soll, so kann die MethodecenterOnScreen()verwendet werden.

Um ein weiteres Fenster zu erzeugen, müssen folgende Schritte abgearbeitet werden:

1. Instanziieren eines neuen Objektes

(9)

2. Stil der Stage festlegen (initStyle()) 3. Vaterfenster festlegen (initOwner()) 4. Eigenschaften Festlegen (getter und setter) 5. Szene für die Stage festlegen

6. Stage sichtbar machen (show())

2.2.2 GUI-Elemente

Wie bereits beschrieben, werden in einer Stage verschiedene Szenen dargestellt, de- ren unterschiedliche GUI-Elemente mithilfe eines Szenegraphen beschrieben. Zu ei- nem Zeitpunkt kann nzur eine Szene dargestellt werden, aber unterschiedliche Szenen können im Zeitverlauf ausgetauscht werden.

Der Szenengraph besteht aus drei Arten von Knoten:

Root ist ein Knoten ohne Elternknoten und wird auch Wurzelknoten genannt.

Branch ist ein Knoten der sowohl Eltern als auch Kinder hat.

Leaf ist ein Knoten, der keine Kinder hat.

Knoten, die Kinder haben können, also Root und Branch, sind Container-Klassen.

Leafs sind entweder leere Container oder elementare GUI-Komponenten.

Zusätzlich kann jeder Knoten mit einerideindeutig identifiziert werden; Diese muss im Szenengraphen eindeutig sein. Durch die Methodelookup("id")kann ein Knoten mit der entsprechenden id gefunden werden.

Auf jeden Knoten können auch, wie bereits erwähnt, Transformationen angewandt werden, hierbei gibt es verschiedene Arten:

Verschiebung (Translate) verschiebt den Knoten (und seine Kidner) im Koordinaten- system.

Rotation (Rotate) rotiert den Knoten (und seine Kinder) um einen gewissen Punkt im Koordina-tensystems.

Skalierung (Scale) vergrößert oder verkleinert die Darstellung eines Knotens (und seiner Kinder)

Transformationen können auch aneinandergereiht werden, um so komplexere Transfor-

mationen zu erstellen. Dies geschieht übernode.getTransforms().add(new <Transformation>);

Die Transformation kann hierbei Translate, RotateoderScalesein.

Wie bereits erwähnt, kann der Szenengraph in einer XML-Datei beschrieben werden, die einfach in das Programm geladen wird. Somit kann eine deutliche Menge an Code eingespart werden.

(10)

Weitere Zusammenfassungen von Malte Jakob gibt es unter i-malte.jimdofree.com Bei der Einbindung muss darauf geachtet werden, dass jedes Element, das interaktiv sein soll, mit einer entsprechendenfx:id=ïd"versehen wird, die dann über den ange- gebenenfx:controller=durch die Kommandos@FXML <Class> <name>;in das Pro- gramm importiert werden.

Dieses Laden in das Programm passiert über die Klasse FXMLLoader. Bei der In- itialisierung wird diesem die FXML-Datei zugewiesen. Die Benötigten Klassen können dann überload()oderget-Befehle erzeugt werden.

Shapes

Mit der Klasse Shape kann ein fast beliebig geformtes Objekt erstellt werden. Für be- sonders häufig verwendete Formen gibt es eigene Subklassen; Diese sind:Arc, Circle, CubicCurve, Ellipse, Line, Path, Polygon, Polyline, QuadCurve, Rectangle

Jede Form hat einen Rand und eine Füllung, die mittelssetStroke()undsetFill() angepasst werden können.

Ebenfalls können Formen zusätzlichen Operationen unterzogen werden, um komple- xere FOrmen zu erstellen. Diese sind:

Vereinigung Shape.union()

Aus den beteiligten Shapes wird eine neue Shape, die die Form beider Shapes hat.

Substraktion Shape.substract()

Die entstehende Shape hat nur noch die Form der ersten Shape, bei der die Teile Fehlen, die sich mit der zweiten Shape überlappen

Schnittmenge Shape.intersect()

Die entstehende Shape hat die Form der Teile, in denen sich beide Shapes über- lappen.

2.3 Effekte

Effekte können auf jeden Knoten im Szenegraphen angewandt werden. Sie werden im Package javafx.scene.effect bereitgestellt sind sehr vielfältig. Beispiele sind:

Glanzeffekte, Unschärfe, Bewegung, Schatten, Spiegelung

2.4 Animationen

So wie ei den Effekten, können auch Animationen auf jeden Knoten angewandt werden.

Hierbei gibt es mehrere Möglichkeiten, Knoten zu Animieren:

Transitions

Zeitlicher Übergang einer bestimmten Eigenschaft

(11)

Timelines

Auf einer Zeitleiste werden für gewisse Eigenschaften gewisse Werte Festgelegt (KeyValue), die zu einem gewissen Zeitpunkt (KeyFrame) eintreffen sollen. Die Übergänge zwischen den gegebenen Zeitpunken werden vom System berechnet (auch „Interpolation“ genannt).

Beide Arten der Animation haben jedoch gewisse Grundeigenschaften:

Rate: Geschwindigkeit der Animation Delay: Verzögerung nach dem Start

cycleCount: Anzahl an Wiederholungen (Unendlich ist auch möglich).

autoReverse: Nach ende der eigentlichen Animation, wird sie nochmal rückwärts ab- gespielt.

Die Animation kann mit.start()gestartet und mit.stop()gestoppt werden.

2.4.1 Transitions

Je nach der Eigenschaft, die verändert wird, unterscheidet man bei den Transitions in weitere Unterkategorien:

Fade: Veränderung der Transparenz

Rotate, Translate oder Scale: Drehen, vergrößern oder verschieben eines Knotens Path: Knoten wird entlang eines definierten Pfades bewegt

Eine Transition wird mittelsnew <Kategorie>Transition()initiiert und mittels.setNode(<node>) einem Knoten zugeordnet.

2.4.2 Timelines

Um eine Timeline zu erzeugen, kann einfachnew Timeline()verwendet werden. Um

einenKeyValuezu erzeugen, wird der Constructornew KeyValue(<Node-Instanz>.<Property>,

<Wert>) genutzt. Diese KeyValues können dann einem neuen KeyFrame zugeord- net werden: new KeyFrame(<Zeitpunkt>,<EventHandler>, <KV1>,<KV2>,...). Im Constructor kann auch ein EventHandler mitgegeben werden; DIes liegt daran, dass jedes mal, wenn in der Timeline ein KeyFrame erreicht wird, ein Event ausgelöst wird, auf das reagiert werden kann. Der Zeitpunkt muss als Duration-Objekt angegeben werden.

Zum Schluss müssen die KeyFrames nur noch der Timeline zugeordnet werden:

timeline.getKeyFrames().addAll(<Frames>). DAnach kann die Animation mittels .play()abgespielt werden; Dazu benötigt es allerdings mindestens zwei Frames; Eener davon zu Beginn (Duration.ZERO) und ein weiterer, der Das Ende darstellt.

Möchte man nicht, dass der Interpolator zwischen den Frames zwischenbilder bere- chet, so kann er auch auf DISCRETEgestellt werden.

(12)

Weitere Zusammenfassungen von Malte Jakob gibt es unter i-malte.jimdofree.com

2.5 Layoutobjekte

Für die bessere Positionierung der GUI-Komponenten gibt es verschiedene Layout- Objekte, die die Positionierung übernehmen. Häufig genutzte Layout-Objekte sind

• HBox

• VBox

• BorderPane

• GridPane BorderPane

Eine BorderPane wird in fünf Bereiche unterteilt: Top, Bottom, Left, Right und Center.

In einen Bereich kann genau ein Element eingefügt werden. Möchte man mehr als 5 Elemente in einer Szene verwenden, so empfiehlt sich das Verwenden von Containern.

Gesetzt werden die Elemente über setTop(),setBottom() usw. Methode.

Innerhalb eines Bereiches nehmen die Elemente nur so viel Platz ein, wie sie tatsäch- lich brauchen. Die Top- und Bottom-Bereiche sind so hoch, wie das höchste Element in diesem Bereich. Die Left- und Right-bereiche benötigen nur so viel Platz wei deren Ele- mente. Die Mitte nimmt den gesamten restlichen Platz ein. Außer in der Mitte, wo das Objekt zentriert wird, werden alle Elemente linksbündig eingefügt. Dieses Verhalten kann durch die Methode setAlignment()geändert werden.

JavaFX verwendet das CSS-Border-Modell, was bedeutet, dass bei jedem Element noch padding und margin eingestellt werden kann.

HBox und VBox

Eine HBox positioniert ihre Kinder linksbündig horizontal ohne Abstand nebeneinan- der. Die HBox ist wie die Top- und Bottom-Bereiche immer so groß wie das größte Kind, kann aber auch manuell geändert werden. Die Kinder behalten jedoch ihre Hö- he, was allerdings auch durch die MethodesetHgrow()geändert werden kann. Möchte man Abstand zwischen den Kindern, so kann die Methode setSpacing()verwendet werden.

Die Kinder werden in einer Liste verwaltet, die übergetChildern()abgerufen wer- den kann. Überadd()kann ein Element hinzugefügt werden, auch an einen gewissen Index.

Die VBox funktioniert analaog, nur setzt sie ihre Kinder vertikal über- und unter- einander.

GridPane

Dieser Container erstellt ein Gitternetz, bei dem Zeilen und Spalten in unterschied- licher Anzahl und Größe auftreten können. In jede Zelle kann ein Element platziert werden. Ein Element kann allerdings auch mehrere Zellen belegen. Beim Platzieren des

(13)

Elementes muss auch Zeile und Spalte angegeben werden, in der das Element platziert werden soll.

2.5.1 Spezielle Container

Group

Eine Group verwendet keinerlei Layoutregeln für seine Kinder; Alle werden an die Position (0,0) gelegt und müssen manuell verschoben werden Möchte man dennoch ein automatisches Layout innerhalb der Group, so kann man einen Layout-Container hineinlegen. Die Group selbst ist immer so groß, dass all ihre Kinder hinein passen.

Das besondere an der Group ist, dass ein Effekt (Siehe Punkt 2.3), der auf sie angewandt wird, automatisch auch auf alle Kindknoten angewandt wird.

Pane

Eine Pane ist die Superklasse aller Layout-Panes. Sie selbst besitzt keine Regeln und alle Kindknoten müssen, wie bei der Group, absolut positioniert werden. Dies kann z.B. für Zeichnungen recht nützlich sein.

Anders als die Group, gibt die Pane einen angewandten Effekt nicht an seine Kind- knoten weiter. Ebenfalls kann für die Pane eine Größe gewählt werden. Wird diese zu klein gewählt, so werden die Kindknoten abgeschnitten.

2.6 Dialoge

Eine besondere Form der Fenster sind Dialogfenster. Diese sind meist nur für kleine Informationen oder Eingabeaufforderungen gedacht.

Für die meist benötigten Gelegenheiten gibt es in JavaFX bereits vorgefertigte Klas- sen Alert, ChoiceDialogundTextInputDialog, deren grobes Aussehen noch über bereitgestellteset-Methoden verändert werden kann (z.B. welches Icon bei einem Alert gezeigt werden soll). All diese Klasse erben von der Klasse Dialog, die eine vorgefer- tigteDialogPane als Stage bietet.

Alternativ kann ein individueller Dialog erstellt und angezeigt werden. Hierfür muss aber zuerst eine Stage erstellt, eine Szene festgelegt und die Modalität (siehe Punkt 2.6.1) festgelegt werden.

Möchte man der DialogPane einen Bestimmten Knopf hinzufügen, so geschieht das über<DialogPane-Name>.getButtonTypes().add(<ButtonType>);

Möchte man einem Knopf einen anderen Text zuweisen, so geschieht dies über

<DialogPane-Name>.lookupButton(<ButtonType>).setText(<text>);

2.6.1 Eigenschaften von Dialogen

Neben dem Aussehen hat ein Dialog noch verschiedene andere Eigenschaften; Eine davon ist dieModalität, die angibt, ob andere Fenster der Anwendung noch benutzbar sein sollen, während das Dialogfenster angezeigt wird, oder nicht. Die Voreinstellung

(14)

Weitere Zusammenfassungen von Malte Jakob gibt es unter i-malte.jimdofree.com ist Modal, also sind Eingaben nur im Dialogfenster möglich. Bei der Modalität gibt es zudem noch verschiedene Abstufungen:

Modality.NONE

Eingaben sind überall möglich.

Modality.APPLICATION_MODAL

Die Gesamte Anwendung ist für Interaktionen gesperrt.

Modality.WINDOW_MODAL

Nur das Vaterfenster ist für Interaktionen gesperrt.

Neben der Modalität eines Dialoges kann er auch nochblockierend sein (oder eben nicht). Wird der Dialog mittels show() angezeigt, so wird die Anwendung nicht blo- ckiert und arbeitet ganz normal weiter. Wird sie mittelsshowAndWait()angezeigt, so so wird die Anwendung blockiert; Hierbei wird zusätzlich ein Wert zurückgegeben – die Auswahl des Nutzers. So kann auf Entscheidungen im Dialogfenster reagiert werden.

Der Rückgabetyp bei DialogPanes ist standardmäßig ButtonType; Bei InputDialog der eingegebene String und bei ChoiceDialog der Indes des gewählten Elements. Die Rückgabetypen können allerdings beim Erstellen des Dialoges geändert werden; Hier- für hängt man an den DatenTyp Dialog noch einen generischen Datentyp an (z.B.

Dialog<int>). Ist der Datentyp bestimmt, so muss allerdings noch eine spezielle Me- thode implementiert werden: resultConverterProperty() – diese gibt an, wie der gedrückte Knopf zum entsprechenden Datentyp umgewandelt werden soll. Allerdings kann der Nutzer den Dialog auch einfach schließen, ohne eine Auswahl zu treffen, daher sollte mittels .isPresent() überprüft werden, ob überhaupt eine Auswahl getroffen wurde.

2.7 Menüs

Menüs können in JavaFX an zwei Stellen aufterten:

In der Menüleise (MenuBar)

Liegt in der Regel direkt unter dem Fensterbalken und benötigt eineBorderPane als Wurzelknoten.

Als Kontext-Menü (ContextMenu)

Kann überall durch einen rechten Mausklick aufgerufen werden. Der Inhalt ist meist vom Kontext abhängig. Die Generierung geschieht meist über EventHand- ler oder Filter.

EineMenuBarkann mehrereMenus enthalten, welche wiederumMenuItems und weitere Menus enthalten können. Diese werden über.getItems().addAll(<items>)hinzuge- fügt.

Des weiteren gibt es noch besondereMenuItems:CheckMenuItemundRadioMenuItem.

Ersteres kann als Schalter verwendet, werden, um Dinge ein- und auszuschalten. Bei RadioMenuItems kann immer nur eine Option der vorhandenen gewählt werde – sofern sie in der selben Toggle-Group sind.

(15)

2.8 Multimedia

Möchte man in einer Anwendung eine kurze Audiodatei spielen, so eignet sich die Klasse AudioClip, die das Abspielen von Audiodateien ohne Verzögerungen ermög- licht und das Anpassen der Eigenschaften erlaubt. Bei mehrfachem Abspielen kann es jedoch zu Überlagerungen führen. Initialisiert wird ein Objekt dieser Klasse wie folgt:

new AudioClip(this.getClass().getRessource("<Pfad>").toString());. Abspie- len und anhalten erfolgt mittels der Methoden.play()und.stop(). Mittels.isPlaying() kann abgefragt werden, ob der Clip aktuell abgespielt wird.

Möchte man längere Audiodateien, Videos oder Livestreams implementieren, so eig- net sich die KlasseMediaPlayer. Der Erstellungsprozess ist hier jedoch etwas länger:

1 // Media - Objekt erzeugen

2 Media media = new Media (new File("<path >"). toURI (). toString ());

3 // MediaPlayer instanziieren

4 MediaPlayer mediaPlayer = new MediaPlayer ( media );

5 // MediaView erzeugen (nur bei Videos )

6 MediaView mediaView = new MediaView ( mediaPlayer );

7 // interagieren

8 mediaPlayer .play (); // Startet die Datei vom aktuellen Zeitpunkt (zu Beginn 0) 9 mediaPlayer . pause (); // Stoppt Wiedergabe

10 mediaPlayer .stop (); // Stoppt die Wiedergae und springt an den Anfang 11 mediaPlayer .seek(new Duration (ms )); /* Springt zu der in Millisekunden 12 angegebenen Stelle */

MediaPlayer ist lediglich für das Abspielen verantwortlich und liefert keinerlei Steue- rungsmöglichkeiten und Informationen an die GUI – diese Aufgebe übernimmt die MediaView.

DerMediaPlayer hat verschiedene Zustände: unknown, ready und playing.

Von playing kann in verschiedene Zustände gewechselt werden: paused, stalled (lädt Daten nach) und stopped.

Bei jedem Zustandsübergang wird vom MediaPlayer ein Event geworfen. Wurde die Datei zu Ende abgespielt, so verbleibt dermediaPlayerim playing-Zustand, wirft allerdings ein EndOfMedia-Event.

2.9 Threads

Threads sind kleine Prozesse innerhalb eines Prozesses, die unabhängig voneinander arbeiten können. Möchten allerdings zwei Threads gleichzeitig auf die selben Daten zugreifen, kann es zu Problemen kommen, es sei denn, die Vorgänge sindThread-Safe – dies ist bei JavaFXnicht der Fall.

Möchte ein anderer Thread als der JavaFX-Thread die GUI verändern, so kommt es zu einem Laufzeitfehler. Also muss bei externen Änderungswünschen der JavaFX- Thread mit diesen Änderungen beauftragt werden. Dies geschieht mittels

Platform.runlater(runnable Object).

(16)

Weitere Zusammenfassungen von Malte Jakob gibt es unter i-malte.jimdofree.com Das auslagern in Threads kann allerdings auch in GUI-Programmen durchaus nütz- lich sein. Muss eine sehr rechenintensive Aufgabe ausgeführt werden, so kann es sein, dass im selben Thread keine Rechenzeit mehr bleibt, um die GUI zu animieren – die GUI friert ein. Um das zu vermeiden, können solche Aufgaben in Java-Threads oder JavaFX-Tasks ausgelagert werden.Diese können den Haupt-Thread bei beendigung der Aufgabe benachrichtigen, damit diese in der GUI entsprechend angezeigt werden kann.

2.9.1 Arbeiten mit Threads

Um einen Thread zu erzeugen, kann eine Klasse entweder von der KlasseThread– ein leerer Thread, der keine Funktion erfüllt – erben, oder das Interfacerunnableimple- mentieren. Zusätzlich bietet die KlasseThreadauch einige statische Methoden an, die beim Aufruf immer mit dem aufrufenden Thread arbeiten (z.B. Thread.sleep(ms)).

Möchte man nun einen Thread aus einer Thread-Klasse erzeugen, so muss zu beginn das entsprechende Objekt mit new instanziiert werden. Danach muss die Methode start() aufgerufen werden, welche wiederum die Methoderun() ausführt.rundarf jedoch niemals direkt ausgeführt werden.

Ein Thread endet, wenn die run-Funktion endet. Möchte man einen Thread von außen terminieren, so kann einem Thread mittelsinterrupt()mitgeteilt werden. Der Thread muss das entsprechende Interrupt-Flag allerdings mittelsThread.interrupted() von selbst abfragen und sich freiwillig beenden; Diese Methode löscht dabei das Interrupt- Flag. Ob ein anderer Thread interrupted ist, kann man mittels<Thread>.isInterrupted() abfragen; Dies löscht die Flag allerdings nicht. Gewisse Methoden wie sleep, wait oder join werfen immer eine InterruptException, die mit einem try-catch Block abgefangen werden müssen.

Möchte man einen Thread aus einer Klasse erzeugen, die das runnable-Interface im- plementiert, so muss zuerst das Objekt instanziiert werden. Dieses Objekt muss dann mittels Thread <name> = new Thread(runnable instance) in einen Tread umge- wandelt werden. Dieser Thread kann dann ebenfalls mittelsstart gestartet werden.

2.9.2 Interprozesskommunikation

Möchten zwei Threads gleichzeitig auf dieselben Daten zugreifen, so kann dies zu Problemen führen. Um dies zu vermeiden mussMutual Exclusionangewandt werden. In Java wurde dies durch das Monitorkonzeptimplementiert. Möchte man eine Methode oder ein Objekt nur einem Thread zur gleichen Zeit zugänglich machen, so kann das Schlüsselwort synchronizedvorangestellt werden.

Innerhalb eines Monitors kann auch über wait so lange mit der Ausführung der nachfolgenden Befehle gewartet werden, bis sie ein durch notify ausgelöstes Event erhält.

Ab Java 5 gibt es auch synchronisierte Collections, Lock-/ Condition-Objekte und Semaphoren.

(17)

3 CSS

Die Form und das Aussehen eines Knoten können verändert werden. Dies geht entweder im Java-Programm über entsprechendeset-Methoden, oder in einer externen Datei – demCascadingStylesheet (CSS). Ähnlich wie bei der FXML-Datei bedeutet dies eine deutliche Einsparung an Code.

CSS kann über verschiedene Wege in das Programm integriert werden. Zum einen können die CSS-Regeln direkt an das Objekt angefügt werden:

<objekt>.setStyle("<regeln>");. Zum anderen kann im CSS eine Klasse definiert werden, zu der ein Objekt dann hinzugefügt wird:

<objekt>.getStyleClass().add("<css-gruppe>");. Möchte man einer gesamten Szene einem Stylesheet zuordnen, so kann man auch folgenden Befehl verwenden:

<szene>.getStylesheets().add("<css-datei>");

3.1 Syntax

Doch wie genau funktioniert CSS? Eine Element-Art wird mit folgender Syntax be- schrieben:<element> {<eigenschaft>:<wert>;...}

Es können auch für mehrere Elemente die selben Regeln deklariert werden, hierfür wird folgende Syntax angewandt:<element>, <element>, .... Möchte man nur Elemente in bestimmten Konstellationen umstylen (z.B. nur Knöpfe innerhalb eines Formulars), so kann diese Syntax angewandt werden:<vater> <kind>; Die Deklarationen gelten dann für das entsprechende Kindelement. Zudem kann ein Element auch mit einer einzigartigen ID versehen werden. In CSS kann das entsprechende Element dann wie folgt angesprochen werden: #<id>. Natürlich lassen sich auch Gruppen erstellen. Im Gegensatz zur ID kann eine Gruppe mehrere Elemente haben, die ihr angehören. Sie kann mit .<gruppe>angesprochen werden. JavaFX hat für jede seiner Elemente eine eigene Gruppe erstellt. Möchte man also allen Knöpfen ein anderes Aussehen verleihen, so kann man die entprechenden Regeln für die Gruppe.buttondeklarieren.

Die einzelnen Eigenschaften entsprechen den Eigenschaften aus HTML, jedoch wird immer -fx- vorangestellt. Die Eigenschaften werden wie bei html auch vererbt. De- finiert man für die Wurzel eine Hintergrundfarbe, so haben auch alle Kinder diese Hintergrundfarbe, es sei denn in einer entsprechenden Regel wird für das Kind explizit etwas anderes definiert.

Die Regeln werden auch kaskadiert, haben also unterschiedliche Prioritäten. Am

„wichtigsten“ sind die Regeln, die direkt über setStyle() definiert werden. Danach kommen die Regeln, die über.setStylesheet()hinzugefügt werden. Am unwichtigs- ten sind die voreingestellten CSS-Regeln.

(18)

3.2 Beispiele

Mit diesem Wissen kann die Gestaltung viel simpler gestaltet werden. Um in einem Spielbrett jede zweite Feld schwarz zu machen, kann jedem entsprechenden Feld die Klasse „black“ zugewiesen werden und im CSS mit .black{-fx-background-color:

#000000}definiert werden. Ohne CSS müsste bei jedem Feld die Methodenkonstella- tion<element>.setFill(new Color(0,0,0))angewandt werden.

Beim Eines Bildes ist der Unterschied sogar noch gravierender. In Java wären fol- gende Befehle nötig (ohne Fehlerbehandlung):

Image picture = new Image(getClass().getResource("Bild"));

ImageView figure = new ImageView(picture);

<element>.setGraphic(figure);

Mit CSS ist hier nur das Kommando-fx-graphic: urlnötig. def

4 Event Handling

In 2.1.2 wurde das Konzept des Eventhandlings bereits erklärt; Nun folgt die Erklä- rung der Implementierung. Die meisten Targets besitzen setOnxxx(eventhandler)- Methoden. Diese existieren für jegliches Event (z.B.ActionEventundsetOnAction).

Über solch eine Methode kann allerdings nur ein Handler registriert werden. Möchte

man mehrere Handler registrieren, so kann dies über dieaddEventHandler(<event>,<eventhandler>)- Methode geschehen. Die Handler werden allerdings nicht zwangsweise in der selben

Reihenfolge aufgerufen, in der sie registriert wurden.

BeisetOn-Methoden kann eine anonyme Instanz erzeugt werden. Anonyme Instan- zen werden erzeugt, wenn sie benötigt werden und danach wieder gelöscht. ein Beispiel ist folgendes:

1 target . setOnAction (new EventHandler < ActionEvent >(){

2 @Override

3 public void handle ( ActionEvent event ){

4 // Handling

5 }

6 });

Noch schneller geht es mit sogenannten Lambdas. Hat eine Klasse nur eine einzige Methode, so kann auf das (anonyme) instanziieren der Klasse verzichtet werden, da die Funktion, der der Handler übergeben wird durch ihren Parameter bereits definiert, was für einen Handler sie erwartet. Somit muss nur noch die Funktion implementiert werden:

1 <element >. addActionListener (e->

2 {

3 // Handling 4 });

(19)

Möchte man einen Handler in der Capture-Phase setzen, statt in der Bubbling-Phase, so wird statt addEventHandler() addEventFileter() verwendet. Möchte man ein Event herausfiltern, so kann innerhalb des Handlers <event>.consume() aufgerufen werden.

Neben ActionHandlern gibt es auch Listener. Um diese zu Nutzen, muss jedoch zuerst eine Property erstellt werden. Für eine Property eines primitiven Datentyps gibt es die KlasseSimple<Datentyp>Property. Wird ein Feld als Property deklariert, so wird für jede Änderung des Wertes ein Event ausgelöst. Diese Events können von Listenern empfangen werden, die die Methode Handle ausführen. In der Funktion zugänglich sind sowohl der alte als auch der neue Wert.

Möchte man eine Darstellung der GUI immer sofort an Änderungen eines entspre- chenden Wertes binden – oder anders herum – so kann dies natürlich über Properties und listener gemacht werden. Eine leichtere Lösung ist hierbei jedoch dasDatabinding.

Dies verbindet zwei Werte entweder uni- oder bidirektional miteinander. Ändert sich also der eine Wert, so wird der andere Wert ebenfalls verändert. Das Binding geschieht über Object.<name>Property().bind(object.<name>Property());Hierbei ist die Property innerhalb derbind()Funktion die Property, die beobachtet wird; Der Wert der anderen Property wird entsprechend angepasst. Die gebundene Property kann al- lerdings nicht mehr durch andre Methoden verändert werden; Sollte man es dennoch versuchen, so gibt es einen Laufzeitfehler. Die andere Property kann nach belieben verändert werden.

Möchte man eine bidirektionale Bindung, so geschieht dies über die Methode bindBidirectional().

Referenzen

ÄHNLICHE DOKUMENTE