Wie kann ein GridLayout geändert werden?

Durch Überschreiben der Methode layoutContainer(Container parent) kann die Aufbaureihenfolge eines GridLayout von zeilenweise nach spaltenweise geändert werden.

Java definiert den LayoutManager GridLayout, bei dem alle Komponenten rasterartig in gleich großen Feldern angeordnet werden. Die Reihenfolge der geladenen Komponenten verläuft dabei von oben zeilenweise jeweils in der für die Komponente festgelegten Richtung entweder von links nach rechts oder umgekehrt. Bleiben Zellen frei, so liegen diese demnach nebeneinander in der jeweils untersten Reihe.

Das folgende Beispiel zeigt dies anhand von sieben JButton in einem GridLayout mit drei Zeilen und drei Spalten.

Anordnung von Komponenten in einem GridLayout
GridLayout mit horizontalem Verlauf von links nach rechts
public class GridLayoutBsp {

    public GridLayoutBsp() {
        init();
    }

    private void init() {
        JPanel panel = new JPanel();
        panel.setLayout(new GridLayout(3,3));
        panel.add(new JButton("Button eins"));
        panel.add(new JButton("Button zwei ohne Border"));
        panel.add(new JButton("Button drei"));
        panel.add(new JButton("Button vier"));
        panel.add(new JButton("Button f\u00fcnf"));
        panel.add(new JButton("Button sechs"));
        panel.add(new JButton("sieben"));
        JFrame frame = new JFrame();
        frame.add(panel);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setTitle("Vertical Grid Layout");
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new GridLayoutBsp());
    }
}

Um den Verlauf der Komponenten von horizontal nach vertikal abzuändern, bieten sich zwei Möglichkeiten an.

  1. Durch Implementierung des Interface LayoutManager kann eine völlig neue Layout-Klasse geschrieben werden.
  2. Durch Erweitern der Klasse GridLayout und Überschreiben von public void layoutContainer(Container parent) werden auf einfache Weise alle sonstigen Eigenschaften von GridLayout geerbt.

Da das beabsichtigte Ziel der Abänderung des GridLayout, abgesehen von der Reihenfolge, keine darüber hinausgehende Änderung der Komponenten-Positionen beinhaltet, bietet sich die Methode der Erweiterung an.
Hierzu werden in der neuen Klasse die Konstruktoren übernommen. Sie verweisen dann, durch Aufruf von super(), auf ihre Pendants in der Superklasse GridLayout. Wie bereits oben erwähnt muss dann nur noch layoutContainer() überschrieben werden. In dieser Methode findet die eigentliche Layout-Arbeit statt, die Positionierung aller Komponenten des Zielcontainers.

Überschreiben von layoutContainer(Container parent)

Die gesamte Routine steht in einem synchronized-Block, um das Zeichnen der Komponenten Thread-sicher zu gestalten.
Hier werden zunächst die Randabstände (Insets) des Containers ermittelt, bevor die folgenden Eigenschaften in Variablen abgelegt werden:

int ncomponents
Anzahl der im Container enthaltenen Komponenten
int nrows
Zahl der durch das Layout zu erzeugenden Reihen
int ncols
Zahl der durch das Layout zu erzeugenden Spalten
boolean ltr
Boolscher Wert für die Container-eigene Orientierungsrichtung, in der Komponenten in ihm gezeichnet werden; true wenn dies von links nach rechts erfolgt.
public class VerticalGridLayout extends GridLayout {

    public VerticalGridLayout() {
        super();
    }

    public VerticalGridLayout(int rows, int cols) {
        super(rows, cols);
    }

    public VerticalGridLayout(int rows, int cols, int hgap, int vgap) {
        super(rows, cols, hgap, vgap);
    }

