HOWTO: GUI mit Swing – Teil 5: Concurrency in Swing

Wie entwickelt man eigentlich eine GUI mit Java Swing? Eine mehrteilige Einführung in dieses Thema. Teil 5 beschäftigt sich mit Nebenläufigen prozessen in Swing

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

      Nachdem wir nun erfolgreich eine GUI gebaut haben, die auf unsere ersten Klicks reagiert wollen wir uns nun mit der Nebenläufigkeit in Swing Anwendungen beschäftigen.

      Aufgabe aus Teil 4

      Aufgabe überspringen

      Aus Teil 4 war noch ein ActionListener für den Menüpunkt beenden zu realisieren.

      Action Listener

      Wir erstellen analog zum EditActionListener eine neue Klasse MenuActionListener. In dieser prüfen wir ob das Event dass der Listener enthält das Beenden Event ist, schließen erst das JFrame und dann die ganze Anwendung.

      package at.nullpointer.guiprototype.listener;
      
      import java.awt.event.ActionEvent;
      import java.awt.event.ActionListener;
      
      import at.nullpointer.guiprototype.Prototype;
      
      public class MenuActionListener implements ActionListener {
      
      	public void actionPerformed(ActionEvent e) {
      		if(e.getActionCommand().equals("Beenden")){
      			Prototype.getJFrame().dispose();
      			System.exit(0);
      		}
      	}
      }

      Den ActionListener müssen wir nun nur noch dem MenuItem zuweisen. Dies geschieht analog zu der Zuweisung beim JButton. Wir editieren dazu die Klasse PrototypeMenuBar

      		close.addActionListener(new MenuActionListener());

      Damit können wir nun das Programm über den Menüpunkt beenden. Auch das Tastenkürzel ALT+B führt zum Programmende.

      NebenLäufigkeit in Swing

      Die erste Form von Nebenläufigkeit in Swing haben wir bereits in den vorherigen Teilen dieser Serie kennengelernt. Der Event-Dispatcher Thread wurde bereits genutzt um die GUI zu erstellen und anzuzeigen

              //Schedule a job for the event-dispatching thread:
              //creating and showing this application's GUI.
              javax.swing.SwingUtilities.invokeLater(new Runnable() {
                  public void run() {
                      createAndShowGUI();
                  }
              });

      Dabei wird im Initial-Thread, der die main Methode ausführt, oben benannter Code ausgeführt. Man übergibt dem Event-Dispatcher Thread ein Runnable Objekt, dass dieser ausführt sobald er soweit ist. Bei invokeLater arbeitet der Initialthread nach der Übergabe gleich weiter und würde nachfolgenden Code behandeln.

      Für den Fall, dass der nachfolgende Code erst ausgeführt werden darf, wenn die GUI erzeugt wurde gibt es noch die invokeAndWait Methode, die das Runnable Objekt ebenfalls übergibt, aber auf dessen Beendung wartet.

      Führt man seine GUI nicht im Event-Dispatcher Thread aus funktioniert sie meist auch, es kann aber zu Speicherverletzungen und somit Fehlverhalten kommen, welche nur schwer zu finden bzw. korrigieren ist. Von diesem Event-Dispatcher Thread ist jedoch die gesamte Swing Oberfläche abhängig, da die meisten Komponenten nicht Thread Safe implementiert sind. Deshalb sollten darauf nur kurze Tasks, wie das feuern von Events, oder z.B. minimale Textkorrekturen ausgeführt werden (zB durch zusätzliche invokeLater Aufrufe) um ein Einfrieren der Applikation zu vermeiden. Ein Umstand dem wir in den bisherigen Teilen dieses Tutorials kaum Rechnung getragen haben.

      Für rechenintensivere Aufgaben, oder Tasks die zB Netzwerkkommunikation erfordern ist es daher notwendig auf eine andere Abwicklung zurückzugreifen. Sogenannte Worker-Threads werden für die Umsetzung dessen verwendet. Man muss dazu mit seinem Objekt die abstrakte Klasse SwingWorker implementieren, welche einige Features zur Kommunikation mit dem Event-Dispatcher Thread beinhaltet.

      Akuell weist unser Prototyp folgendes Verhalten auf: Wenn wir in unserem Prototypen auf der EditView auf OK klicken übermittelt unser Programm die Daten an die Enterprise Software im Hintergrund. Ist diese mit dem Bearbeiten fertig, übersendet sie uns ein OK, welches wir bestätigen müssen. Erst dann schaltet unser Prototyp in den Anzeigemodus. Verzögert sich nun das Absenden der Bestätigung friert unser Programm ein. Dies kann man leicht nachstellen indem man die Mock-Klasse MockCompanySoftware um z.B. eine fette Schleife erweitert.

      	public String setKundendaten(Kundendaten kdata) {
      		for (int x = 0; x < 100000; x++)
      			for (int y = 0; y < 100000; y++){
      				System.currentTimeMillis();
      			}
      	    return "Daten gesandt";
      	}

      Wir müssen nun merklich warten bis unser Programm weiter verwendbar ist. Minimieren und verschieben funktioniert immerhin. Doch der Button bleibt gedrückt, die Ansicht ebenfalls auf der EditView. Um dem zu begegnen müssen wir die Prototype#sendData Methode ändern. Dazu erzeugen wir einen SwingWorker, der das Senden der Daten für uns übernimmt.

      	public static void sendData() {
      		SwingWorker worker = new SwingWorker<Void , Void>() {
      
      			private String answer;
      
      			// Die eigentliche Aufgabe wird bearbeitet
      			@Override
      			protected Void doInBackground() throws Exception {
      				MockCompanySoftware mcs = new MockCompanySoftware();
      				answer = mcs.setKundendaten(null);
      				return null;
      			}
      
      			/* Wenn die Aufgabe fertig ist wird unsere JOptionPane angezeigt
      			   Dazu wird diese Methode aufgerufen wenn der Event-Dispatcher-Thread
      			   bemerkt, dass der HintergrundThread abgearbeitet ist.
      			 */
      			@Override
      			public void done() {
      				JOptionPane.showMessageDialog(null, answer);
      			}
      		};
      
      		//Der Worker wird ausgeführt
      		worker.execute();
      
      	}

      Hier sehen wir den Aufbau des SwingWorker. Er beinhaltet eine doInBackground Methode, die in einem Background-Thread ausgeführt wird. Sobald dieser Fertig ist wird vom Event-Dispatcher-Thread die done Methode aufgerufen. Mit der execute Methode startet man den Background-Thread. Nach erfolgreiches Event könnte von dem Ergebnis mittels get Methode gebrauch machen.

      Probiert den Prototypen nun nochmal aus, der Button wird nun freigegeben.

      Damit haben wir unsere Applikation nebenläufig gemacht und ich beschließe diese Tutorial-Reihe mit den Worten:

      Wenn ihr euer Wissen um Swing erweitern und effektiv einsetzen wollt, setzt euch am besten gleich hin und programmiert, schaut euch fremden Code z.B. auf Open Sourceplattformen an und programmiert dann weiter. Vielleicht baut ihr ja sogar das Beispiel hier aus. Weitere Anregungen oder Fragen dazu sind natürlich gern gesehen.

      Zum Weiterlesen ein paar Buchtipps, sowie Weblinks. Swing wird neben den online Tutorials und spezialisierten Büchern ebenfalls in div. Standardlehrbüchern zu Java behandelt.

      Trail: Creating a GUI With JFC/Swing – von Oracle: http://download.oracle.com/javase/tutorial/uiswing/index.html

      The JFC Swing Tutorial – Addison-Wesley Verlag 2004 Verlag – ISBN 0201914670, 9780201914672

      http://books.google.at/books?id=3rWTX-vjUhEC

      Für Fortgeschrittene: The definitive guide to Java Swing – Apress Verlag 2005 – ISBN 1590594479, 9781590594476

      http://books.google.at/books?id=YPjZNlEgAMcC

      Ähnliche Artikel:

      HOWTO: GUI mit Swing – Teil 4: Interaktion mit der GUI

      Wie entwickelt man eigentlich eine GUI mit Java Swing? Eine mehrteilige Einführung in dieses Thema. Teil 4 versieht den GUI Prototypen mit erster Funktionalität.

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

          Aufgabe aus Teil 3

          Aufgabe überspringen

          Aufgabe aus Teil 3 war es, nachdem wir die Anzeige Maske fertiggestellt hatten, auch die Edit Maske zu konstruieren.

          EditJPanel

          Damit wir wahlweise die Daten anzeigen, oder bearbeiten können, habe ich in meiner Lösung folgenden Weg eingeschlagen: Das ShowDataJPanel wurde umgebaut zum DataJPanel, welches im Konstruktor nun einen Boolean Parameter entgegennimmt, durch den entschieden wird ob die Daten mittels Label oder Textfield angezeigt werden. Anhand dieses Booleans wird auch entschieden ob die statistsichen Daten angezeigt werden oder der Absende Button.

          public DataJPanel(boolean useTextfields) { //...

          Unsere Maske wird nun immer noch gleich aufgebaut wie vorher, allerdings instanziert sie nicht von Haus aus ein ShowDataJLabel, sondern überlässt einer Factory Klasse die Entscheidung ob es entweder das JLabel oder aber ein EditDataJTextField erzeugt indem es das useTextfields Boolean daran durchschleust.

          JComponent kdnrContent = ShowOrEditJComponentFactory.getJComponent(
          				"0123456", useTextfields);

          Dabei wird das Wissen um den Aufbau des erhaltenen Objekts bewusst klein gehalten, um nicht zu hohe Abhängigkeiten zu erhalten. Das JComponent wird durch folgende Factory erzeugt:

          package at.nullpointer.guiprototype.showdata;
          
          import java.awt.Color;
          import java.awt.Dimension;
          import java.awt.Font;
          
          import javax.swing.JComponent;
          
          public class ShowOrEditJComponentFactory {
          
          	private static final Font font = new Font("Dialog", Font.PLAIN, 18);
          	private static final Dimension textFieldSize = new Dimension(480, 30);
          
          	private static JComponent createJLabel(String caption) {
          		return new ShowDataJLabel(caption);
          	}
          
          	private static JComponent createJTextField(String caption) {
          		return new EditDataJTextField(caption);
          	}
          
          	public static JComponent getJComponent(String caption, boolean isTextField) {
          		JComponent erg = null;
          		if (isTextField) {
          			erg = createJTextField(caption);
          			erg.setMaximumSize(textFieldSize);
          			erg.setMinimumSize(textFieldSize);
          			erg.setPreferredSize(textFieldSize);
          		} else {
          			erg = createJLabel(caption);
          		}
          
          		// Schriftfarbe
          		erg.setForeground(Color.BLACK);
          		// undurchsichtig (notwendig um einen Hintergrund zu zeichnen)
          		erg.setOpaque(true);
          		// Veränderung der Schrift
          		erg.setFont(font);
          
          		return erg;
          	}
          }

          Die beiden Klassen ShowDataJLabel und EditDataJTextField, die erst von der Factory erzeugt werden und dann anhand des allgemeinen Verhaltens der JComponent etwas an unsere Optik angepasst werden sind selbst relativ simpel gehalten:

          package at.nullpointer.guiprototype.showdata;
          
          import javax.swing.JTextField;
          
          public class EditDataJTextField extends JTextField {
          
          	public EditDataJTextField(String caption) {
          		this();
          		this.setText(caption);
          	}
          
          	public EditDataJTextField() {
          		super();
          	}
          }
          package at.nullpointer.guiprototype.showdata;
          
          import javax.swing.JLabel;
          
          public class ShowDataJLabel extends JLabel {
          
          	public ShowDataJLabel() {
          
          		super();
          	}
          
          	public ShowDataJLabel(String caption) {
          		this();
          		this.setText(caption);
          	}
          }
          Der Button

          Natürlich darf in der DataJPanel der Button zum Absenden nicht fehlen, der jedoch nur angezeigt werden soll, wenn Textfelder genutzt werden.

          if (useTextfields) {
          
          			// Zuletzt aktiv
          			JPanel lastPanel = new JPanel();
          			lastPanel.setLayout(new BoxLayout(lastPanel, BoxLayout.LINE_AXIS));
          			JButton btnOk = new JButton("OK");
          			JButton btnCancel = new JButton("Abbrechen");
          			lastPanel.add(Box.createHorizontalGlue());
          			lastPanel.add(btnOk);
          			lastPanel.add(Box.createRigidArea(rowSmall));
          			lastPanel.add(btnCancel);
          			this.add(lastPanel);
          			this.add(Box.createRigidArea(lineSmall));
          
          		} else {
          Aufruf der Sichten

          Jetzt muss nur noch der Aufruf der Sichten angepasst werden, der analog zum Aufruf der Daten Ansicht erfolgt. Bei Beiden gilt es jedoch nun ein Boolean zu übergeben, damit die Factory entscheiden kann, was angezeigt werden soll. Unsere fertige Eingabemaske sieht nun wie folgt aus:

          Edit Sicht

          Die vollständigen Sourcen dazu findet ihr wie immer am Schluss dieses Artikels, gemeinsam mit der nächsten Aufgabe.

          Ein Button erwacht zum Leben

          Um aufzuzeigen wie man einen Button zum Leben erweckt erzeugen wir uns als erstes eine sehr simple GUI:

          package at.nullpointer.learningswing.demojframe;
          
          import javax.swing.JButton;
          import javax.swing.JFrame;
          
          public class InputDemo {
          	int count = 0;
          	String text = " mal geklickt";
          	JButton btn = null;
          
          	public static void main(String[] args){
          		InputDemo gui = new InputDemo();
          		gui.draw();
          	}	
          
          	public void draw(){
          		JFrame frame = new JFrame();
          		btn = new JButton(count+text);
          		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
          		frame.getContentPane().add(btn);
          		frame.pack();
          		frame.setVisible(true);
          	}
          }

          JButton Event Demo Wenn wir diese ausführen bekommen wir folgendes einfach nur einen Button angezeigt, bei dessen Betätigung nichts passiert. Das wollen wir nun ändern. Dafür brauchen wir 2 Dinge, etwas das passieren soll, und einen Weg wie wir rausfinden ob der Button geklickt wurde.

          Unschwer an der Beschriftung des Buttons zu erraten, wollen wir, dass jedes mal wenn er betätigt wird, sich seine Beschriftung ändert und die Anzahl der Klicks korrekt dargestellt wird. Dazu erweitern wir unser Demo um folgende Methode:

          	private void increaseCount(){
          		count++;
          		btn.setText(count+text);
          	}

          Testen lässt sie sich ganz einfach indem wir den Aufruf der Methode am Ende der Main Methode hinzufügen.

          Um nun auf die User Aktion reagieren zu können setzt Java ein Modell ein, das sich Event Handling nennt. Klickt ein User auf den Button wird ein Event (repräsentiert durch ein Event Objekt) abgefeuert. Diese Funktionalität bietet der JButton, ohne dass wir sie selbst implementieren müssten. Jedoch fehlt uns noch ein Weg dieses Event auszulesen. Es gibt verschiedenste Events (nachzusehen im java.awt.event Package) und unser JButton wirft eines vom SubTyp ActionEvent. Jedes dieser Eventtypen kann durch einen eigenen Listener ausgelesen werden. Für unser ActionEvent ist also ein ActionListener notwendig. Dies ist ein Interface, welches wir allerdings schon selbst implementieren müssen. Wir erweitern also unser InputDemo und lassen es das Interface implementieren

          public class InputDemo implements ActionListener {

          Damit wir das Interface vollständig umsetzen, müssen wir auch noch die von ihm geforderte Methode erzeugen. In diesem Fall ist das die actionPerformed Methode, in welcher wir unsere increaseCount methode aufrufen

          	public void actionPerformed(ActionEvent e) {
          		increaseCount();
          	}

          Starten wir nun das Programm sehen wir, dass das existieren eines Listeners alleine noch keine Auswirkungen hat, dem Button muss erst gesagt werden, dass da ein ActionListener auf seine Events wartet. Dazu stellt der JButton wieder eine entsprechende Methode zur Verfügung, welche wir bei der Erzeugung des JButtons aufrufen.

          		btn.addActionListener(this);

          image Und siehe da, unser Count beginnt nun beim Betätigen des Buttons zu steigen und zu steigen!

          ActionListener im Prototypen

          Der Cancel Button

          Unser neu erworbenes Wissen wollen wir jetzt natürlich im Prototypen anwenden. Wir wollen den Abbrechen Button mit Funktionalität belegen, dazu erzeugen wir uns zuerst im Prototypen entsprechende Methoden, die dazu führen dass statt der Editansicht die Showansicht angezeigt wird.

          Eine Methode die die Ansichten zeichnet haben wir ja bereits geschrieben: fillFrameWithDataContent

          Also benötigen wir nur noch eine Methode die das Frame von seinem alten Inhalt befreit:

          	public static JFrame removeJPanel(JFrame frame){
          		Component[] components = frame.getContentPane().getComponents();
          		for(Component component : components){
          			if (component instanceof JPanel){
          				frame.remove(component);
          			}
          		}
          		return frame;
          	}

          Danach erzeugen wir eine eigene Klasse, die wir EditActionListener taufen. Diese wird auf den Klick mit der Maus reagieren. Dabei arbeiten wir der Einfachheit halber in diesem Beispiel mit den static Methoden der Prototype Class. Ein Vorgehen welches ich ansonsten nicht befürworten würde.

          package at.nullpointer.guiprototype.listener;
          
          import java.awt.event.ActionEvent;
          import java.awt.event.ActionListener;
          
          import at.nullpointer.guiprototype.Prototype;
          
          public class EditActionListener implements ActionListener {
          
          	public void actionPerformed(ActionEvent e) {
          		Prototype.removeJPanel(Prototype.getJFrame());
          		Prototype.fillFrameWithDataContent(Prototype.getJFrame(), false).setVisible(true);
          	}
          }

          Zuerst wird unsere neue Methode aufgerufen, dannach befüllen wir das JFrame wieder. Achtung, das JFrame wird dann mittels Aufruf der setVisible Methode erneut für den Benutzer dargestellt, um die Änderungen sichtbar zu machen.

          Nun noch in der Klasse DataJPanel den EditActionListener dem Cancelbutton hinzugefügt und schon reagiert unser Prototyp richtig bei einem Klick auf den Abbrechen Button!

          Der OK Button

          Wie ihr vielleicht an der Benennung des EditActionListener erkannt habt, soll dieser nicht nur für den Cancel Button zuständig sein, sondern auch für den OK Button. Wir lassen ihn daher auch auf Events des OKButtons anhand der addActionListener Methode lauschen.

          Klicken wir nun auf den OK Button sehen wir unser ActionListener nicht zwischen den zwei Buttons unterscheiden kann. Der Klick führt zum selben Ergebnis wie ein Klick auf den Cancel Button. Damit der ActionListener differenzieren kann, nutzen wir die Informationen aus, die das Event, welches der actionPerformed Methode mitgegeben wurden, enthält. Folgende Methoden stehen dabei zur Verfügung:

          getActionCommand() liefert den CommandString der der Action entspricht
          getModifiers() liefert ein int, welches die Kontrolltasten die dabei gedrückt wurden repräsentiert
          getSource() liefert das Objekt auf dem das Event durchgeführt wurde
          getWhen() liefert den Zeitpunkt des Events zurück

          Wir verwenden die getActionCommand() Methode um unser Button Objekt zu identifizieren. Dieses liefert uns den ID-String zurück, den wir bei der Buttonerzeugung angegeben haben, also hierbei “OK”. Nur im falle eines Clicks auf OK führen wir zusätzlich das Absenden der Daten aus.

          	public void actionPerformed(ActionEvent e) {
          		if(e.getActionCommand().equals("OK")){
          			Prototype.sendData();
          		}
          		Prototype.removeJPanel(Prototype.getJFrame());
          		Prototype.fillFrameWithDataContent(Prototype.getJFrame(), false).setVisible(true);
          	}

          Die Methode sendData() würde nun auf den Firmenserver zurückgreifen und Daten an die Datenbank senden. Da dies den Rahmen des Tutorials sprengen würde wollen wir hier nur die Stringbestätigung ausgeben, die unsere Mocksoftware für die Gesendeten Daten retourniert.

          	public static void sendData() {
          		MockCompanySoftware mcs = new MockCompanySoftware();
          		JOptionPane.showMessageDialog(null, mcs.setKundendaten(null));
          	}

          Auch der OK Button wurde nun mit Funktionalität ausgestattet.

          Vorschau und Aufgabenstellung

          Nun da die beiden Buttons reagieren fehlt uns noch das Menü. Hierfür ist ebenfalls ein ActionListener zu implementieren. Hier noch die Ausgangs-Sourcen für diese Aufgabenstellung.

          Viel Erfolg dabei!

          Im nächsten und abschließenden Teil dieser kleinen Serie möchte ich noch kurz die Möglichkeiten für Nebenläufigkeit in Java Swing aufzeigen.

          Ähnliche Artikel:

          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:

              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: