HOWTO: GUI mit Swing – Teil 3: Prototyp – Die Datenview

Wie entwickelt man eigentlich eine GUI mit Java Swing? Eine mehrteilige Einführung in dieses Thema. Teil 3 erweitert den Prototypen um Eingabefelder u.a.

Willkommen zurück bei der Reihe GUI programmieren mit Java Swing. In den vorigen Teilen haben wir uns ein HelloSwing-Beispiel angesehen, und mit diesem anhand einer konkreten Problemstellung einen ersten Prototypen entworfen. In diesem Teil werden wir diesen Prototypen nun um eine weitere Sicht, der View-Sicht erweitern und uns etwas mehr mit verschiedenen Schichten des JFrames sowie den Layouts der Komponenten auseinandersetzen. Um auch noch die Edit-Schicht umzusetzen wird dann noch in einem Beispiel das JTextField sowei der JButton vorgestellt, ehe man sich zum Gestigen des hier erworbenen Wissens selbst ans Umsetzen der Edit-View setzen kann. Diese wird in den nächsten Teilen zur Umsetzung der Funktionalität erforderlich sein.

Zur Erinnerung an die bereits besprochenen und noch geplanten Themen hier noch eine Auflistung selbiger:

      Aufgabe aus Teil 2

      Aufgabe in Teil 2 war es den Prototypen um einen Zusätzlichen JLabel zu erweitern sowie die Schriftgröße unseres StandByJLabels zu vergrößern. Hier kurz der Lösungsweg skizziert:

      Zusätzliches Label:

      Um ein zusätzliches Label zu erzeugen kopieren wir unsere StandbyJLabel Klasse und benennen sie beispielsweise FooterLabel. Damit sie am richtigen Fleck angezeigt wird, und das egal in welchem State der Prototyp aufgerufen wird fügen wir das neue JLabel in der createGUI Methode dem JFrame hinzu:

      	public static JFrame createGUI() {
      		// ... restlicher Code ...
      
      		frame.add(new FooterJLabel(), BorderLayout.PAGE_END);
      
      		return frame;
      	}

      Das JLabel wurde nun dem PAGE_END Bereichs des BorderLayouts zugewiesen und erscheint nun unten im JFrame wenn wir die Anwendung starten. Wir müssen noch die Caption des FooterLabels auf uns selbst oder unsere Domain z.B. „by nullpointer.at“ ändern. Damit der Text rechts unten im JFrame erscheint, verändern wir die Horizontale Ausrichtung von CENTER auf RIGHT

      		this.setHorizontalAlignment(RIGHT);
      Schriftgröße:

      Im Konstruktor der Klasse StandByJLabel wurden folgende Zeilen hinzugefügt:

      		Font font = new Font("Dialog", Font.PLAIN, 24);
      		this.setFont(font);

      Diese erzeugen zuerst ein Fontobjekt, bei dem die Schriftgröße neu gesetzt wird, und dannach wiesen wir sie dem Label zu.

      Habt ihr alles richtig gemacht sollte der StandbyScreen in etwa so aussehen wie in der folgenden Abbildung.

      Fertiger StandbyScreen inkl. Hausübung Unfertige View Sicht

      Startet ihr die Anwendung mit dem Parameter 2 um die Datenansicht zu starten seht ihr dass der Footer hier ebenfalls angezeigt wird, der Rest der Anwendung jedoch noch grau ist und somit nicht befüllt. Dem wenden wir uns jetzt zu.

      Die View-Sicht

      Ein Entwurf einer Kundendatenanzeige für eine Java Swing OberflächeDie View Sicht strotzt nur so von GUI-Elementen. Zum einen haben wir eine Statusanzeige, die immer oben im Bild zu sehen sein wird. Hierfür werden wir erneut auf das BorderLayout zurückgreifen,welches wir mit einem JLabel füttern.

      Danns sind da noch eine Menge Daten über den Kunden die es anzuzeigen gibt.

      An der Aufteilung im Mockup können wir erkennen, dass das BorderLayout hierbei vermutlich an seine Grenzen stößt, weshalb wir das BoxLayout in diesem Artikel behandeln. Ansonsten benötigen wir zur Darstellung der Daten JLabel über JLabel, noch nicht viel Neues in Sicht.

      Damit die Sache mit den neuen Layouts unseren Footer nicht zerstört werden wir dabei erstmals eine weitere Schicht einführen, die unsere Darstellungstypen von einander trennen.

      Abschließend wird dann noch das JTextField vorgestellt, mit dem man man den User zu Eingaben auffordern kann. In einem kleinen Fallbeispiel werden wir ebenfalls den JButton kurz erläutern und als Aufgabe dieses Teils gilt es die Edit-Sicht selbst nachzubauen, was mit hier erworbenen Wissen nicht allzu schwer fallen sollte.

      Zwei JLabel ein BorderLayout.CENTER

      Wenn wir die View nun umsetzen wollen sehen wir dass wir mehr als nur ein Label benötigen werden. Ganze 12 Stück für die jeweiligen Feldbeschriftungen und die Darstellung des Inhalts sind notwendig. Wie in der vorigen Lektion beschrieben beinhaltet das Borderlayout eine Fläche in der Mitte zur Darstellung des Content und eine jeweils seitlich. Was passiert wenn wir da mehr als ein Label reinstecken? Mehr als ein Label in einen Bereich von BorderLayoutTestweise erstellen wir mal ein JFrame und packen 3 Labels rein. Das sollte mittlerweile kein Problem darstellen, weshalb ich hier auf den Source verzichte. Es wurde Label 1 in Gelb dem linken Bereich, dem LINE_START zugewiesen, Label 2 in Rot und Label 3 in Grün dem Bereich in der Mitte, BorderLayout.Center. Und tada Label 3 hat Label 2 überschrieben. Wir müssen also andere Wege gehen wenn wir die 12 Labels anzeigen wollen. Zum Beispiel mit einem anderen Layout:

      Das BoxLayout

      Um dem oben genannten Problem Herr zu werden bietet sich bei der Struktur unsere Ausgaben das Boxlayout an. Dieses offeriert zwei Möglichkeiten die Elemente, die es beherbergt zu verwalten. Entweder es teilt den verfügbaren Bereich horizontal oder vertikal auf alle Elemente auf.

      BoxLayout Typen

      Dabei wird der verfügbare Bereich nicht einfach nur auf alle Elemente verteilt, sondern es werden diese nach Reihenfolge des Hinzufügens von Links nach Rechts bzw. von Oben nach Unten angeordnet und verbrauchen dabei genau soviel Platz wie es das Element notwendig macht.

      Zugewiesen wird das BoxLayout auf folgende Weise:

      pane.setLayout(new BoxLayout(pane,BoxLayout.PAGE_AXIS));

      BoxLayoutDemo resizedBoxLayoutDemo Das Ergebnis ist hierbei auch mit einer kleinen Beispielanwendung verdeutlicht. Verändern wir die Fenstergröße,nutzten die Elemente weiterhin nur den von ihnen ursprünglich belegten Platz. Um nun auch im BoxLayout designtechnische Möglichkeiten zu haben gibt es folgende 3 Möglichkeiten:

      rigid area

      Diese verwendet man wenn man zwischen zwei Elementen einen fixen Abstand einbauen möchte.

              pane.add(label1);
              pane.add(Box.createRigidArea(new Dimension(5,5)));
              pane.add(label2);

      Dieser Code erzeugt zwischen Label 1 und Label 2 einen 5 Pixel breiten oder 5 Pixel hohen Leerbereich, je nach Ausrichtung des BoxLayouts.

      rigid area zwischen den JLabel

      glue

      Mithilfe des Glues nutzt man den Platz der zur Verfügung steht komplett aus. Alles was vor dem Glue hinzugefügt wurde “klebt” an der einen Seite, alles was dannach hinzugefügt wurde “klebt” an der anderen. Werden mehrere Glues hinzugefügt wird der verfügbare Platz zwischen diesen zu gleichen Teilen aufgeteilt.

              pane.add(label1);
              pane.add(Box.createGlue());
              pane.add(label2);
              pane.add(Box.createGlue());
              pane.add(label3);

      image

      custom Box.Filler

      Der Custom Box Filler ermöglicht es einem eine Componente einzufügen die einen minimalen, einen bevorzugten und einen maximal definierten Platz ausfüllt.

              pane.add(label1);
              Dimension minSize = new Dimension(5, 20);
              Dimension prefSize = new Dimension(50, 25);
              Dimension maxSize = new Dimension(100, 100);
              pane.add(new Box.Filler(minSize, prefSize, maxSize));
              pane.add(label2);
              pane.add(new Box.Filler(minSize, prefSize, maxSize));
              pane.add(label3);
      Minimaler Abstand Box.Filler minimal
      Bevorzugter Abstand Box.Filler preferred
      Maximaler Abstand Box.Filler maximal

      Die fertige View

      Damit wir nun unsere View mit dem neuen Wissen anpassen können müssen wir erstmal in der Prototype Klasse in unserem Switch einen Funktionsaufruf im Falle einer 2 als Parameter einfügen. Dieser wird folgende Funktion aufrufen:

      	public JFrame fillFrameWithDataContent(JFrame frame) {
      		// wir erzeugen uns die Datenansicht
      		JPanel showData = new ShowDataJPanel();
      		// und fügen sie dem BorderLayout in der Mitt hinzu
      		frame.getContentPane().add(showData, BorderLayout.CENTER);
      		// Zur optischen Verschönerung erzeugen wir in den noch nicht
      		// belegten Feldern des Borderlayouts noch Rot grundierte Bereiche
      		JPanel placeholder_top = new JPanel();
      		placeholder_top.setBackground(Color.RED);
      		JPanel placeholder_right = new JPanel();
      		placeholder_right.setBackground(Color.RED);
      		JPanel placeholder_left = new JPanel();
      		placeholder_left.setBackground(Color.RED);
      		frame.getContentPane().add(placeholder_top, BorderLayout.PAGE_START);
      		frame.getContentPane().add(placeholder_right, BorderLayout.LINE_START);
      		frame.getContentPane().add(placeholder_left, BorderLayout.LINE_END);
      
      		return frame;
      	}

      In dieser Methode erzeugen wir ein JPanel. Dies ist wie die ContentPane ein Behälter, der weitere Elemente beinhalten kann und diese auch entsprechend den Layout-Vorgaben ausgibt. Hier kommt das Schichtenmodell von Swing wieder zu tragen. Damit mehrere verschiedene Layouts miteinander wirken können, tragen wir einzelne JPanels auf wie eine Grundierung bei auf einer Leinwand.

      Um die Definition des JPanels im JPanel zu halten haben wir das JPanel erweitert:

      package at.nullpointer.guiprototype.showdata;
      
      import java.awt.Color;
      import java.awt.Dimension;
      
      import javax.swing.Box;
      import javax.swing.BoxLayout;
      import javax.swing.JLabel;
      import javax.swing.JPanel;
      
      public class ShowDataJPanel extends JPanel {
      
      	public ShowDataJPanel(){
      
      		this.setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
      		this.setBackground(Color.RED);
      		this.setOpaque(true);
      
      		// Status Zeile
      		// Ein Container für die Zeile
      		JPanel statePanel = new JPanel();
      		// Mit dem BoxLayout.LINE_AXIS um die Elemente in der Zeile auszurichten
      		statePanel.setLayout(new BoxLayout(statePanel, BoxLayout.LINE_AXIS));
      		// Das erklärende Label
      		JLabel stateLabel = new ShowDataJLabel("Status");
      		// Das daten-haltende Label
      		JLabel stateContent = new ShowDataJLabel("aktiv");
      		// Wir wollen dass die Labels an der rechten Seite kleben
      		statePanel.add(Box.createHorizontalGlue());
      		statePanel.add(stateLabel);
      		// 5 Pixel Abstand zwischen den Label
      		statePanel.add(Box.createRigidArea(new Dimension(5, 0)));
      		statePanel.add(stateContent);
      		this.add(statePanel);
      
      		// .....
      
      	}
      
      }

      Hier wird das Layout wie weiter oben besprochen definiert und die Anordnung der Elemente vertikal gesetzt, so dass wir für jede Zeile die wir ausgeben wollen ein weiteres Objekt anfügen können. Der Anwendungsübliche rote Hintergrund wird natürlich nicht vergessen. Im Code zu sehen ist auch noch die erste Zeile. Hierfür erzeugen wir uns zuerst eine weitere Schicht in unserer Zwiebel, in der wir nur das Aussehen einer einzelnen Zeile definieren. Wir setzen das BoxLayout auf eine horizontale Aufteilung des Platzes und nutzen dann die Gestaltungsmöglichkeiten des BoxLayouts um den Inhalt des JPanel entsprechend den Vorgaben des Mocks zu verwirklichen. Unser überschriebenes JLabel bietet uns einige Gestaltungsmöglichkeiten und so kommen wir nach dem Einfügen der weiteren Felder ob individueller Abstände durch die rigid Area sowie nach unten geklebten Informationen über die Aktivität durch den VerticalGlue zu folgendem Ergebnis. Probiert doch selbst ob ihr das ebenfalls hinbekommt, damit ihr auch direkt ein Gefühl für die Auswirkungen des BoxLayouts bekommt.

      fertige View

      Das JTextField & der JButton

      JTextField und JButton DemoDas JTextField offeriert dem Programmierer die Möglichkeit den Benutzer zu Eingaben aufzufordern. Mit dem JButton wiederum kann man diese bestätigen, damit das Programm die neuen Daten verarbeiten kann. Beide Elemente sind notwendig um unser letztes Mockup umsetzten zu können. Da der prinzipielle Aufbau der View Seite sehr ähnelt gibt es hier nur eine kurze Einführung in die beiden neuen GUI Elemente. Das Umsetzen ist dann als Hausaufgabe zu sehen. Die Lösung dazu ist natürlich wieder im nächsten Teil angeführt.

      Das JTextField bietet alle Funktionalität an um Textelemente aufzunehmen. Hierbei gibt es verschiedene Abwandlungen wie z.B. das JPasswordField oder das JFormattedTextField. Ersteres versteckt nach außen welche Buchstaben man eingegeben hat, zweiteres dient zur Überprüfung von Eingaben bzw. Festlegung von Eingabekonventionen. Für unser Beispiel reicht jedoch bereits das normale JTextField, welches im folgenden Source kurz erklärt wird.

      		Dimension d = new Dimension(100,20);
      		JTextField txt_one = new JTextField();
      		txt_one.setMinimumSize(d);
      		JTextField txt_two = new JTextField("InitialText");
      		txt_two.setMinimumSize(d);

      Das erste Textfeld wird erzeugt und danach wird ihm eine Mindestgröße zugewiesen. Beim zweiten Textfeld haben wir zusätzlich noch eine initiale Befüllung im Konstruktor.

      Auch der JButton ist relativ simpel erstellt und hält einen Konstruktor mit einer Möglichkeit zur initialen Befüllung mit Text bereit

      JButton btn = new JButton("press");

      Auch er bietet wieder eine Methode an mit der minimale und maximale Größe gesetzt werden kann, womit unser derzeitiger Anspruch für einen GUI Prototypen abgedeckt ist.

      Man sieht, rein das Anzeigen dieser Komponenten ist keine Hexerei, die Magie dahinter ist wird dann im nächsten Teil dieser Reihe aufgedeckt.

      Aufgabenstellung

      Ein Entwurf einer Maske zur Kundenerfassung für eine Java Swing OberflächeEs gilt nun die Edit-Sicht mithilfe des in dieser Lektion erlangten Wissens umzusetzen, man soll ein EditJPanel erzeugen und auf dieser die notwendigen UIElemente platzieren. Wenn man den Prototypen mit dem Startparameter 3 startet, soll dieses sichtbar sein. Natürlich soll unser Footer nicht überschrieben werden. Die Ausgangssourcen sind hier zu finden: nullpointer.at HOWTO Swing – Teil3 Sourcen

      Have Fun!

      Ähnliche Artikel:

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

      Wie entwickelt man eigentlich eine GUI mit Java Swing? Eine mehrteilige Einführung in dieses Thema. Teil 2 enthält den ersten Prototypen.

      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:

      java.lang.NoClassDefFoundError: org/codehaus/plexus/classworlds/launcher/Launcher

      Beheben von java.lang.NoClassDefFoundError: org/codehaus/plexus/classworlds/launcher/Launcher bei der Migration von Maven 2 nach Maven 3 kurz erklärt.

      Migration von Maven 2 nach Maven 3

      Vor kurzem habe ich von Maven 2 auf Maven 3 umgestellt. Aus Mangel an Upgrade Guides habe ich mich an die Readme.txt gehalten. Demnach soll man das Archiv, das man unter http://maven.apache.org/download.html downloaden kann, entpacken und danach den PATH um die Lokation von Maven erweitern. Maven benötigt ebenfalls die Umgebungsvariable JAVA_HOME, welches auf das SDK verweist.

      Danach soll man die Installation mit Aufruf der Maven Executable testen:

      mvn –version

      Was zu folgender Ausgabe geführt hat

      java.lang.NoClassDefFoundError

      Eine Exception wird geworfen:

      java.lang.NoClassDefFoundError: org/codehaus/plexus/classworlds/launcher/Launcher

      Das Problem dabei war nun folgendes. Prinzipiell benötigt Maven keine weiteren Umgebungsvariablen gesetzt. In meiner Entwicklungsumgebung war jedoch die Variable M2_HOME vorhanden, die noch auf die alte Maven Version zeigte. Maven 3 versucht nun dabei über diese Umgebungsvariablen auf seine Bibliothek zuzugreifen. Dies führt zu oben genanntem Fehler, da logischerweise die falschen Libraries angesprochen werden. Ein Entfernen dieser Variable oder ein Ändern auf die Variable M3_HOME inklusive richtiger Pfadzuweisung behebt das Problem.

      Ich hoff das hilft dem einen oder anderen diese kleine Hürde schnell zu überspringen.

      Ähnliche Artikel:

      HOWTO: GUI mit Swing – Teil 1: Einführung

      Wie entwickelt man eigentlich eine GUI mit Java Swing? Eine mehrteilige Einführung in dieses Thema.

      Tjo, es gibt in der Programmierung meiner Meinung nach nichts nervenaufreibenderes als die Entwicklung einer Bedienoberfläche.

      • Ist alles gut positioniert?
      • Ist die GUI intuitiv?
      • Stimmt die Tabluatorreihenfolge?
      • Welches Farbschema ist passend?
      • Sind ausreichend Hilfestellungen vorhanden?

      und so weiter und so weiter …

      Alles wichtige Fragen, doch um sich mit den Feinheiten davon auseinandersetzen zu dürfen, muss man erstmal wissen wie man eine GUI überhaupt erzeugt! Und genau das möchte ich in den kommenden Blogposts behandeln und zwar mit Java unter Zuhilfenahme der Swing Bibliotheken.

      Ein erster kurzer Überblick über die kommenden Themen

      Was ist Swing überhaupt?

      Nun die Swing Bibliotheken umfassen allerlei grafische Komponenten wie z.B Buttons, Split Panes und Tables. Ich falle hier in die englische Sprache, da diese Begriffe später auch in der Entwicklung verwendet werden. Viele der in Swing enthaltenen Komponenten bieten zudem brauchbare Funktionalitäten wie ein Sortieren des Inhalts, Drucken, drag’n’drop. Swing selbst ist seit Java 1.2 Bestandteil der Runtime und baut auf das ältere AWT (Abstract Window Toolkit) auf. Es gehört zu den JFC (Java Foundation Classes) die eine Sammlung von APIs für die GUI Entwicklung einhält, und greift auf diese zusätzlichen APIs auch zu.

      Hello Swing

      Um einen ersten Vorgeschmack auf das zu geben, was da kommt, möchte ich mit einem Hello Swing Beispiel beginnen. Man bekommt dabei einen ersten Einblick in die Notwendigkeiten, die der Aufbau einer GUI mit Swing mit sich bringen. Eine kurze Erklärung dazu befindet sich an den jeweiligen Stellen im Sourcecode.

      package at.nullpointer.learningswing.helloswing;
      
      import javax.swing.JFrame;
      import javax.swing.JLabel;
      
      /**
       * Hello Swing - Simple GUI-Demo
       *
       * @author Tom
       *
       */
      public class HelloSwing {
      
      	public static void main(String[] args) {
      
      		// Zwecks Threadsicherheit wird die Methode die die
      		// GUI anzeigt vom event-dispatching Thread gestartet
      		javax.swing.SwingUtilities.invokeLater(new Runnable() {
      			public void run() {
      				createAndShowGUI();
      			}
      		});
      	}
      
      	/**
      	 * Erzeut ein Fenster und zeigt es.
      	 */
      	private static void createAndShowGUI() {
      		// Ein Fenster erzeugen
      		JFrame frame = new JFrame("Hello Swing");
      		// Dem X Button des Fensters rechts oben wird die Funktion
      		// des Beendens des Fensters zugewiesen
      		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      
      		// Ein Label mit unserem Hallo erzeugen
      		JLabel label = new JLabel("Hello Swing");
      		// Das Label auf die Darstellbare Oberfläche stecken
      		frame.getContentPane().add(label);
      		// Das Fenster komprimieren
      		frame.pack();
      
      		// Das Fenster sichtbar machen
      		frame.setVisible(true);
      	}
      }

      Herangezogen hab ich für diesen Quelltext, das Original der Java Einführung von Sun/Oracle, die man hier findet.

      Unsere Zielanwendung

      Jede einzelne Swing Komponente anzuführen und zu erklären würde den Rahmen dieses Eintrags sprengen. Dennoch haben wir viel vor. Ich möchte jetzt ein Szenario vorstellen, für das wir eine GUI entwickeln wollen.

      Eine Reinigungsfirma mit 4 Filialen überlegt den Einsatz von Stammkundenkarten, um die Kundschaft länger zu binden, Kundenprofile anlegen zu können für gezielte Werbemaßnahmen und das Reinigungsbedürfnis der Kundschaft analysieren zu können. Zur Umsetzung benötigen sie nun eine Anwendung die von einem Kartenlesegerät die Kundennummer einliest, diese mit dem zentralen Server der Firmensoftware abgleicht und um dann der Verkäuferin in der Filiale diverse Information anzeigt, wie z.B. Name und Adresse, sowie Telefonnummer. Es soll außerdem angezeigt werden ob die Karte aktiviert wurde. Auch die letzte Aktivität der Karte soll der Verkäuferin angezeigt werden (inkl. Filialnummer). Als zusätzliches Feature soll man neue Karten registrieren können, man muss dazu Name Adresse und Telefonnummer einer noch nicht aktivierten Karte zuweisen können.

      Da wir hier nur ein Fallbeispiel aufbereiten, wollen wir nicht gegen einen wirklichen Kartenleser und eine richtige Firmensoftware programmieren. Wir verwenden sogenannte Mock-Objekte, oder auch Dummy-Objekte, die sehr vereinfacht entsprechende Funktionalität zur verfügung stellen.

      Der MockCardReader:
      package at.nullpointer.learningswing.mock;
      
      /**
       * Mockobjekt als Simulation eines Kundenkartenlesesystem
       *
       * @author fake
       *
       */
      public class MockCardReader {
      
      	private final int aktiveKdnr = 12345;
      	private final int gesperrteKdnr = 54321;
      	private final int neueKdnr = 66666;
      
      	/**
      	 * Liefert eine aktive Kundennummer zurück Public Methode um JUnitTestfälle
      	 * zu unterstützen
      	 *
      	 * @return int
      	 */
      	public int getAktiveKdnr() {
      		return aktiveKdnr;
      	}
      
      	/**
      	 * Liefert eine gesperrte Kundennummer zurück Public Methode um
      	 * JUnitTestfälle zu unterstützen
      	 *
      	 * @return int
      	 */
      	public int getGesperrteKdnr() {
      		return gesperrteKdnr;
      	}
      
      	/**
      	 * Liefert eine neue Kundennummer zurück Public Methode um JUnitTestfälle zu
      	 * unterstützen
      	 *
      	 * @return int
      	 */
      	public int getNeueKdnr() {
      		return neueKdnr;
      	}
      
      	/**
      	 * Liefert eine Kundennummer zurück
      	 *
      	 * @return
      	 */
      	public int getKdnr() {
      		int choose = (int) (System.currentTimeMillis() % 3);
      		switch (choose) {
      		case 0:
      			return aktiveKdnr;
      		case 1:
      			return gesperrteKdnr;
      		case 2:
      			return neueKdnr;
      		default:
      			return aktiveKdnr;
      		}
      	}
      
      }

      Die MockFirmenSoftware:
      package at.nullpointer.learningswing.mock;
      
      import java.util.Date;
      import java.util.HashMap;
      import java.util.Map;
      
      /**
       * Simulation der Firmenkundendatenbank
       *
       * @author fake
       *
       */
      public class MockCompanySoftware {
      
      	private Map kundendaten = new HashMap();
      
      	/**
      	 * Erzeugt und befüllt initial die Datenbank
      	 */
      	public MockCompanySoftware() {
      		Kundendaten kd1 = new Kundendaten(12345, "Max", "Mustermann",
      				"Mustermannstraße 3#1111 MusterWien", 1305840610, true,
      				new Date(2010, 10, 10), "Filiale 03");
      		Kundendaten kd2 = new Kundendaten(54321, "Maxime", "Musterfrau",
      				"Wo anders 4#1111 MusterWien", 1231543656, false, new Date(
      						2010, 9, 10), "Filiale 03");
      
      		kundendaten.put(Integer.valueOf(kd1.getKdnr()), kd1);
      		kundendaten.put(Integer.valueOf(kd2.getKdnr()), kd2);
      	}
      
      	/**
      	 * Man kann die Kundendaten eines Kunden über die Kundennummer abfragen
      	 *
      	 * @param kundennummer
      	 * @return Kundendaten wenn vorhanden, null wenn nicht vorhanden
      	 */
      	public Kundendaten getKundendaten(int kundennummer) {
      		return kundendaten.get(Integer.valueOf(kundennummer));
      	}
      
      	public String setKundendaten(Kundendaten kdata){
      		return "Daten gesandt";
      	}
      
      }
      Das Kundendatenobjekt:
      package at.nullpointer.learningswing.mock;
      
      import java.util.Date;
      
      /**
       * Objekt repräsentiert einen Kunden in der Datenbank
       *
       * @author fake
       *
       */
      public class Kundendaten {
      	private int kdnr;
      	private String vorname;
      	private String nachname;
      	private String adresse;
      	private long telefonnummer;
      	private boolean aktiv;
      	private Date lastAktive;
      	private String filialname;
      
      	public Kundendaten(int kdnr, String vorname, String nachname,
      			String adresse, long telefonnummer, boolean aktiv, Date lastAktive,
      			String filialname) {
      		super();
      		this.kdnr = kdnr;
      		this.vorname = vorname;
      		this.nachname = nachname;
      		this.adresse = adresse;
      		this.telefonnummer = telefonnummer;
      		this.aktiv = aktiv;
      		this.lastAktive = lastAktive;
      		this.filialname = filialname;
      	}
      
      	public int getKdnr() {
      		return kdnr;
      	}
      
      	public void setKdnr(int kdnr) {
      		this.kdnr = kdnr;
      	}
      
      	public String getVorname() {
      		return vorname;
      	}
      
      	public void setVorname(String vorname) {
      		this.vorname = vorname;
      	}
      
      	public String getNachname() {
      		return nachname;
      	}
      
      	public void setNachname(String nachname) {
      		this.nachname = nachname;
      	}
      
      	public String getAdresse() {
      		return adresse;
      	}
      
      	public void setAdresse(String adresse) {
      		this.adresse = adresse;
      	}
      
      	public long getTelefonnummer() {
      		return telefonnummer;
      	}
      
      	public void setTelefonnummer(long telefonnummer) {
      		this.telefonnummer = telefonnummer;
      	}
      
      	public boolean isAktiv() {
      		return aktiv;
      	}
      
      	public void setAktiv(boolean aktiv) {
      		this.aktiv = aktiv;
      	}
      
      	public Date getLastAktive() {
      		return lastAktive;
      	}
      
      	public void setLastAktive(Date lastAktive) {
      		this.lastAktive = lastAktive;
      	}
      
      	public String getFilialname() {
      		return filialname;
      	}
      
      	public void setFilialname(String filialname) {
      		this.filialname = filialname;
      	}
      
      }

      Um noch einen ungefähren Überblick darüber zu erhalten wie unsere GUI einmal aussehen wird, hier noch 3 Mockups, Beispielbilder, nach denen wir unsere GUI entwerfen werden.

      Der StandbyScreen:

      Ein Entwurf für einen Standbyscreen für eine Java Swing Oberfläche

      Anzeige der Kundendaten:

      Ein Entwurf einer Kundendatenanzeige für eine Java Swing Oberfläche

      Eingabemaske für einen Neukunden:

      Ein Entwurf einer Maske zur Kundenerfassung für eine Java Swing Oberfläche

      Da wir nun ein ungefähres Bild davon haben, wie unsere Anwendungen auszusehen haben, und was sie können sollen gilt es selbiges umzusetzen. Damit werden wir uns im nächsten Teil dieser Serie beschäftigen. Vorerst erschaffen wir jedoch einen Prototypen der noch keinerlei Funktionsumfang beinhaltet.

      Bis zum nächsten Teil dieser Reihe!

      Ähnliche Artikel:

      PHP und $_SERVER[‚SCRIPT_URI‘] – Beispiel Soap WSDL

      Die Variablen im PHP Array $_SERVER sind sehr praktisch für die automatische Zusammenstellung von absoluten URLs. Eine Variable davon heißt SCRIPT_URI. Diese beinhaltet die komplette URL (aus der Adresszeile des Browsers) inlusive Protokoll, Port, …

      Die Variablen im PHP Array $_SERVER sind sehr praktisch für die automatische Zusammenstellung von absoluten URLs.

      Eine Variable davon heißt SCRIPT_URI (also anzusprechen unter $_SERVER[‚SCRIPT_URI‘])

      Diese beinhaltet die komplette URL (aus der Adresszeile des Browsers) inlusive Protokoll, Port, …

      Leider ist sie jedoch nicht immer verfügbar.

      Nachprüfen lässt sich das mit einer php Datei mit Inhalt

      <?php phpinfo();

      Beim Aufruf der Seite bekommt man im Abschnitt Apache Environment alle $_SERVER Variablen zu sehen.

      Falls sich hierbei $_SERVER[‚SCRIPT_URI‘] nicht findet fehlt das Apache Modul mod_rewrite bzw. es ist nicht aktiviert.

      Dies lässt sich nachholen durch das Laden des entsprechenden Apache Moduls:

      LoadModule rewrite_module /usr/lib/apache2/modules/mod_rewrite.so

      Wenn das Modul geladen ist, muss es noch mit:

      RewriteEngine on

      aktiviert werden. Dies kann sowohl in der Konfigurationsdatei des Apache Webservers erfolgen als auch in einer .htaccess Datei im jeweiligen Script Verzeichnis.

      Ebenfalls Voraussetzung für das Vorhandensein der Variable ist, dass die Webseite in einem Virtuellen Host liegt:

      <VirtualHost *:80>
      	ServerAdmin webmaster@domain.tld
      	DocumentRoot /....
      	ServerName www.domain.tld
      	...
      </VirtualHost>
      

      Dies sollte jedoch bei den meisten Hostern bereits der Fall sein (auch viele Linux Distributionen liefern Ihre Apache Konfiguration bereits standardmäßig mit Virtuellen Hosts aus).

      Tipp:

      Abgesehen vom Erstellen absoluter Links für beispielsweise Menüeinträge einer Webseite lässt sich $_SERVER[‚SCRIPT_URI‘] auch hervorragend dazu benutzen im Zusammenhang mit SOAP Webservices die SOAP Location der ausgelieferten WSDL Dateien anzupassen.

      Auszug aus einer WSDL Datei:

      ...
      <binding name="testBinding" type="tns:testPortType">
      <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http" />
      <wsdl:operation name="login">
      <soap:operation soapAction="%SOAP_LOCATION%?method=login" style="rpc" />
      <wsdl:input>
      ...

      Der dazugehörige PHP Code:

      ...
      if( isset($_GET['wsdl']) ) {
          $wsdl = @file_get_contents( $wsdlFile );
          if( !$wsdl ) {
              die( 'invalid wsdl file' );
          }
          $wsdl = str_replace( '%SOAP_LOCATION%', $_SERVER['SCRIPT_URI'], $wsdl );
          header( 'Content-Type: text/xml' );
          echo $wsdl;
      }
      ....

      Ähnliche Artikel: