GridBagLayout

Status
Nicht offen für weitere Antworten.
B

Beni

Gast
[size=+2]GridBagLayout[/size]
Das [JAPI]GridBagLayout[/JAPI] ist wohl der am meisten Probleme verursachende [JAPI]LayoutManager[/JAPI]. Deshalb soll dieser Artikel einige Unklarheiten beseitigen.

Wie bei den meisten LayoutManagern wird auch beim GridBagLayout jede [JAPI]Component[/JAPI] mit einem Layout-Objekt, den [JAPI]GridBagConstraints[/JAPI] verbunden. Dieses Layout-Objekt sagt dem LayoutManager wo und wie die Component erscheinen soll:

Bitte beachtet auch noch die Links ganz am Ende dieses Textes.

[size=+2]Das Gitter[/size]
Das GridBagLayout erzeugt ein Gitter in dem die Componenten ausgerichtet werden. Welche und wieviele Gitterzellen überdeckt werden sollen, kann mit den gridx/y/width/height Variablen von GridBagConstraints festgelegt werden.
  • GridBagConstraints.gridx sagt, welches die erste überdeckte Zelle von links her ist
  • GridBagConstraints.gridy sagt, welches die erste überdeckte Zelle von oben her ist
  • GridBagConstraints.gridwidth wieviele Zellen in horizontaler Richtung überdeckt werden
  • GridBagConstraints.gridheight wieviele Zellen in vertikaler Richtung überdeckt werden

Im folgenden werde ich jeweils ein kleines Beispiel angeben. Das Demonstrationsprogramm zeichnet das Gitter des GridBagLayouts auf, und bietet damit einen Einblick in das Layout.

Im ersten Beispiel wurden 3 JButtons erzeugt. Der ersten Button "top" hat die Grenzen 0/0/2/1, der zweite Button "bottomLeft" die Grenzen 0/1/1/1, und der letzte Button "bottomRight" schliesslich 1/1/1/1.

Wie man sieht, überdeckt der erste Button in der horizontalen zwei Gitterzellen, da sein gridwidth auf 2 gesetzt wurde:

beni-albums-faq-picture10-gbl1.png


[HIGHLIGHT=Java]import java.awt.*;

import javax.swing.*;

public class Demo extends JLayeredPane{
public static void main( String[] args ) throws Exception{
UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName() );
Demo panel = new Demo();

JFrame frame = new JFrame( "GridBagLayout" );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.add( panel );

JButton top = new JButton( "top" );
GridBagConstraints topConstraints = new GridBagConstraints();
topConstraints.gridx = 0;
topConstraints.gridy = 0;
topConstraints.gridwidth = 2;
topConstraints.gridheight = 1;

JButton bottomLeft = new JButton( "bottom left" );
GridBagConstraints bottomLeftConstraints = new GridBagConstraints();
bottomLeftConstraints.gridx = 0;
bottomLeftConstraints.gridy = 1;
bottomLeftConstraints.gridwidth = 1;
bottomLeftConstraints.gridheight = 1;

JButton bottomRight = new JButton( "bottom right" );
GridBagConstraints bottomRightConstraints = new GridBagConstraints();
bottomRightConstraints.gridx = 1;
bottomRightConstraints.gridy = 1;
bottomRightConstraints.gridwidth = 1;
bottomRightConstraints.gridheight = 1;

panel.add( top, topConstraints );
panel.add( bottomLeft, bottomLeftConstraints );
panel.add( bottomRight, bottomRightConstraints );

frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}

/*
* Alles was jetzt noch folgt ist lediglich dazu da, das Gitter welches
* vom GridBagLayout benutzt wird, aufzuzeichnen.
*/
private JPanel content;
private JComponent glass;
private GridBagLayout layout;

