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:

FlowLayout Fehler

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:

FlowLayout richrig

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:

ExtendedFlowLayout

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.

One Reply to “D) Einen Layout-Manager anpassen”

  1. PeterF

    Super Artikel, löst gerade mein Problem!
    Eigentlich hätte ich erwartet, dass das FlowLayout von Swing genau so funktioniert wie jetzt das ExtendedFlowLayout…

Schreibe einen Kommentar

Diese Website verwendet Akismet, um Spam zu reduzieren. Erfahre mehr darüber, wie deine Kommentardaten verarbeitet werden.