D) Einen Layout-Manager anpassen
Manchmal kommt es vor, dass Sie auf einen komplexeren Layout-Manager wie das GridBagLayout
zurückgreifen müssen, obwohl ein bereits bestehender, einfacherer Layout-Manager nur geringfügig von Ihren Anforderungen abweicht. Benötigen Sie diese Art von Layout-Manager auch noch häufiger in Ihrer Anwendung, kann es unter Umständen sinnvoll sein, wenn Sie den einfacheren Layout-Manager anpassen. Dieses Szenario spielen wir in diesem Kapitel anhand des FlowLayouts
durch.
Das FlowLayout
ist ein sehr simples Layout zur Anordnung von GUI-Elementen auf einem Container, welches die Komponenten „fließend“ hintereinander anordnet. Ein Problem hierbei ist, dass das FlowLayout
am Ende des möglichen Darstellungsbereichs des Containers nicht etwa die restlichen Komponenten in die nächste Zeile verschiebt, sondern einfach am Rand abschneidet. In diesem Kapitel erweitern Sie das FlowLayout
zum ExtendedFlowLayout
, welches das Problem mit einem Zeilenumbruch umgeht.
Ist-Situation, Soll-Situation
Betrachten wir oben genanntes Problem an einem einfachen Beispiel. Wir setzen einem JFrame
ein BorderLayout
und füllen selbiges im nördlichen Bereich mit einem JPanel
mit gesetztem FlowLayout
. In diesem JPanel
befinden sich drei JLabels
. Zur besseren Unterscheidung werden die einzelnen Texte mit unterschiedlicher Hintergrundfarbe hinterlegt:
import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; public class Test { public static void main(String[] args) { JFrame frame = new JFrame("ExtendedFlowLayout vs. FlowLayout"); JPanel normalFlow = new JPanel(); JLabel n1 = new JLabel("Das ist ein Label mit viel Text (FL)"); JLabel n2 = new JLabel("Das ist noch ein Label mit viel Text (FL)"); JLabel n3 = new JLabel("Und das ist ein drittes Label mit viel Text (FL)"); n1.setBackground(Color.YELLOW); n2.setBackground(Color.RED); n3.setBackground(Color.GREEN); n1.setOpaque(true); n2.setOpaque(true); n3.setOpaque(true); frame.setLayout(new BorderLayout()); normalFlow.setLayout(new FlowLayout(FlowLayout.LEFT)); frame.add(normalFlow, BorderLayout.NORTH); frame.add(new JLabel("Platzhalter"), BorderLayout.CENTER); normalFlow.add(n1); normalFlow.add(n2); normalFlow.add(n3); frame.setSize(450, 100); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }
Wenn Sie dieses kleine Programm ausführen, bekommen Sie eine solche Ansicht:
Sie sehen also nur zwei der ursprünglich drei JLabels
. Ziehen Sie das Fenster ein bisschen größer, erscheint wie von Geisterhand das dritte JLabel
:
Dies ist so natürlich nicht akzeptabel. Natürlich könnte ein JScrollPane
eingesetzt werden, aber dies ist auch nicht immer erwünscht. Deshalb erarbeiten wir uns ein ExtendedFlowLayout
, das für den benötigten Zeilenumbruch sorgt. Wenn unser ExtendedFlowLayout
fertig ist, können wir das obige FlowLayout
durch unser ExtendedFlowLayout
ersetzen, und erhalten dann folgende Anzeige:
Die Hülle
Natürlich müssen nur ganz bestimmte Bereiche des FlowLayouts
angepasst werden (sonst könnten Sie sich gleich von 0 an Ihren eigenen Layout-Manager schreiben). Es gilt diese Methoden immer im Vorfeld durch Studium des Ursprungs-Quellcodes herauszufiltern: Die Methoden preferredLayoutSize
, die die bevorzugte Größe des Containers berechnet, und minimumLayoutSize
, die die minimale Größe des Containers berechnet, sind relevant für uns. Sehen Sie sich hierzu zum besseren Verständnis den Quellcode des FlowLayouts
an.
Den Quellcode zu Standardklassen des JDKs finden Sie in Ihrem JDK-Installationsordner im gepackten Archive src.zip.
Erstellen Sie sich nun eine Klasse, die von FlowLayout
erbt, alle Standardkonstruktoren implementiert, und die oben angesprochenen Methoden überschreibt.
package de.jbb.tools; import java.awt.Container; import java.awt.Dimension; import java.awt.FlowLayout; public class ExtendedFlowLayout extends FlowLayout { private static final long serialVersionUID = 1L; public ExtendedFlowLayout() { super(); } public ExtendedFlowLayout(int arg0, int arg1, int arg2) { super(arg0, arg1, arg2); } public ExtendedFlowLayout(int arg0) { super(arg0); } public Dimension preferredLayoutSize(Container target) { return null; } public Dimension minimumLayoutSize(Container target) { return null; } }
Originalimplementierung
Die Originalimplementierung unserer überschriebenen Methoden sehen so aus:
public Dimension preferredLayoutSize(Container target) { synchronized (target.getTreeLock()) { Dimension dim = new Dimension(0, 0); int nmembers = target.getComponentCount(); boolean firstVisibleComponent = true; boolean useBaseline = getAlignOnBaseline(); int maxAscent = 0; int maxDescent = 0; for (int i = 0; i < nmembers; i++) { Component m = target.getComponent(i); if (m.isVisible()) { Dimension d = m.getPreferredSize(); dim.height = Math.max(dim.height, d.height); if (firstVisibleComponent) { firstVisibleComponent = false; } else { dim.width += this.hgap; } dim.width += d.width; if (useBaseline) { int baseline = m.getBaseline(d.width, d.height); if (baseline >= 0) { maxAscent = Math.max(maxAscent, baseline); maxDescent = Math.max(maxDescent, d.height - baseline); } } } } if (useBaseline) { dim.height = Math.max(maxAscent + maxDescent, dim.height); } Insets insets = target.getInsets(); dim.width += insets.left + insets.right + this.hgap * 2; dim.height += insets.top + insets.bottom + this.vgap * 2; return dim; } } public Dimension minimumLayoutSize(Container target) { synchronized (target.getTreeLock()) { boolean useBaseline = getAlignOnBaseline(); Dimension dim = new Dimension(0, 0); int nmembers = target.getComponentCount(); int maxAscent = 0; int maxDescent = 0; boolean firstVisibleComponent = true; for (int i = 0; i < nmembers; i++) { Component m = target.getComponent(i); if (m.visible) { Dimension d = m.getMinimumSize(); dim.height = Math.max(dim.height, d.height); if (firstVisibleComponent) { firstVisibleComponent = false; } else { dim.width += this.hgap; } dim.width += d.width; if (useBaseline) { int baseline = m.getBaseline(d.width, d.height); if (baseline >= 0) { maxAscent = Math.max(maxAscent, baseline); maxDescent = Math.max(maxDescent, dim.height - baseline); } } } } if (useBaseline) { dim.height = Math.max(maxAscent + maxDescent, dim.height); } Insets insets = target.getInsets(); dim.width += insets.left + insets.right + this.hgap * 2; dim.height += insets.top + insets.bottom + this.vgap * 2; return dim; } }
Da sich die beiden Methoden sehr ähnlich sind, betrachten wir hier nur die Methode preferredLayoutSize
.
Dieser Methode wird der Container
übergeben, auf welchem die auszurichtenden Komponenten hinzugefügt wurden. In der Methode selbst werden alle Komponenten der Reihe nach angesehen, und die Größe der Komponenten mitsamt Platzhaltern zur Gesamtgröße des Containers hinzugefügt. Dies geschieht durch die Zeilen
Dimension dim = new Dimension(0, 0); // Container Dimension int nmembers = target.getComponentCount(); // Anzahl der Komponenten auf dem Container boolean firstVisibleComponent = true; // Erste Komponente in diesem Container ... for (int i = 0; i < nmembers; i++) { // Alle Komponenten durchlaufen Component m = target.getComponent(i); // Aktuelle Komponente auswählen if (m.isVisible()) { // Nur berücksichtigen, falls die Komponente auch sichtbar ist Dimension d = m.getPreferredSize(); // Die Größe der Komponente auslesen dim.height = Math.max(dim.height, d.height); // Falls die aktuelle Komponente höher als die // aktuelle Größe des Container ist, die Höhe // der aktuellen Komponente zuweisen if (firstVisibleComponent) { // Falls es sich um die erste Komponente handelt ... firstVisibleComponent = false; // ... nichts machen, außer die Variable auf false setzen } else { // ... ansonsten ... dim.width += this.hgap; // ... den vertikalen Platzhalter hgap addieren } dim.width += d.width; // Die Breite der Dimension um die Breite der Komponente erhöhen ... return dim;
Die zurückgegebene Dimension
entspricht der empfohlenen/minimalen Größe des übergebenen Containers.
Auf der nächsten Seite werden wir die gegebenen Methoden nach unseren Wünschen modifizieren.
Super Artikel, löst gerade mein Problem!
Eigentlich hätte ich erwartet, dass das FlowLayout von Swing genau so funktioniert wie jetzt das ExtendedFlowLayout…