nullpointer.at

HOWTO: GUI mit Swing – Teil 2: Prototyp – Der StandbyScreen


Willkommen zurück bei meiner Reihe GUI programmieren mit Java Swing. Im vorigen Teil, Teil1: Einführung, haben wir eine erste BeispielGUI kennengelernt. In diesem Teil werden wir uns nun intensiver mit dem Aufbau einer GUI beschäftigen. Dafür setzen wir aus der Aufgabenstellung des ersten Teils die ersten Oberflächen-Masken um.

Nochmal zur Erinnerung die bereits umgesetzten und noch geplanten Themen:

Das eigentliche Fenster

Nun, bevor es ans platzieren der UI Elemente geht ist es wie schon im HelloSwing Beispiel notwendig ein Fenster – ein JFrame – zu erzeugen. Dieses dient als Wurzelelement der GUI, auf das jegliches weitere Element gesetzt werden kann/muss. Wurzelelemente werden auch als Top-Level Container bezeichnet. Top-Level Container haben ein paar besondere Eigenschaften, z.B. hat jeder eine ContentPane. Dies ist die Fläche auf die alle anderen Elemente aufgesetzt werden können. Außerdem kann dem Top-Level Container eine Menüleiste zugewiesen werden. Die Aufteilung sieht man an der nachfolgenden Grafik.

Dabei liefert uns das JFrame schon ein paar grafische Eigenheiten frei Haus: die Möglichkeit ein Menü einzubinden und die üblichen 3 Buttons zum Minimieren, Maximieren und Schließen des Fensters, samt deren Funktionalität. Natürlich hat auch das JFrame eine ContentPane, auf dem jedes beliebige Widget (Button, Label, Textfeld, …) verankert werden kann.

Leeres JFrameJFrame mit Menu und befüllter ContentPane

Im linken Bild sieht man ein leeres JFrame, im rechten Bild wurde dem JFrame eine Menübar (grün eingefärbter Bereich) hinzugefügt. Zur Visualisierung der ContentPane wurde dort ein Element mit fixer Höhe eingefügt und ebenfalls eingefärbt (roter Bereich). In diesem Bereich erfolgt durch Zuweisung weiterer Elemente die Darstellung der GUI. Dies passiert normalerweise in mehreren Schichten, hat also eine gewisse Ähnlichkeit mit einer Zwiebel. Was damit gemeint ist, wird anhand des Prototypen, den wir entwickeln, mit intensiverer Nutzung der Layouts noch aufgezeigt.

Zuerst jedoch betrachten wir den Code der zum erzeugen eines JFrames notwendig ist.

		JFrame frame = new JFrame("Hello Swing");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		frame.pack();
		frame.setVisible(true);

Wir erzeugen ein JFrame Objekt, und setzen die Sichtbarkeit auf true. Vergessen sollte man auch die DefaultCloseOperation nicht, diese ist notwendig um das Programm zu schließen, vergisst man sie wird zwar das angezeigte Fenster beendet, nicht aber das eigentliche Programm. Nicht außer acht zu lassen ist natürlich wieder der Event-Dispatching Thread, der für die Darstellung der GUI verantwortlich ist. Alles was an der GUI optisch verändert wird passiert hier drin. Es ist ein Single Thread Mechanismus der dazu dient Fehlerfälle zu vermeiden die durch das Verändern der GUI aus anderen Threads heraus entstehen kann. Für unseren Prototypen noch nicht essentiel, aber sozusagen die Basis für eine möglichst fehlerfreie Umsetzung einer GUI.

Prototyp Mechanismus

