GUI AnPr Name 1 Klasse Datum Die Notwendigkeit von GUIs Computerprogramme wurden erst dann für den „Laien“ interessant, als die Nutzerführung über eine grafische Nutzeroberfläche (englisch „Graphical User Interface“ – kurz GUI) realisiert wurde. Um dies zu ermöglichen, sind mitunter sehr aufwändige Programmierelemente zu realisieren. Da es für die meisten Programmierer jedoch zu zeitaufwändig (wenn überhaupt machbar) ist derartige Funktionen zu realisieren, greifen sie im Regelfall auf vorgefertigte Elemente in einer Programmbibliothek zurück, welche lediglich nur noch „angepasst“ werden müssen. In Java gibt es mehrere Programmbibliotheken, die für uns nutzbar sind. Hier die vier wichtigsten: AWT Swing SWT JavaFX Jede dieser Bibliotheken hat seine Vor- und Nachteile. AWT und Swing sind Teil der JFC (also Java Foundation Classes) und sind somit auf allen Betriebssystemen gleich verfügbar. Wir müssen uns nicht um irgendwelche Abhängigkeiten Gedanken machen. AWT ist wiederum die Basis, auf der Swing aufsetzt. SWT wurden von IBM für Eclipse entwickelt und hat mitunter ein ansprechenderes Design als Swing (wobei Swing bezüglich „Look and Feel“ durchaus anpassbar ist). Nachteilig wirkt sich bei SWT aus, dass mitunter Zusatzbibliotheken benötigt werden, welche nicht zwingend auf jedem System vorhanden sind. JavaFX ist von Oracle die mittelfristig bevorzugte Lösung für Desktop Applikationen, wobei es jedoch von der Programmierkomplexität im Vergleich zu SWT und Swing aufwändiger ist. In Bezug auf Einfachheit und Portierbarkeit bietet Swing im Zusammenspiel mit AWT derzeit die beste Lösung für unseren Unterricht. Aus diesem Grunde werden wir mit Swing arbeiten, wobei die grundsätzlichen Konzepte im Wesentlichen allgemeingültig sind. Wir finden die Swing Klassen in der Java Bibliothek „javax.swing“. Info: Eine Klasse von Swing hatten wir bereits kennen gelernt – die JOptionPane Klasse. Hier haben wir die Popup Fenster genutzt, um einfache Ein- und Ausgaben zu realisieren. 2 Die Grundstruktur Ein Programm mit einer GUI hat typische Elemente. Hier die wichtigsten: ANPR_GUI_Programmierung_v01.docx Seite 1 GUI AnPr Jedes dieser Elemente findet ein Pendant in unserer Swing Bibliothek. Unser erstes Ziel ist es nun, diese Elemente auszuprobieren, so dass wir das grundsätzliche Verhalten verstehen und nutzen können. 3 JFrame – oder der Rahmen unseres Programms Alle Elemente werden auf den JFrame „montiert“. Doch bevor wir dies tun, probieren wir mal eine einfache Anwendung aus: Zuerst muss klar sein, dass wir hier nur einen kleinen „Testballon“ fliegen lassen. Unsere Applikation kann eigentlich noch gar nichts! Das einzige, was hier passiert ist, dass ein Fenster geöffnet wird, in dessen Titel unser vorgesehener Text „Meine erste GUI“ zu sehen ist. Das ist zwar nicht viel, aber immerhin der erste Schritt. Bleibt der Interpreter jetzt eigentlich stehen, wenn wir in der Zeile „myJFrame.setVisible(true);“ sind? Antwort: So wie wir bis jetzt unseren Code gesehen haben, müsste man das denken. Tatsächlich ist es aber so, dass Java einen eigenen „Thread“ für alle GUI Aktivitäten hat (also einen eigenen Prozess, der parallel zu dem Prozess läuft, der unser Programm gestartet hat). Dieser sogenannte UI Thread übernimmt nun unser Fenster. Somit läuft der Thread unseres Aufrufes ganz normal weiter und die main Methode wird beendet! Unsere Variable „myJFrame“ repräsentiert nun das Fenster. Wir sind also nun darauf angewiesen, dass die Klasse JFrame alles das bietet, was wir für unser Programm benötigen. Das ist zwar soweit OK, da wir recht viel mit JFrame anstellen können, aber wir wollen noch flexibler werden. Deshalb gehen wir einen Schritt weiter – wir erstellen unsere eigene JFrame Klasse, indem wir von JFrame erben. Wie wir im Code sehen, erzeugen wir uns eine eigene Klasse, welche von JFrame erbt. Somit können wir die JFrame Klasse um beliebig viele Methoden ergänzen, so wie wir es brauchen. Weiterhin wurde in die MyJFrame Klasse auch gleich die main Methode implementiert, so dass sich die Klasse selbst aufrufen kann. Da der JFrame ohnehin im UI Thread läuft können wir dies so realisieren. Nun müssen wir in unserem Code noch etwas „aufräumen“. Es ist sinnvoll, dass die Dinge, welche für die Funktionalität der gesamten Klasse benötigt werden, auch innerhalb der Klasse definiert werden und nicht wie im obigen Beispiel in der main Seite 2 AnPr GUI Methode. Hierzu zählt in unserem kleinen Programm alles, was wir individuell setzen. Sehen wir uns vor diesem Hintergrund mal folgendes Programm an: Der Unterschied ist nun, dass wir alle notwendigen Einstellungen in eigenen Methoden kapseln. Dies schafft Übersicht! Auch den Titel können wir außerhalb des Superkonstruktors über die Methode „setTitle“ setzen. Bei GUI Implementierungen ist es wichtig, den Überblick zu bewahren, da sie sehr schnell sehr große Ausmaße annehmen. Programme mit einer vierstelligen Anzahl von Codezeilen sind keine Seltenheit. Gewöhnen Sie sich also an, alle Funktionalitäten in eine geeignete Methoden zu Codieren und die Methode sinnvoll zu benennen. Anmerkung: Im Code wurde stets mit dem „this“ Schlüsselwort gearbeitet. Dies „Geschmackssache“, da wir „this“ eigentlich nur dann brauchen, wenn wir lokal die gleichen Bezeichner haben als auf Instanzlevel (so wie wir es oft bei Settern haben). Hier habe ich „this“ stets verwendet, um den Blick auf den Zugriff auf Instanzvariablen und Instanzmethoden zu verdeutlichen. In der eigenen Implementierung kann auf das „this“ aber auch verzichet werden. 4 Einfügen eines Elements Elemente werden mit „add“ an das JFrame angehängt. Hierbei können wir verschiedene Elemente platzieren, welche weiter unten näher beschrieben werden. Für unseren ersten Versuch verwenden wir ein „JLabel“, also ein Textelement. Um möglichst übersichtlich zu bleiben ist es eine gängige Praxis, die Platzierung der Elemente in eine eigene Methode auszulagern. Wenn wir den Code nun ausführen, haben wir unser erstes Element in den JFrame eingetragen und wir sehen den Text „hallo“ auf dem Bildschirm. 5 Das Layout Wenn wir unseren Code um ein zweites JLabel erweitern, werden wir eine kleine Überraschung sehen. Unser JFrame zeigt nun nur den letzten Text („welt“) an: Das Problem das wir hier haben ist, dass wir noch kein Layout festgelegt haben. Eine zentrale Herausforderung bei GUI Anwendungen ist, dass wir zur Programmierzeit nicht immer festlegen können, welche Größe der Bildschirm hat und somit was die sichtbare Fläche für unser Fenster ist. Insofern ist es sinnvoll, das Fenster unseres JFrames von der Größe her änderbar zu machen. Dadurch können wir aber nicht mehr unsere Elemente fix auf dem Fenster platzieren. Eine Lösung hierfür bieten die LayoutManager. Seite 3 GUI AnPr 5.1 Der Layout Manager Die (dynamische) Platzierung unserer Elemente übernimmt der Layoutmanager von Java. Bevor wir uns diesen ansehen, müssen wir uns nochmal kurz mit dem JFrame beschäftigen. Dieser JFrame besteht eigentlich aus mehreren Ebenen. Dies ist für die ersten Applikationen zwar erstmal nicht superwichtig, aber wir müssen jetzt das Verhalten der Inhaltsdarstellung ändern und das läuft nun mal auf der Ebene der ContentPane. Dort müssen wir einen LayoutManager platzieren, der sich um die Anordnung der Inhalte kümmert. Der Code hierzu würde wie folgt aussehen: Die große Frage ist nun, was mache ich jetzt mit dem Layoutmanager eigentlich? Hier müssen wir (leider) etwas weiter ausholen. Java bietet mir eine Vielzahl von Layoutmanagern an. Hier eine kurze Auflistung der Swing Layoutmanager: BorderLayout BoxLayout CardLayout GridBagLayout GridLayout GroupLayout SpringLayout Es würde jetzt zu weit führen, alle einzelnen Layouts detailliert zu beschreiben. Unter folgendem Link können die Eigenheiten der Layoutmanager nachgelesen werden: https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html An dieser Stelle werden wir uns auf das GridBagLayout beschränken, da es eine sehr flexible Lösung darstellt, die fast alle Fälle abdeckt. Um das Layout zu verstehen, müssen wir unseren Dialog etwas schematischer sehen. Wir können ihn als eine Ansammlung von Spalten und Zeilen (also „Zellen“) sehen, wobei es auch Inhalte gibt, welche mehrere Felder abdecken können. Seite 4 AnPr GUI 5.2 Die Konfiguration des Layout Managers Wir haben jetzt eine Möglichkeit, die einzelnen Elemente zu benennen, da eine Ortsangabe möglich ist. Jedes dieser Elemente bekommt nun grafische Eigenschaften, die sogenannten „Constraints“. Dort wird festgelegt, wie sich die Zelle im Gesamtverbund verhalten soll. Jeder Zelle kann über eine X- und Y-Koordinate identifiziert werden. Dazu wird jedem Element mitgeteilt, wo im GridBag es sich befindet. Weiterhin können wir noch festlegen, wie viel Prozent der Höhe oder Breite das Element verwenden darf (Standard ist eine Gleichverteilung). Wie gezeigt, setzen wir nun die einzelnen Elemente auf die X- und Y-Position und legen das „Gewicht“ fest. Setzen wir es bspw. auf 0.67, dann „versucht“ das Element 67% der Gesamtbreite für sich zu verwenden (sofern genügend Platz vorhanden ist). Wenn wir nun noch ein zusätzliches Label erstellen wollen, welches beide Spalten der ersten beiden hinweggeht, müssen wir einen neuen Constraint setzen: „gridwidth“. Wir dürfen nicht vergessen, dass der Wert für gridwidth für alle folgenden Elemente wieder zurückgesetzt werden muss (sofern wir für jedes Element das Constraints Objekt wiederverwenden). 6 Die Elemente eines Frames 6.1 JLabel für Text Textelemente werden als „Labels“ bezeichnet. In Swing erfolgt dies über die Klasse „JLabel“. Hier können neben dem Textinhalt auch Textgröße und Textfarbe eingestellt werden: Wenn wir den Code nun ausführen, sehen wir unseren (jetzt roten) Text. Seite 5 GUI AnPr Bevor wir nun weitermachen, noch ein kleiner Hinweis auf die Klasse „Font“. Der hier genutzte Konstruktor für den Font enthält drei Parameter. Gehen wir diese kurz durch: Parameter: Fontname Fontstil Fontgröße Bedeutung: Bezeichner für den Font. Wenn ein spezieller Font angegeben wird, muss er auch auf dem System vorhanden sein! Insofern bieten sich allgemeingültige „logische“ Fonts an (siehe Beispiel). Beispiel: DIALOG: DIALOG_INPUT SANS_SERIF SERIF MONOSPACED Siehe Code oben. Hier wird PLAIN, BOLD und ITALIC (also normal, fett und kursiv) unterschieden. Fontgröße in pts. Siehe Code oben 6.2 JLabel für Bilder Es gibt mehrere Möglichkeiten, ein Bild in ein JFrame einzutragen. Wir konzentrieren uns hier erstmal auf das einfachste Verfahren – ein Bild direkt in ein JLabel einzufügen: Das File kann direkt im Projekt eingetragen werden. Hierzu platzieren wir es direkt im Rootverzeichnis unseres Eclipse Projektes. Alternativ kann man auch ein eigenes Package verwenden, was als eigener Unterordner beim Öffnen angegeben werden muss. Achten Sie hier immer auf Groß/Kleinschreibung, da das Programm ja eventuell auch unter Linux laufen könnte. 6.3 Eingabefelder Eingaben werden mittels „JTextField“ gemacht. Sie können in vielerlei Hinsicht noch konfiguriert werden (Schriftgröße- und Farbe, Hintergrundfarbe etc.), wobei wir uns zuerst „nur“ auf die einfache Funktionsweise konzentrieren. Seite 6 AnPr GUI Der Zugriff auf den eingegebenen Wert können wir über die Methode getText() ermöglicht. Dies werden wir bei dem Kapitel für die Weiterverarbeitung näher betrachten. 6.4 Buttons Mit die wichtigsten Elemente stellen Buttons dar. Sie werden über die Klasse JButton erzeugt. Neben einfachen Textwerten können auch Grafiken eingefügt werden. Hierzu kann mit „setIcon()“ ein ImageIcon eingesetzt werden. Dieser wird wie beim Bild einfügen geladen. Somit hätten wir nun die wichtigsten Elemente besprochen. Doch eine sehr wichtige Sache fehlt noch. Wir müssen es irgendwie schaffen, auf die Ereignisse unserer Elemente (bspw. Button gedrückt, oder Text verändert) im Code reagieren… 7 Signale vom Betriebssystem Unsere Usereingaben, egal ob mit Maus oder Tastatur, werden erstmal vom Betriebssystem in Empfang genommen. Von dort aus geht das Signal weiter an die einzelnen laufenden Programme, unter anderem auch die JVM (Java Virtual Machine). Diese könnte nun das Signal weiterleiten. Die große Frage ist aber, an wen eigentlich? Dies muss der Programmierer erstmal festlegen, indem er sagt, welche Methoden die JVM im Falle eines Klicks aufrufen soll. Und schon sind wir beim zweiten Problem angekommen. Woher soll die JVM wissen, dass es eine Klasse von uns gibt namens „MyJFrame“ und dann dort auch noch eine Methode aufrufen, die wir geschrieben haben? Zum Zeitpunkt der Programmierung der JVM gab es unsere Klasse ja noch gar nicht! Sehen wir uns hierzu das folgende Bild mal genauer an: Seite 7 GUI AnPr Das Geheimnis zu diesem Problem heißt „Interface“. Ein Interface ist ein eigenes Programmierkonstrukt, welches lediglich die Signatur von Methoden beinhaltet (ähnlich wie abstrakte Methoden). Sie stellt eine Art „Vertrag“ dar, in dem steht, welche Methoden zu erwarten sind. Ein Interface kann man „implementieren“, was nichts anderes bedeutet, dass die implementierende Klasse alle Methoden dieses „Vertrags“ auch implementiert. In unserem Fall ist das Interface „ActionListener“ und die zu implementierende Methode heißt „actionPerformed(ActionEvent arg0)“. Wenn ich der JVM jetzt mitteile, dass meine Klasse JFrame dieses Interface implementiert hat, so muss die JVM nun davon ausgehen, dass auch diese Methode existiert und kann sie somit aufrufen. Sehen wir uns den nebenstehenden Code nochmal Schritt für Schritt an. So – nun haben wir es fast geschafft! das Einzige Problem ist nun noch, dass wir bei der Auswertung des Ereignisses noch nicht eindeutig feststellen können, wer das Event eigentlich ausgelöst hat. Wenn wir bspw. in unserem JFrame zwei Buttons platziert haben, möchten wir natürlich wissen, von wem das Event stammt. Hierzu müssen wir noch eine Kleinigkeit anpassen: Wir können mit der Methode „getSource()“ der Klasse „ActionEvent“ nun herausfinden, wer das Event eigentlich ausgelöst hat. Hierzu brauchen wir aber stets eine Referenz auf das Button Objekt. Aus diesem Grunde dürfen wir es nicht als lokale Variable, sondern als Instanzvariable deklarieren. Achten sie darauf, dass der Variablenname nun so aussagekräftig ist, dass wir immer wissen, um welchen Button es sich hier handelt! Wichtig: Grundsätzlich ist es notwendig, alle GUI Elemente mit denen wir noch weiterarbeiten müssen, in Instanzvariablen abzulegen. Dies gilt für alle Elemente die Events auslösen, aber auch für Elemente, deren Eigenschaften wir während der Laufzeit verarbeiten oder anpassen wollen (wie bspw. Text ändern, Text auslesen, Grafiken anpassen usw.). Seite 8 AnPr 8 GUI Lizenz Diese(s) Werk bzw. Inhalt von Maik Aicher (www.codeconcert.de) steht unter einer Creative Commons Namensnennung - Nicht-kommerziell - Weitergabe unter gleichen Bedingungen 3.0 Unported Lizenz. Seite 9