    public void layoutContainer(Container parent) {
        synchronized (parent.getTreeLock()) {
            Insets insets = parent.getInsets();
            int ncomponents = parent.getComponentCount();
            int nrows = getRows();
            int ncols = getColumns();
            boolean ltr = parent.getComponentOrientation().isLeftToRight();

            if (ncomponents == 0) {
                return;
            }
            if (nrows > 0) {
                ncols = (ncomponents + nrows - 1) / nrows;
            } else {
                nrows = (ncomponents + ncols - 1) / ncols;
            }
            // Fensterdimensionen ohne Kopfleiste
            int w = parent.getWidth() - (insets.left + insets.right);
            int h = parent.getHeight() - (insets.top + insets.bottom);
            // Platzbedarf einer Komponente mit gap
            w = (w - (ncols - 1) * getHgap()) / ncols;
            h = (h - (nrows - 1) * getVgap()) / nrows;

            if (ltr) {
                for (int c = 0, x = insets.left; c < ncols; c++, x += w + getHgap()) {
                    for (int r = 0, y = insets.top; r < nrows; r++, y += h + getVgap()) {
                        int i = c * nrows + r;
                        if (i < ncomponents) {
                            parent.getComponent(i).setBounds(x, y, w, h);
                        }
                    }
                }
            } else {
                for (int c = 0, x = parent.getWidth() - insets.right - w; c < ncols; c++, x -= w + getHgap()) {
                    for (int r = 0, y = insets.top; r < nrows; r++, y += h + getVgap()) {
                        int i = c * nrows + r;
                        if (i < ncomponents) {
                            parent.getComponent(i).setBounds(x, y, w, h);
                        }
                    }
                }
            }
        }
    }
}

In der Elternklasse wird innerhalb der Konstruktoren sichergestellt, dass mindestens eine der Angaben für die Zeilen- und Spaltenzahl größer null ist, somit können hieraus, unter Berücksichtigung der Komponentenzahl, die benötigen Anzahlen von Zeilen und Spalten berechnet werden. Da alle Komponenten im GridLayout in der selben Größe dargestellt werden, sind dann aus der Containergröße die zur Verfügung stehenden Breiten und Höhen der einzelnen Komponenten zu ermittelt. Sie werden in den beiden Variablen w und h abgelegt.

Bei der eigentlichen Positionierung der Komponenten wird dann in zwei Blöcken zwischen der o.a. Ladereihenfolge von links nach rechts bzw. der gegenläufigen unterschieden, weil dies natürlich Auswirkungen auf die horizontalen Positionen der einzelnen Komponenten hat. Das Grundprinzip der Berechnung bleibt jedoch ähnlich. In der äußeren Schleife werden die horizontalen, in der inneren die vertikalen Positionen berechnet.
Für die Links-Rechts-Orientierung wird in der äußeren Schleife der Startpunkt x in der linken oberen Ecke des Containers unter Berücksichtigung der Insets festgelegt. Der x-Wert dieses Punktes wird bei jedem Schleifendurchlauf schrittweise um eine Komponentenbreite und einen horizontalen Zwischenraum incrementiert. In der inneren Schleife wird Entsprechendes für den y-Wert der vertikalen Position durchgeführt.

Die Berechnung des Array-Indexes der zu setzenden Komponente geschieht dann folgendermaßen: Die Komponenten werden spaltenweise gesetzt, sodass im vorliegenden Beispiel mit drei Reihen und drei Spalten die Indices der ersten Spalte 0, 1 und 2 lauten. Multipliziert man mit diesen die Gesamtzahl der Reihen (3) erhält man jeweils den Index des ersten Feldes einer Spalte (0, 3, 6). Addiert man dazu jeweils die Reihennummer, erhält man den endgültigen Index der zu ladenden Komponente. Sie wird durch setBounds() unter Übergabe der Parameter für Position und Größe positioniert.

VerticalFlowLayout mit Aufbau von links nach rechts
VerticalGridLayout mit Aufbau von links nach rechts
VerticalFlowLayout mit Verlauf von rechts nach links
VerticalGridLayout mit Verlauf von rechts nach links

Zum Ausführen des Layouts reicht es dann aus, in der oben gelisteten Klasse GridLayoutBsp die Zeile

panel.setLayout(new GridLayout(3,3));

durch

panel.setLayout(new VerticalGridLayout(3,3));

auszutauschen und den Frametitel anzupassen.