Damit wir alle 3 Zustände im Prototypen erreichen können hätten wir verschiedene Wege gehen können, z.B. einzelne Testklassen. Hier habe ich mich dafür entschieden der Main Klasse einen Parameter mitzugeben um zu bestimmen Welche Ansicht dargestellt wird. Im Source des Prototypen finden wir die bereits bekannten Elemente wieder, den Event-Dispatching Thread, das JFrame das erzeugt wird. Im Gegensatz zu unserem Hello Swing Beispiel aus dem ersten Teil dieser Serie haben wir aber nun das Setzen des Sichtbar-Flags in eine weitere Methode ausgelagert, die erst zum Schluss aufgerufen wird. Dazwischen haben wir 3 fette TODOs prangern. An diesen Stellen wollen wir später noch Code einfügen, der die speziellen Bedürfnisse der einzelnen Zustände der Anwendung umsetzen wird.

package at.nullpointer.guiprototype;

import javax.swing.JFrame;

public class Prototype {

	static int state = 0;

	public static JFrame createGUI() {
		// Ein Fenster erzeugen
		JFrame frame = new JFrame("StoreCardReader");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		frame.pack();
		frame.setVisible(true);

		return frame;
	}

	public static JFrame showGUI(JFrame frame){
		// Das Fenster auf sichtbar setzen
		frame.pack();
		frame.setVisible(true);

		return frame;
	}

	/**
	 * Ein Simpler Mechanismus entscheidet welche Oberfläche der Prototyp
	 * darstellen soll. Hier wurde bewusst auf die Behandlung von Exceptions
	 * und ähnliches zwecks Vereinfachung verzichtet.
	 *
	 * @param args 1... Wartescreen, 2... Datenanzeige, 3... Dateneingabe
	 */
	public static void main(String[] args) {

		if(args != null && args.length != 1) {
			System.err.println("Error selecting JFrame");
			System.exit(0);
		}

		state = Integer.parseInt(args[0]);

		// Zwecks Threadsicherheit wird die Methode die die
		// GUI anzeigt vom event-dispatching Thread gestartet
		javax.swing.SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				JFrame gui = createGUI();
				switch(state){
				case 1: // WarteScreen
					//TODO fillFrameWithPendingContent(gui);
					break;
				case 2: // Daten anzeigen
					//TODO
					break;
				case 3: // Daten eingeben
					//TODO
					break;
				default:

				}
				showGUI(gui);
			}
		});
	}

}

Nun sollte ein leeres Fenster beim Start der Anwendung angezeigt werden, wenn man sie mit einem der drei Parameter startet. Die entsprechende Zahl entspricht dem Parameter:

  1. Wartescreen
  2. Datenansicht
  3. Dateneingabe

Die Menüleiste

Die Klassiker unter den Menüleisten sind die Punkte “Datei”&”Beenden” und “Hilfe” oder “?”. Dies sind GUI Elemente die wir auf allen drei Ansichten darstellen wollen. Daher werden wir Zeile 48 um den Aufruf einer neuen Methode erweitern, die dem JFrame eine GUI zuweist:

				JFrame gui = addMenu(createGUI());

Die entsprechende Methode umfasst die Erzeugung eines Menübalken und das Aufsetzen dieser Leiste auf den Top-Level Container. Sie muss jedoch noch der Prototype Klasse hinzugefügt werden.

			public JFrame addMenu(JFrame frame) {
				JMenuBar menubar = new PrototypeMenuBar();
				frame.setJMenuBar(menubar);
				return frame;
			}

Eine MenuBar zu erzeugen ist ebenfalls nicht sonderlich aufwendig. Um jedoch den Code schlank zu halten und dem Paradigma “one class one function” zu folgen lassen wir das Menü in der Klasse PrototypeMenuBar erzeugen. In dieser Klasse werden zwei JMenu Objekte erzeugt, die die Punkte direkt in der Menüleiste darstellen. Einem dieser Objekte fügen wir noch ein JMenuItem hinzu, welches einen Unterpunkt eines JMenus repräsentiert. Diesen könnte man nun allen Events zuordnen und mit Funktionalität versehen. Dies ist jedoch erst in einem späteren Teil dieses Howtos vorgesehen.

package at.nullpointer.guiprototype.menu;

import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;

import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.KeyStroke;