public Demo(){
setLayout( new OverlayLayout( this ) );


layout = new GridBagLayout();
content = new JPanel( layout );
content.setBorder( BorderFactory.createEmptyBorder( 2, 2, 2, 2 ) );
content.setOpaque( true );
content.setBackground( Color.WHITE );

glass = new JComponent(){
@Override
protected void paintComponent( Graphics g ) {
Point origin = layout.getLayoutOrigin();
int[][] dimensions = layout.getLayoutDimensions();
g = g.create();

g.setColor( Color.BLACK );
((Graphics2D)g).setStroke( new BasicStroke( 2f ) );

int x = origin.x;
int y = origin.y;
int w = 0;
int h = 0;

for( int i = 0, n = dimensions[0].length; i<n; i++ )
w += dimensions[0];
for( int i = 0, n = dimensions[1].length; i<n; i++ )
h += dimensions[1];

int tx = x;
int ty = y;
for( int i = 0, n = dimensions[0].length; i<=n; i++ ){
g.drawLine( tx, y, tx, y+h );
if( i<n )
tx += dimensions[0];
}

for( int i = 0, n = dimensions[1].length; i<=n; i++ ){
g.drawLine( x, ty, x+w, ty );
if(i<n)
ty += dimensions[1];
}

g.dispose();
}
@Override
public boolean contains( int x, int y ) {
return false;
}
};
glass.setOpaque( false );

super.add( content );
setLayer( content, DEFAULT_LAYER );

super.add( glass );
setLayer( glass, MODAL_LAYER );
}

@Override
public void add( Component component, Object constraints ){
content.add( component, constraints );
}
}[/HIGHLIGHT]

[size=+2]Grössenrelationen[/size]
Solange nichts anderes spezifiziert ist, versucht das GridBagLayout jeder Component ihre preferredSize (=optimale Grösse) zuzuweisen. Das kann dann lustige Effekte geben:

beni-albums-faq-picture11-gbl2.png

[HIGHLIGHT=Java]JButton top = new JButton( "top" );
GridBagConstraints topConstraints = new GridBagConstraints();
topConstraints.gridx = 0;
topConstraints.gridy = 0;
topConstraints.gridwidth = 2;
topConstraints.gridheight = 1;

JButton bottomLeft = new JButton( "<html>bottom<br>left</html>" );
GridBagConstraints bottomLeftConstraints = new GridBagConstraints();
bottomLeftConstraints.gridx = 0;
bottomLeftConstraints.gridy = 1;
bottomLeftConstraints.gridwidth = 1;
bottomLeftConstraints.gridheight = 1;

JButton bottomRight = new JButton( "a very very long and useless text" );
GridBagConstraints bottomRightConstraints = new GridBagConstraints();
bottomRightConstraints.gridx = 1;
bottomRightConstraints.gridy = 1;
bottomRightConstraints.gridwidth = 1;
bottomRightConstraints.gridheight = 1;[/HIGHLIGHT]

Es ist aber auch möglich die Grösse der Gitterzellen relativ zueinander zu bestimmen. Dazu werden weightx/y verwendet:
  • GridBagConstraints.weightx bestimmt das "Gewicht" einer Component in horizontaler Richtung
  • GridBagConstraints.weighty bestimmt das "Gewicht" einer Component in vertikaler Richtung

Wenn wir z.B. dem oberen Button weighty auf 2, und die der anderen Buttons auf 1 setzen, wird die obere Gitterzeile höher. Wenn wir gleichzeitig noch weightx des Buttons unten rechts auf 2 setzen, wird die zweite Spalte breiter:
beni-albums-faq-picture12-gbl3.png


Auch schön zu sehen ist, wie das GridBagLayout diese Einstellung umgehen kann wenn es Platzprobleme gibt. Dann versucht das GridBagLayout jeder Component zumindest eine minimale Grösse zu geben:
beni-albums-faq-picture13-gbl4.png


[HIGHLIGHT=Java]JButton top = new JButton( "top" );
GridBagConstraints topConstraints = new GridBagConstraints();
topConstraints.gridx = 0;
topConstraints.gridy = 0;
topConstraints.gridwidth = 2;
topConstraints.gridheight = 1;
topConstraints.weightx = 1;
topConstraints.weighty = 2;

JButton bottomLeft = new JButton( "bottom left" );
GridBagConstraints bottomLeftConstraints = new GridBagConstraints();
bottomLeftConstraints.gridx = 0;
bottomLeftConstraints.gridy = 1;
bottomLeftConstraints.gridwidth = 1;
bottomLeftConstraints.gridheight = 1;
bottomLeftConstraints.weightx = 1;
bottomLeftConstraints.weighty = 1;

