GroupLayout für Homosapiens
Inhaltsverzeichnis
GroupLayout für Homosapiens:
Das GroupLayout ist ein mächtiger LayoutManager, der leider oft als komplizierter missverstanden wird, als er eigentlich ist. Besonders GUI-Builder erzeugen meist sehr hässlichen Code mit dem Grouplayout, weshalb ich euch rate erst mal ein GroupLayout von Hand zu schreiben. GroupLayout wurde mit dem Ziel geschaffen, von GUI Buildern verwendet zu werden, von Hand programmiert, liegt der Schreibaufwand etwa wie beim GridBagLayout.
Einem JPanel ein GroupLayout zu verpassen funktioniert erstmal nicht ganz genauso wie bei anderen LayoutManagern:
<code=java>JPanel panel=new JPanel(); GroupLayout layout = new GroupLayout(panel); panel.setLayout(layout);</code=java>
Vorsicht: Das GroupLayout wirft sehr schnell mal Exceptions wenn bei der initialisierung etwas schief gelaufen ist (z.B ein Komponent vergessen). Davon nicht abschrecken lassen! Andere LayoutManager kaschieren manche Ungereimtheiten und zeigen trotzdem was an. Das GroupLayout ist da wohl eher auf Perfektionismus ausgelegt: richtig oder gar nicht.
Zu den Komponenten:
Auch hier geht das GroupLayout wieder einen eigenen Weg: Komponenten werden nicht per panel.add(Component c) "hinzugefügt". Man fasst die Komponenten in Gruppen zusammen und setzt diese in Beziehungen zueinander. Anders als bei den GridBagConstraints wurde beim GroupLayout dieses "Beziehungen setzen" aber zweigeteilt: Man gibt die Größenverhältnisse einmal vertikal und einmal horizontal an:
Beispiel: Wir imitieren ein FlowLayout mit 3 Labels:
<code=java>JLabel label1=new JLabel("1"); JLabel label2=new JLabel("2"); JLabel label3=new JLabel("3");</code=java>
1. Beziehungen auf vertikaler Ebene:
Wir wollen, dass alle 3 Labels in der gleichen Zeile stehen. Sehen wir von "Norden" aus auf unser Layout drauf (man beuge sich über den Bildschirm und blicke nach unten), sind die 3 Labels nebeneinander. Wenn wir 2 oder mehr Komponenten nebeneinander ausrichten wollen, benötigen wir eine ParallelGroup:
<code=java>ParallelGroup verticalGroup=layout.createParallelGroup();</code=java>
Dieser ParallelGroup werden jetzt die Komponenten hinzugefügt:
<code=java>verticalGroup.addComponent(label1).addComponent(label2).addComponent(label3);</code=java>
2. Beziehungen auf horizontaler Ebene:
Sehen wir von "Westen" auf unser Layout (man stelle sich links neben den Bildschirm und blicke hinein), sind die 3 Labels hintereinander. Dafür gibts die SequentialGroup:
<code=java>SequentialGroup horizontalGroup=layout.createSequentialGroup;
horizontalGroup.addComponent(label1).addComponent(label2).addComponent(label3);</code=java>
Zum Abschluss müssen wir unsere erstellten Gruppen noch dem Layout mit folgenden Methoden zuweisen:
<code=java>layout.setVerticalGroup(verticalGroup); layout.setHorizontalGroup(horizontalGroup);</code=java>
Jetzt noch panel in einen JFrame packen und fertig, unser erstes GroupLayout läuft!
Wichtig: Wenn du einen Komponenten nur zu einer Group addest, fliegt eine Exception und es wird nichts angezeigt!
VerticalFlowLayout
Aber was nützt uns ein FlowLayout? Schon so mancher hat den Bedarf nach einem VerticalFlowLayout angemeldet. Mit GroupLayout kein Problem: wir vertauschen einfach vertical- und horizontalGroup.
<code=java>SequentialGroup verticalGroup=layout.createSequentialGroup(); verticalGroup.addComponent(label1).addComponent(label2).addComponent(label3);
ParallelGroup horizontalGroup=layout.createParallelGroup; horizontalGroup.addComponent(label1).addComponent(label2).addComponent(label3);</code=java>
Standard GUI
Wie sieht es nun mit komplexeren Masken aus? Ich habe früher versucht aus zig verschiedenen, verschachtelten Panels mit jeweils eigenem einfachen LayoutManagern eine GUI zusammenzubasteln. Der Aufwand war enorm, die Übersichtlichkeit schrecklich, es war fehleranfällig und meist kam sowieso nicht das raus was gewollt war. Mit dem GroupLayout kann man sich das Panel verschachteln sparen, man verschachtelt die Groups. Das schafft zwar das Problem des Verschachtelns nicht aus der Welt, man kann sich aber besser auf die eine Sache konzentrieren weil man sich eine Menge Panels spart.
Beispiel: Wir erweitern unser VerticalFlowGroupLayout: Da soll in jede Zeile noch zusätzlich ein Textfeld und ein Button rein:
1. Vertikale Group:
Wir stellen uns wieder vor, wir blicken von oben in unser Layout rein. Jetzt gibt’s zwei Betrachtungsweisen: Haben wir 3 Zeilen hintereinander, in denen je ein Label, ein Textfeld und ein Button nebeneinander stehen oder haben wir 3 Spalten nebeneinander mit jeweils 3 Labels, Textfeldern und Buttons hintereinander? Die Entscheidung bleibt dir überlassen, ich werde es hier anhand des „3 Zeilen“ Ansatzes demonstrieren:
<code=java>SequentialGroup verticalGroup=layout.createSequentialGroup();
verticalGroup.addGroup(layout.createParallelGroup() .addComponent(label1).addComponent(textField1).addComponent(button1) ); verticalGroup.addGroup(layout.createParallelGroup() .addComponent(label2).addComponent(textField2).addComponent(button2) ); verticalGroup.addGroup(layout.createParallelGroup() .addComponent(label3).addComponent(textField3).addComponent(button3) );</code=java>
2. Horizontale Group
Analog zur Vertikalen Group: Haben wir 3 Zeilen nebeneinander in denen Label, Textfeld und Button hintereinander stehen oder haben wir 3 Spalten hintereinander, in denen untereinander je 3 Labels, Textfelder oder Buttons stehen? W man sich beim Vertikalen Teil für die Zeilen Denkweise entschieden hat, ist es mMn sinnvoll dabei zu bleiben:
<code=java>ParallelGroup horizontalGroup=layout.createParallelGroup();
horizontalGroup.addGroup(layout.createSequentialGroup() .addComponent(label1).addComponent(textField1).addComponent(button1) ); horizontalGroup.addGroup(layout.createSequentialGroup() .addComponent(label2).addComponent(textField2).addComponent(button2) ); horizontalGroup.addGroup(layout.createSequentialGroup() .addComponent(label3).addComponent(textField3).addComponent(button3) );</code=java>
In meinem StandardFrame mit fixer Größe sieht das ganze nun aber so aus:
Die Komponenten füllen den gesamten Platz der aus – wir brauchen soetwas wie den Glue aus dem BoxLayout. Im GroupLayout heißt das Gap was die Sache mMn auch besser beschreibt (wer will denn Kleber in seiner GUI?).
Gaps und Größenangaben
Erstens gibt’s da mal eine Komfortfunktion die man ich eigentlich immer aktiviert habe:
<code=java>layout.setAutoCreateGaps(true);</code=java>
Damit kleben die Komponenten nicht mehr so aneinander, wie man das auch vom GridLayout her kennt.
Zu unserm Problem, wir können Gaps genauso wie Komponenten einer Gruppe hinzufügen:
<code=java>verticalGroup.addGap(Short.MAX_VALUE);</code=java>
addGap erwartet einen Integer Wert als Parameter, der angibt wie groß der Gap sein soll. Short.MAX_VALUE liegt bei ca. 32000, so große Displays gibt’s noch nicht.
Man kann addGap aber auch 3 ints übergeben: addGap(int min,int pref, int max) Man kann also Minimum, Preferred und Maximum Size einstellen. Das funktioniert genauso bei Komponenten:
Ändern wir den Code auf
<code=java>verticalGroup.addGroup(layout.createParallelGroup() .addComponent(label1) . addComponent(textField1,20,20,20) .addComponent(button1) );</code=java>
Wird unser Textfield1 immer genau 20 Pixel hoch sein. Achtung: Die Größenangaben beziehen sich immer auf die Achse, in der sie gemacht werden. Angaben in der VerticalGroup beeinflussen nur die Höhe, Angaben in der HorizontalGroup nur die Breite.
Die Größenangaben manuell anzugeben ist allerdings kein sauberer Stil. Komponenten haben ja selber schon min, preferred und max Size Einstellungen, die werden dadurch einfach ignoriert. Um dem GroupLayout zu sagen, es soll immer auf eine dieser Methoden zurückgreifen gibt’s 2 Konstanten: GroupLayout.DEFAULT_SIZE und GroupLayout.PREFERRED_SIZE
<code=java>verticalGroup.addGroup(layout.createParallelGroup()
.addComponent(label1) .addComponent(textField1,GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,GroupLayout.PREFERRED_SIZE) .addComponent(button1) );</code=java>
Jetzt kann das Textfeld maximal die Höhe seiner PreferredSize haben.
Beispiel: Wir wollen links unten in unserem Fenster einen OK Button einfügen.
Wir fügen einen Gap zwischen Zeile 3 und dem Ok Button ein:
<code=java>verticalGroup.addGap(0,0,Short.MAX_VALUE); verticalGroup.addComponent(okButton);
horizontalGroup.addGroup(layout.createSequentialGroup()
.addGap(0,0,Short.MAX_VALUE) .addComponent(okButton));</code=java>
ein (0,0,Short.MAX_VALUE) Gap füllt soviel Platz aus, wie ihm zur Verfügung steht. Egal wie groß ihr das Fenster macht, der OK Button ist immer rechts unten.
Fortgeschrittenes:
Beispiel: Wir stellen die Font eines unserer Labels auf 80 und wollen, dass die Textfeld und Button in der Mitte der Zeile angezeigt werden. Wir setzten für unsere Textfelder unterschiedliche Preferred Sizes und wollen dass die Buttons alle schön untereinander stehen und gleich breit sein. Ausserdem soll das Layout nur so viel Platz wie nötig ausfüllen.
Erstmal setzten wir ganz normal die Preferred Size der Textfelder und die Font des Labels:
<code=java>Font f = new Font( "Arial", Font.PLAIN, 80 ); label1.setFont(f);
textField1.setPreferredSize(new Dimension(70,20)); textField2.setPreferredSize(new Dimension(100,20)); textField3.setPreferredSize(new Dimension(130,20));</code=java>
Problem 1: Die Komponenten einer Zeile der Höhe nach mittig ausrichten: ParallelGroups kann man beim erzeugen einen Alignment Parameter übergeben. (Alignment. LEADING, TRAILING, CENTER oder BASELINE
<code=java>verticalGroup.addGroup(layout.createParallelGroup(Alignment.CENTER) .addComponent(label1) .addComponent(textField1,GroupLayout.DEFAULT_SIZE,GroupLayout.DEFAULT_SIZE,GroupLayout.PREFERRED_SIZE) .addComponent(button1) );</code=java>
Problem 2: Quasi ein Tabstopp für die Buttons. Wir haben nicht mehr einfach 4 Zeilen, wir haben 4 Zeilen und 2 Spalten.
Die HorizontalGroup muss deshalb verändert werden: Wir stellen uns vor, wir schauen von „Westen“/Links auf unser Layout. Da wir in unserem Layout 2 Spalten haben (Rote Rechtecke), die klar abgegrenzt hintereinander kommen sollen Brauchen wir erstmal eine SequentialGroup. SequentialGroup horizontalGroup=layout.createSequentialGroup();
Jetzt betrachten wir erstmal nur das linke Rechteck (um das rechte kümmern wir uns nachher). 3 Zeilen übereinander mit je Label und Textfeld hintereinander oder 3 Labels übereinander, danach 3 Textfelder übereinander. (Wir machens auf die Zeilen Art).
<code=java>ParallelGroup labelTextfieldGroup=layout.createParallelGroup()
.addGroup(layout.createSequentialGroup() .addComponent(label1) .addComponent(textField1,GroupLayout.DEFAULT_SIZE,GroupLayout.DEFAULT_SIZE,GroupLayout.PREFERRED_SIZE) ) .addGroup(layout.createSequentialGroup() .addComponent(label2) .addComponent(textField2,GroupLayout.DEFAULT_SIZE,GroupLayout.DEFAULT_SIZE,GroupLayout.PREFERRED_SIZE) ) .addGroup(layout.createSequentialGroup() .addComponent(label3) .addComponent(textField3,GroupLayout.DEFAULT_SIZE,GroupLayout.DEFAULT_SIZE,GroupLayout.PREFERRED_SIZE) );</code=java>
Dann noch die 4 Buttons übereinander, das ist wieder einfacher:
<code=java>ParallelGroup buttonGroup=layout.createParallelGroup(Alignment.TRAILING)
.addComponent(button1) .addComponent(button2) .addComponent(button3) .addComponent(okButton);</code=java>
Jetzt stöpseln wir die zwei roten Rechteck-Gruppen zusammen:
<code=java>horizontalGroup.addGroup(labelTextfieldGroup).addGroup(buttonGroup);</code=java>
Problem 3: Den Buttons die gleiche Breite verpassen.
Dafür gibt’s die Funktion linkSize:
<code=java>layout.linkSize(SwingConstants.HORIZONTAL,button1,button2,button3,okButton);</code=java>
SwingConstants.HORIZONTAL gibt an ob man die Breite oder die Höhe verlinken will. Danach kann man beliebig viele Komponenten hinzuschreiben (varargs).
Weiterführende Links:
API-Dokumentation vom GroupLayout
Oracle: How to Use GroupLayout (The Java™ Tutorials > Creating a GUI With JFC/Swing > Laying Out Components
--bERt0r 04:06, 16. Dez 2011 (CET)