public class PrototypeMenuBar extends JMenuBar {
	JMenu file, help;
	JMenuItem close;

	public PrototypeMenuBar() {

		super();
		// Das Menü mit dem namen Datei erstellen
		file = new JMenu("Datei");
		// Den Buchstaben D unterstreichen
		file.setMnemonic(KeyEvent.VK_D);
		// Eine Beschreibung anfügen
		file.getAccessibleContext().setAccessibleDescription(
				"Hier können Sie das Programm beenden");
		// Der JMenuBar hinzufügen, sonst wird es nicht angezeigt
		this.add(file);

		// Ein Menüitem erzeugen
		close = new JMenuItem("Beenden", KeyEvent.VK_B);
		// Darstellung des Tastenkürzels
		close.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B,
				ActionEvent.ALT_MASK));
		close.getAccessibleContext().setAccessibleDescription(
				"Beendet das Programm.");
		file.add(close);

		// Das Help Menü erstellen
		help = new JMenu("?");
		help.setMnemonic(KeyEvent.VK_HELP);
		help.getAccessibleContext().setAccessibleDescription(
				"Nicht implementiert!");
		this.add(help);

	}
}

Das JLabel

Ein Entwurf für einen Standbyscreen für eine Java Swing OberflächeAls nächsten Schritt wollen wir die erste Oberfläche mit Information für den User befüllen. Wir beginnen mit dem Wartescreen, da dieser am einfachsten zu realisieren ist und starke paralellen zu unserem HelloSwing Beispiel aufweist. Wie auf dem Mockup zu sehen ist, ist hierfür nur ein Text notwendig der dem User den aktuellen Status mitteilt. Hierfür bedienen wir uns dem UIElement JLabel, welches zwar ein Container für Text ist, zusätzlich aber noch ein Icon oder nur ein Icon beinhalten könnte.

Um nun entsprechendes Label in unsere Anwendung einzubinden, erledigen wir das TODO im switch zur Anzeige bei Case 1 der Anwendung

//TODO fillFrameWithPendingContent(gui);

wird ausgebessert auf:

fillFrameWithPendingContent(gui);

Damit dies auch funktioniert müssen wir diese Methode natürlich auch noch implementieren:

			private JFrame fillFrameWithPendingContent(JFrame frame){
				JLabel standbyJLabel = new StandbyJLabel();
				frame.getContentPane().add(standbyJLabel, BorderLayout.CENTER);
				return frame;
			}

Hierbei gehen wir nach dem selben Schema vor, wie wir es schon bei dem Menü getan haben, wir erstellen uns ein neues Objekt und fügen es dem Frame hinzu. Das neue Objekt ist wiederum eine Erweiterung des normalen JLabels, das wir um einen Construktor erweitert haben, in dem wir das Label mit den von uns gewünschten Werten ausstatten.

package at.nullpointer.guiprototype.standby;

import java.awt.Color;

import javax.swing.JLabel;

public class StandbyJLabel extends JLabel {

	final private String caption = "Bitte ziehen Sie eine Kundenkarte\n über den Scanner";

	public StandbyJLabel() {

		super();
		// Hintergrundfarbe
		this.setBackground(Color.RED);
		// Schriftfarbe
		this.setForeground(Color.YELLOW);
		// undurchsichtig (notwendig um einen Hintergrund zu zeichnen)
		this.setOpaque(true);
		// Text
		this.setText(caption);
		// Position des Textes innerhalb des Labels
		this.setHorizontalAlignment(CENTER);
		this.setVerticalAlignment(CENTER);
	}

}

Starten wir nun das Programm bekommen wir folgendes Bild zu sehen:

Prototyp StandbyScreen packed

Es ist geschafft, der Text wird angezeigt. Leider ist noch nicht alles zu unserer Zufriedenheit. Die Anwendung ist noch etwas zu schmal geraten. Dies ändern wir indem wir in der Klasse Prototype folgende Änderungen vornehmen:

in den Methoden createGUI die Zeile frame.pack(); durch frame.setBounds(50,50,640,480); ersetzen, und