JButton bottomRight = new JButton( "bottom right" );
GridBagConstraints bottomRightConstraints = new GridBagConstraints();
bottomRightConstraints.gridx = 1;
bottomRightConstraints.gridy = 1;
bottomRightConstraints.gridwidth = 1;
bottomRightConstraints.gridheight = 1;
bottomRightConstraints.weightx = 2;
bottomRightConstraints.weighty = 1;[/HIGHLIGHT]

[size=+2]Ausfüllen[/size]
Normalerweise werden die Componenten nicht grösser als ihre preferredSize. Aber man kann dem GridBagLayout auch sagen, dass die Componenten ihre ganze Zelle ausfüllen sollen. Dazu wird fill verwendet. GridBagConstraints.fill kann vier unterschiedliche Werte haben:
  • GridBagConstraints.NONE: da passiert garnichts, die Standardeinstellung
  • GridBagConstraints.HORIZONTAL: die Zelle wird in der Horizontallen ausgefüllt.
  • GridBagConstraints.VERTICAL: die Zelle wird in der Vertikalen ausgefüllt.
  • GridBagConstraints.BOTH: die Zelle wird vollständig ausgefüllt.

In diesem Beispiel füllt der top-Button HORIZONTAL, der linke untere Button BOTH und der rechte untere Button VERTICAL seine Zelle aus:
beni-albums-faq-picture14-gbl5.png


[HIGHLIGHT=Java]JButton top = new JButton( "top" );
GridBagConstraints topConstraints = new GridBagConstraints();
topConstraints.gridx = 0;
topConstraints.gridy = 0;
topConstraints.gridwidth = 2;
topConstraints.gridheight = 1;
topConstraints.weightx = 1;
topConstraints.weighty = 2;
topConstraints.fill = GridBagConstraints.HORIZONTAL;

JButton bottomLeft = new JButton( "bottom left" );
GridBagConstraints bottomLeftConstraints = new GridBagConstraints();
bottomLeftConstraints.gridx = 0;
bottomLeftConstraints.gridy = 1;
bottomLeftConstraints.gridwidth = 1;
bottomLeftConstraints.gridheight = 1;
bottomLeftConstraints.weightx = 1;
bottomLeftConstraints.weighty = 1;
bottomLeftConstraints.fill = GridBagConstraints.BOTH;

JButton bottomRight = new JButton( "bottom right" );
GridBagConstraints bottomRightConstraints = new GridBagConstraints();
bottomRightConstraints.gridx = 1;
bottomRightConstraints.gridy = 1;
bottomRightConstraints.gridwidth = 1;
bottomRightConstraints.gridheight = 1;
bottomRightConstraints.weightx = 2;
bottomRightConstraints.weighty = 1;
bottomRightConstraints.fill = GridBagConstraints.VERTICAL;[/HIGHLIGHT]

[size=+2]Position[/size]
Wenn eine Component ihre Zelle(n) nicht ausfüllt, kann sie verschiedene Positionen haben. Sie könnte z.B. in der Mitte positioniert sein, oder an einer Ecke hängen. Die Position wird mit anchor bestimmt. Die Variable GridBagConstraints.anchor kann eine ganze Menge von Werten annehmen. Die Werte lassen sich aber in 3 Kategorien einteilen:
  • Absolute Positionen
    CENTER, NORTH, NORTHEAST, EAST, SOUTHEAST, SOUTH, SOUTHWEST, WEST, NORTHWEST.
    Diese Positionen sind wohl selbsterklärend :wink:
  • Relativ zur Orientation
    PAGE_START, PAGE_END, LINE_START, LINE_END, FIRST_LINE_START, FIRST_LINE_END, LAST_LINE_START, LAST_LINE_END
    Dabei kann man sich eine Seite voller Text vorstellen. Die einzelnen Werte Stellen wichtige Positionen des Textes dar, z.B. wo der erste Buchstabe ist (FIRST_LINE_START) oder auf welcher Seite man zu lesen beginnt (LINE_START). Diese Positionen sind interessant wenn man Applikationen schreibt, die z.B. auchmal ins Arabisch übersetzt werden.
  • Relativ zur Basislinie
    BASELINE, BASELINE_LEADING, BASELINE_TRAILING, ABOVE_BASELINE, ABOVE_BASELINE_LEADING, ABOVE_BASELINE_TRAILING, BELOW_BASELINE, BELOW_BASELINE_LEADING, BELOW_BASELINE_TRAILING
    Mit diesen Positionen können die Componenten entlang einer Basislinie (jede Zeile kann eine Basislinie haben) ausgerichtet werden. Das ist erst ab Java 1.6 möglich.

