In folgendem Beispiel werden zwei Fenster unterschiedlicher Größe an unterschiedlicher Position erzeugt. Als Konstruktorparameter werden die gewünschten Fensterüberschriften angegeben. Ein Fenster kann über seinen Dienst setVisible(false) bei Bedarf auch temporär ausgeblendet werden.
import java.awt.*; // awt-Bibliotheken importieren import javax.swing.*; // swing-Bibliotheken importieren public class DoppelFenster { public static void main (String[] args) { JFrame frame1 = new JFrame("JFrame 1"); frame1.setLocation(200, 200); frame1.setSize(300,200); frame1.setVisible(true); JFrame frame2 = new JFrame("JFrame 2"); frame2.setLocation(550, 200); frame2.setSize(300,400); frame2.setVisible(true); } }
Die Fenster sind bislang noch völlig leer und alle internen Strukturen
müssten bei einem Vorgehen wie oben gezeigt in der jeweiligen Methode
(hier main)
erzeugt werden, was unpraktisch und fehleranfällig ist.
Es ist daher besser (und üblich), für jedes Fenster, das man benötigt,
eine eigene Klasse aus JFrame abzuleiten, die alle
fensterspezifischen Dinge beinhaltet.
Handelt es sich dabei um das Hauptfenster einer Applikation, kann in dieser Klasse
aus Gründen der Einfachheit auch die main-Methode untergebracht werden,
in der dann das Fenster in einer Variablen des entsprechenden Typs instanziiert wird.
Siehe dazu das folgende Beispiel:
import java.awt.*; // awt-Bibliotheken importieren import javax.swing.*; // swing-Bibliotheken importieren public class BasisFenster extends JFrame { public BasisFenster(String title) { super(title); // Oberklassenkonstruktor aufrufen this.setSize(300,400); // die Angabe "this" ist optional, da this.setLocation(200, 200); // die Klasse selbst ein JFrame ist this.setVisible(true); } public static void main (String[] args) { BasisFenster bf = new BasisFenster("BasisFenster"); } }
Im nächsten Schritt werden wir nun unseren leeren Fensterrahmen um verschiedene Strukturen / Inhalte erweitern.
Die verfügbare Fläche des Fensters wird als ContentPane bezeichnet. Um auf sie zugreifen zu können, stellt die Klasse JFrame die Methode getContentPane() zur Verfügung. Die ContentPane selbst stellt ebenfalls verschiedene Dienste wie z.B. add(...) und setLayout(...) bereit.
Mit der AnweisunggetContentPane().setLayout( new FlowLayout() )
muss zunächst eine Instanz des gewünschten Layout-Managers der ContentPane zugewiesen werden, bevor danach mit der AnweisunggetContentPane().add(...)
Objektinstanzen verschiedener Oberflächenelemente der ContentPane zugefügt werden können.Im folgenden Beispiel werden zwei Schaltflächen (JButton) erzeugt und jeweils über einen Konstruktorparameter mit "Blau" bzw. "Rot" beschriftet.
import java.awt.*; // awt-Bibliotheken importieren import javax.swing.*; // swing-Bibliotheken importieren public class ButtonDemo extends JFrame { JButton button1; // Variable für ein ButtonObjekt JButton button2; // Variable für ein ButtonObjekt public ButtonDemo(String title) { super(title); // Oberklassenkonstruktor aufrufen button1 = new JButton("Blau"); // Buttons instanziieren button2 = new JButton("Rot"); getContentPane().setLayout(new FlowLayout()); // Layout setzen getContentPane().add(button1); // Button einfügen getContentPane().add(button2); // Button einfügen setSize(300,400); setLocation(200, 200); setVisible(true); } public static void main (String[] args) { ButtonDemo demo = new ButtonDemo("ButtonDemo"); } }
Anders als in vielen anderen Programmiersprachen wird in Java die Anordnung bzw. exakte Position von GUI-Elementen nicht statisch programmiert, sondern von speziellen Klassen, den Layout-Managern, dynamisch verwaltet. In unserer GUI verwenden wir das FlowLayout, das nur für sehr einfache GUI's (so wie die unsere) geeignet ist. Es ordnet die einzelnen GUI-Elemente in der Reihenfolge, in der sie per .add(...) in die ContentPane eingefügt werden, einfach horizontal von links nach rechts im Fenster an, solange ausreichend Platz dafür vorhanden ist.
Möchte man komplexere GUI-Strukturen erzeugen, kann z.B. das GridBagLayout verwendet werden.
Verändere mit Hilfe der Maus die Fensterbreite und beobachte
die Buttons.
Was stellst du fest?
Ändere die Beschriftung der Buttons von
Rot / Blau auf Rotf\u00e4rber / Blauf\u00e4rber.
Möchte man deutsche Umlaute verwenden, sollte deren Unicode-Wert
angegeben werden, da sie sonst nicht immer korrekt dargestellt werden.
Wie wirkt sich diese Änderung auf die Buttons aus?
Um die Größe von Buttons nicht von deren Beschriftung abhängig zu machen, bietet die Klasse verschiedene Möglichkeiten, so. u.a. die Methode setPreferredSize(...). Informiere dich in der Online-Dokumentation über deren Verwendung und ändere die Größe eines Buttons auf eine Breite von 140px und eine Höhe von 40px.
Unsere GUI besteht bisher nur aus der View-Komponente des MVC-Modells (s.o.), d.h. ausschliesslich aus einer funktionslosen graphischen Oberfläche. Um einen Controller zu realisieren bietet Java vielfältige Möglichkeiten, Ereignisse (events) auch in der Anwendung zu erzeugen und darauf zu reagieren (listener). Wir werden uns jedoch auf eine rudimentäre Ereignisbehandlung beschränken, die nur auf Fensterereignisse (z.B. Mausklicks) reagiert, da dies für unsere Zwecke ausreichend ist und sie mit einem minimalen Model (entspricht dem eigentlichen Anwendungsprogramm, hier nur das Umschalten des Hintergrundes von rot auf blau und umgekehrt) ergänzen.
Dazu müssen als erstes die entsprechenden AWT-Bibliotheken importiert
werden (s.u.).
Da in Java nur eine Einfachvererbung möglich ist,
wird der entsprechende Ereignisbehandler (auch Beobachter genannt) durch
die Anweisung
implements ActionListener
in Form einer Schnittstelle (interface) im Klassenkopf dazugebunden. Der ActionListener ist eine abstrakte Klasse, deren Methode actionPerformed(...) überschrieben bzw. implementiert werden muss. Sie wird automatisch ausgeführt, sobald in einem überwachten Element ein Ereignis auftritt und erhält einen ActionEvent als Parameter, der zur Unterscheidung verschiedener Ereignisquellen (hier Buttons) ausgewertet werden kann (s.u.).
import java.awt.*; import javax.swing.*; import java.awt.event.*; // Event-Bibliothek importieren public class ButtonDemo extends JFrame implements ActionListener { JButton button1 ; // Variable für ein ButtonObjekt JButton button2 ; // Variable für ein ButtonObjekt public ButtonDemo(String title) { super(title); button1 = new JButton("Blauf\u00e4rber"); button1.addActionListener(this); button1.setActionCommand("blue"); button2 = new JButton("Rotf\u00e4rber"); button2.addActionListener(this); button2.setActionCommand("red"); getContentPane().setLayout(new FlowLayout()); // Layout setzen getContentPane().add(button1); // Button einfügen getContentPane().add(button2); // Button einfügen setSize(300, 400); setLocation(200, 200); setVisible(true); } public void actionPerformed( ActionEvent ae) { String action = ae.getActionCommand(); if ( action.equals( "blue" ) ) getContentPane().setBackground( Color.blue ); else getContentPane().setBackground( Color.red ); repaint(); } public static void main ( String[] args ) { ButtonDemo demo = new ButtonDemo("ButtonDemo"); } }
Damit Ereignisse in einem Oberflächenelement, wie z.B. ein Mausklick auf einen Button oder das Drücken der Eingabetaste in einem Textfeld, vom ActionListener erkannt und behandelt werden können, muß ein Bezug zwischen beiden hergestellt werden. Dies erfolgt über die Anweisung
.addActionListener(this)
an dem entsprechenden Element (s.o.), wobei der Parameter this auf den ActionListener dieser Klasse verweist.
Weiterhin sollte das jeweilige ActionCommand explizit gesetzt werden (hier z.B. durch button1.setActionCommand("blue"), s.o), um von der Beschriftung und verwendeten Sprache unabhängig zu sein. Macht man dies nämlich nicht, wird automatisch die Beschriftung des Button dafür verwendet. Dies ist problematisch, da jede kleine Änderung hierin zu einem nicht mehr funktionierenden Programm führen würde.
Was jetzt noch fehlt für eine vollständige GUI sind Möglichkeiten für die Eingabe und Ausgabe von Informationen. Dazu werden wir nun
Die Eingabe beinhaltet ein einzeiliges Textfeld, die Ausgabe besteht aus einem sechszeiligen Textbereich, der automatisch mit Scrollbalken versehen wird, wenn der Inhalt dessen Größe überschreitet. Inhalte können mit .getText() ausgelesen bzw. mit .setText(...) dem ganzen Bereich zugewiesen und mit .append(...) am Ende angehängt werden.
import java.awt.*; import java.awt.event.*; import javax.swing.*; public class TextDemo extends JFrame implements ActionListener { JButton berechneKnopf; // Variable für ein ButtonObjekt JButton beendeKnopf; // Variable für ein ButtonObjekt JTextField eingabeFeld; // einzeiliges Textfeld JLabel eingabeFeldLabel; JTextArea ausgabeFeld; // mehrzeiliges Textfeld JLabel ausgabeFeldLabel; public TextDemo(String title) { super(title); eingabeFeld = new JTextField(15); // 15 Spalten breit eingabeFeldLabel = new JLabel("Eingabe"); berechneKnopf = new JButton("Kopieren"); berechneKnopf.addActionListener( this ); berechneKnopf.setActionCommand("copy"); ausgabeFeld = new JTextArea(6, 15); // 6 Zeilen hoch, ausgabeFeldLabel = new JLabel("Ausgabe"); // 15 Spalten breit beendeKnopf = new JButton("Beenden"); beendeKnopf.addActionListener( this ); beendeKnopf.setActionCommand("exit"); getContentPane().setLayout(new FlowLayout()); getContentPane().add( eingabeFeldLabel ); getContentPane().add( eingabeFeld ); getContentPane().add( ausgabeFeldLabel ); getContentPane().add( new JScrollPane(ausgabeFeld) );// scrollbar getContentPane().add( berechneKnopf ); getContentPane().add( beendeKnopf ); setSize( 210, 250 ); // Fenstergröße setzen setResizable(false); // Fenstergröße fixieren setLocation(200, 200); // Fensterposition setzen this.setVisible(true); } public void actionPerformed( ActionEvent ae) // Controller { if ( ae.getActionCommand().equals( "copy" ) ) { ausgabeFeld.append(eingabeFeld.getText() + "\r\n"); // Model eingabeFeld.setText(""); } else System.exit(0); } public static void main ( String[] args ) { // lokale Instanz der Klasse erzeugen TextDemo tc = new TextDemo("Text Demo"); } }
Das Model besteht wiederum nur aus zwei Codezeilen, in denen der Inhalt
Eingabefeldes erst in den Ausgabebereich kopiert und dann gelöscht wird.
In einem komplexeren Programm mit mehreren Klassen würden hier die
öffentlichen Dienste / Methoden dieser Klassen aufgerufen / eingebunden.
Bemerkenswert ist zum Schluss noch die Anweisung setResizable(false), mit der die Fenstergröße fixiert wird, sodass es nicht mit der Maus vergrössert oder verkleinert werden kann, wobei wegen des FlowLayout die Anordnung aller Elemente "verrutschen" würde.
© K. Milzner, 2019 |