in der Methode vor showGUI die Zeile frame.pack(); entfernen

Schon ist unsere Anwendung nicht mehr zu so einem schmalen Dasein verdonnert:

Prototyp StandbyScreen

Hier sieht man jedoch auch deutlich wo eines der Mankos eines JLabels liegt. In der Präsentation seines Inhaltes. Wie man in der Klasse StandybyJLabel vielleicht bemerkt hat ist im String caption ein Zeilenumbruch eingebaut. Dieser wird bei der Darstellung des Inhalts gänzlich ignoriert. Verkleinert man die Fenstergröße unseres Prototyps ergibt sich außerdem, dass das Label hier ebenfalls keinen Zeilenumbruch beherrscht.

JLabel beherrscht keinen Zeilenumbruch

Hierfür bedient man sich anderen gängigen Praktiken, denn das JLabel kann simples html intepretieren. Wir ersetzen also den caption String durch folgenden Text:

„<html><center><p>Bitte ziehen Sie eine Kundenkarte<br/> über den Scanner</p></center></html>“

Und schon sollte unser Standbyscreen wie gewüscht aussehen, und auch skalierbar sein.

Prototyp in richtiger Größe Prototyp verkleinert mit Textumbruch

BorderLayout

Als wir das Label dem StandbyScreen zugewiesen haben haben wir einen noch nicht erklärten Parameter genutzt:

BorderLayout.CENTER

Hierbei haben wir die erste Berührung gemacht mit den Möglichkeiten wie in Swing Komponenten auf der ContentPane dargestellt werden. Zur Anordnung der Komponenten kann man dieser nämlich verschiedene Layouts zuweisen. Hier einige Beispiele:

  • BorderLayout
  • Flowlayout
  • Boxlayout
  • GridBagLayout
  • etc

Hier möchte ich kurz die Eigenheiten des BorderLayouts aufzeigen, während wir in weiterer Folge, in Teil 3 der Serie, bei der Gestaltung der Datenanzeige das BoxLayout kennen lernen werden. Eine ContentPane die über das BorderLayout organisiert wird stellt folgende 5 Bereiche zum Hinzufügen von Elementen bereit:

  • PAGE_START
  • LINE_START
  • CENTER
  • LINE_END
  • PAGE_END

deren Aufteilung in dieser Demoanwendung gut zu erkennen ist. Hierfür wurde jedem dieser Bereiche ein Button mit entsprechender Caption hinzugefügt.

BoderLayout DemoBild

Dabei muss natürlich wie in unserem Beispiel nicht jeder Bereich mit einem Element befüllt sein. Der CENTER Abschnitt besitzt nämlich noch eine besondere Eigenschaft: Er übernimmt immer die gesamte Fläche der ContentPane die nicht von einem der Abschnitte beansprucht wird. Somit eignet sich dieses Layout hervorragend um mehrere Bereiche einer Anwendung zu strukturieren. LINE_START könnte zum Beispiel ein Menü mit einer vertikalen Symbolleiste beinhalten, PAGE_END einen Hinweistext und PAGE_START essentielle Informationen für den CENTER Bereich der immer sichtbar sein sollte.

Vorschau & Lernaufgabe

Im nächsten Teil werden wir die anderen GUI Elemente designen und lernen das BoxLayout kennen, sowie das JTextField und den JButton. Bis dahin hier noch der gesamte Source zum Lernprojekt bisher. Als als kleine Lernaufgabe könntet ihr ja noch einen Footer in unserer Anwendung hinzufügen, in dem ihr euch als Entwickler der Anwendung rühmt. Das ganze soll natürlich rechtsbündig zu lesen sein, und ebenfalls in gelb auf roter Schrift. Wenn ihr noch den Text des StandbyJLabels vergrößern könnt, habt ihr den Source für Teil 3 fit gemacht.

Ähnliche Artikel:


Beitrag veröffentlicht

in

,

von

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

*