Im nächsten Beispiel wurden die absoluten Positionen verwendet um die Buttons an die Ränder zu drücken:
beni-albums-faq-picture15-gbl6.png


[HIGHLIGHT=Java]JButton top = new JButton( "top" );
GridBagConstraints topConstraints = new GridBagConstraints();
topConstraints.gridx = 0;
topConstraints.gridy = 0;
topConstraints.gridwidth = 2;
topConstraints.gridheight = 1;
topConstraints.weightx = 1;
topConstraints.weighty = 1;
topConstraints.anchor = GridBagConstraints.NORTH;

JButton bottomLeft = new JButton( "bottom left" );
GridBagConstraints bottomLeftConstraints = new GridBagConstraints();
bottomLeftConstraints.gridx = 0;
bottomLeftConstraints.gridy = 1;
bottomLeftConstraints.gridwidth = 1;
bottomLeftConstraints.gridheight = 1;
bottomLeftConstraints.weightx = 1;
bottomLeftConstraints.weighty = 1;
bottomLeftConstraints.anchor = GridBagConstraints.SOUTHWEST;

JButton bottomRight = new JButton( "bottom right" );
GridBagConstraints bottomRightConstraints = new GridBagConstraints();
bottomRightConstraints.gridx = 1;
bottomRightConstraints.gridy = 1;
bottomRightConstraints.gridwidth = 1;
bottomRightConstraints.gridheight = 1;
bottomRightConstraints.weightx = 1;
bottomRightConstraints.weighty = 1;
bottomRightConstraints.anchor = GridBagConstraints.SOUTHEAST;[/HIGHLIGHT]

[size=+2]Ränder[/size]
Für jede Component kann ein gewisser Platz bis zu den Grenzen der Zellen freigehalten werden. Dies wird mit den insets gemacht. Die [JAPI]Insets[/JAPI] können für alle vier Seiten verschieden sein.

Hier wurden verschiedene Werte zwischen 0 und 20 für die insets verwendet:
beni-albums-faq-picture16-gbl7.png


[HIGHLIGHT=Java]JButton top = new JButton( "top" );
GridBagConstraints topConstraints = new GridBagConstraints();
topConstraints.gridx = 0;
topConstraints.gridy = 0;
topConstraints.gridwidth = 2;
topConstraints.gridheight = 1;
topConstraints.weightx = 1;
topConstraints.weighty = 1;
topConstraints.fill = GridBagConstraints.BOTH;
topConstraints.insets = new Insets( 5, 5, 5, 5 );

JButton bottomLeft = new JButton( "bottom left" );
GridBagConstraints bottomLeftConstraints = new GridBagConstraints();
bottomLeftConstraints.gridx = 0;
bottomLeftConstraints.gridy = 1;
bottomLeftConstraints.gridwidth = 1;
bottomLeftConstraints.gridheight = 1;
bottomLeftConstraints.weightx = 1;
bottomLeftConstraints.weighty = 1;
bottomLeftConstraints.fill = GridBagConstraints.BOTH;
bottomLeftConstraints.insets = new Insets( 5, 0, 0, 5 );

JButton bottomRight = new JButton( "bottom right" );
GridBagConstraints bottomRightConstraints = new GridBagConstraints();
bottomRightConstraints.gridx = 1;
bottomRightConstraints.gridy = 1;
bottomRightConstraints.gridwidth = 1;
bottomRightConstraints.gridheight = 1;
bottomRightConstraints.weightx = 1;
bottomRightConstraints.weighty = 1;
bottomRightConstraints.fill = GridBagConstraints.BOTH;
bottomRightConstraints.insets = new Insets( 20, 20, 20, 20 );[/HIGHLIGHT]

[size=+2]Minimalgrösse[/size]
Das GridBagLayout beachtet die Minimalgrösse jeder Component. Man kann die Minimalgrössen aber auch aktiv beeinflussen mit ipadx/y. Diese beiden Werte werden zur horizontalen, bzw. vertikalen Minimalgrösse addiert.

In diesem Beispiel wurde ipadx des unteren linken Buttons auf 30 gesetzt. Damit wird die Minimalgrösse des Buttons um 30 Pixel breiter:
beni-albums-faq-picture17-gbl8.png


[HIGHLIGHT=Java]JButton top = new JButton( "top" );
GridBagConstraints topConstraints = new GridBagConstraints();
topConstraints.gridx = 0;
topConstraints.gridy = 0;
topConstraints.gridwidth = 2;
topConstraints.gridheight = 1;
topConstraints.weightx = 1;
topConstraints.weighty = 1;
topConstraints.ipadx = 0;
topConstraints.ipady = 0;

JButton bottomLeft = new JButton( "bottom left" );
GridBagConstraints bottomLeftConstraints = new GridBagConstraints();
bottomLeftConstraints.gridx = 0;
bottomLeftConstraints.gridy = 1;
bottomLeftConstraints.gridwidth = 1;
bottomLeftConstraints.gridheight = 1;
bottomLeftConstraints.weightx = 1;
bottomLeftConstraints.weighty = 1;
bottomLeftConstraints.ipadx = 30;
bottomLeftConstraints.ipady = 0;

JButton bottomRight = new JButton( "bottom right" );
GridBagConstraints bottomRightConstraints = new GridBagConstraints();
bottomRightConstraints.gridx = 1;
bottomRightConstraints.gridy = 1;
bottomRightConstraints.gridwidth = 1;
bottomRightConstraints.gridheight = 1;
bottomRightConstraints.weightx = 1;
bottomRightConstraints.weighty = 1;
bottomRightConstraints.ipadx = 0;
bottomRightConstraints.ipady = 0;[/HIGHLIGHT]

[size=-2][P.S. Danke für die Korrektur Baunty] [/size]


[size=+2]Weiterführendes Material[/size]
 

Ebenius

Top Contributor
Wie das GridBagLayout die Komponenten anordnet

Das GridBagLayout legt Komponenten in Zellen auf einem Raster aus. Die GridBagConstraints bestimmen sowohl Eigenschaften der einzelnen Zellen, als auch Eigenschaften die die Lage der Komponente innerhalb Ihrer Zelle beeinflussen.

[size=+1]Wie werden Lage und Größe der Zellen ermittelt?[/size]

Im ersten Schritt nimmt das GridBagLayout die PreferredSize aller Komponenten auf dem Container zur Hand und überprüft, ob die Komponenten mit diesen Größen alle auf dem Container angeordnet werden können. Dabei beachtet das GridBagLayout die Lage der Zellen zueinander (gridx, gridy), verbundene Zellen (gridwidth, gridheight), den Außenabstand der Zelle zur Rastergrenze (insets) und den zusätzlichen Innenabstand (ipadx, ipady). Ist der Container zu klein (zu wenig hoch, oder zu wenig breit, oder beides), benutzt das GridBagLayout keine PreferredSize mehr, sondern nimmt statt derer die MinimumSize aller Komponenten zur Hand.

Die Größe einer einzelnen Zelle richtet sich primär nach der PreferredSize (respektive MinimumSize) der in ihr liegenden Komponente. Wenn die Größe des Containers die benötigte Größe über- oder unterschreitet, bestimmt das Gewicht (weightx, weighty), in welchem Verhältnis zueinander die Zellen verkleinert/vergrößert werden.

Nun stehen die Zellengrößen exakt fest. Das GridBagLayout legt nun die Komponenten in den Zellen ab.

[size=+1]Wie werden Komponenten innerhalb ihrer Zelle ausgelegt?[/size]

Die Eigenschaft fill bestimmt, ob eine Komponente ihre Zelle ausfüllen soll, auch wenn die Zelle größer oder kleiner ist als die PreferredSize (respektive MinimumSize) der Komponente dies vorschlägt. Mögliche Werte sind NONE (nicht auffüllen), HORIZONTAL (nur horizontal auffüllen), VERTICAL (nur vertikal auffüllen), oder BOTH (horizontal und vertikal auffüllen). Diese Eigenschaft verändert ausschließlich die Lage der betreffenden Komponente innerhalb ihrer eigenen Zelle.

Wenn eine Zelle (in einer oder beiden Richtungen) größer oder kleiner ist als die PreferredSize (respektive MinimumSize) der zugehörigen Komponente es wünscht, und wenn die Komponente die Zelle (in entsprechender Richtung) nicht auffüllen soll, bestimmt die Eigenschaft anchor, wie die Komponente innerhalb ihrer Zelle ausgelegt werden soll. Diese Eigenschaft verändert ausschließlich die Lage der betreffenden Komponente innerhalb ihrer eigenen Zelle.

[size=+1]Ein Beispiel:[/size]
Ein JLabel 1 hat den Text "Hallo", ein zweites JLabel 2 hat den Text "Ich bin ein JLabel". Beide JLabels liegen mit gleichem Gewicht 1.0 nebeneinander auf einem JPanel mit GridBagLayout. Die Komponenten füllen die Zellen horizontal auf, alle Abstände sind null.

Das JLabel 1 möchte gern 100px breit sein, das JLabel 2 möchte gern 200px breit sein, das JPanel ist 300px breit. Beide JLabels werden genau in ihrer PreferredSize ausgelegt. Vergrößert sich nun das JPanel auf das Doppelte seiner Größe (600px), dann stehen 300px zusätzlicher Platz zur Verfügung. Das erste JLabel vergrößert sich auf 250px (100px + 300px * 50%) und das zweite JLabel auf 350px (200px + 300px * 50%). Setzt man nun das Gewicht des zeiten JLabels auf 3.0, dann haben bei 300px JPanel-Breite die JLabels wie oben 100px und 200px, bei 600px JPanel-Breite hat des erste JLabel nun aber eine Breite von 175px (100px + 300px * 25%) und das zweite eine Breite von 425px (200px + 300px * 75%).

Verkleinert man das Panel nun auf 220px, dann interessieren die PreferredSizes nicht mehr, denn die passen ja nicht mehr auf das Panel. Also werden die MinimumSizes genommen ─ ich erfinde bei beiden JLabels 10px. Dann wird das erste JLabel 60px (10px + 200px * 25%) breit und das zweite JLabel 160px (10px + 200px * 75%) breit sein.

Jetzt ändern wir die Größe des JPanels auf 300px. Alles wird in Lieblingsgröße angezeigt. Und nun ändern wir den Text des ersten JLabels auf "Ich bin jetzt viel länger". Die PreferredSize des zweiten JLabels wird nun breiter. Schon passen nicht mehr beide Komponenten auf das JPanel. Und schon wird wieder die MinimumSize genommen. Das erste JLabel wird 80px (10px + 280px * 25%) und das zeite wird 220px (10px + 280px * 75%).​


Troubleshooting

[size=+1]Warum geht mein Layout bei Nutzereingaben kaputt?[/size]

Das eben dargestellte Beispiel zeigt ein häufig auftretendes Problem. Wenn Komponenten innerhalb eines GridBagLayouts ihre PreferredSize verändern, kann das ganze Layout sich unerwünscht verschieben. JTextFields, JTextAreas, JSpinner, JFormattedTextFields und einige andere Komponenten verändern ihre PreferredSize in Abhängigkeit von Nutzereingaben. Gibt der Nutzer einen längeren Text ein, verschiebt sich das Layout.

Um diesem Problem beizukommen kann man alle PreferredSizes und MinimumSizes der betroffenen Komponenten auf feste Werte setzen. Oder man kann auf die Spalte/Zeile -1 (Zelle -1*-1 nicht vergessen) Platzhalter in der richtigen Größe setzen (mit Box.createRigidArea() oder ähnlich) und bei allen anderen Komponenten weightx und weighty auf null setzen.

nach oben
 
Status
Nicht offen für weitere Antworten.

Neue Themen


Oben