Eigenen LayoutManager schreiben (Neue Version)

Volvagia

Top Contributor
1. Theorie
2. LayoutManager
3. LayoutManager2

Es besteht neben den vordefinierten [c]LayoutManager[/c] der JRE-Standartbibliothek und externen APIs natürlich die Möglichkeit, eigene Manager zu entwickeln. Jeder, der schon einmal mit den "
Code:
Null-Layout
" gearbeitet hat weiß, dass
Code:
Component
s mit den Methoden
Code:
setLocation
und
Code:
setSize
bzw. gleichzeitig per
Code:
setBounds
ihre Position und Größe vorgegeben werden. Der
Code:
LayoutManager
macht das Selbe mit dem Unterschied, dass er von den Komponenten auf dem er sitzt entkoppelt ist und so leicht wiederverwendet werden kann. Etwas ähnliches könnte man eigendlich nur mit einen
Code:
ComponentListener
erreichen, der dafür bei weitem nicht optimal ist.

Um einen
Code:
LayoutManager
zu schreiben, gibt es prinzipiell 2 Wege: Man implementiert das Interface
Code:
LayoutManager
oder
Code:
LayoutManager2
. Dabei wird
Code:
LayoutManager
benutzt, falls das Hinzufügen ohne Parameter stattfindet. Ein Beispiel wäre das FlowLayout, welches einfach der Reihe nach anordnet. Diese holen sich die
Code:
Component
en direkt vom Parent, da sie keine weiteren Informationen abspeichern müssen. Hingegen wird
Code:
LayoutManager2
benutzt, falls ein Parameter (genannt
Code:
Constraint
) wärend des Hinzufügens übergeben werden soll. Ein Beispiel wäre das BorderLayout, welches mit 5 Strings arbeitet um die Position des
Code:
Component
s anzugeben. Für den Parent des
Code:
LayoutManager
s spielt es keine Rolle, welches Interface benutzt wird. Es wird meistens von dem
Code:
Component
en auf dem das Layout sitzt geprüft, um welches
Code:
Interface
es sich handelt und so die demensprechende Action durchgeführt.
Code:
LayoutManager2
erweitert
Code:
LayoutManager
und hat so auch Methoden, die es eigendlich nicht benötigt.


Code:
LayoutManager
definiert folgende Methoden:

addLayoutComponent(String, Component)

Es gibt bei
Code:
Container
n allgemein 5 Add-Methoden für
Code:
Component
s.

[c]add(Component)[/c]
[c]add(Component, int)[/c]
[c]add(Component, Object)[/c]
[c]add(String, Component)[/c]
[c]add(Component, String, Object)[/c]

Diese Methode wird aufgerufen, sobald ein
Code:
Component
dem Parent mit einen der letzten 3 Methoden hinzugefügt wird, und es sich nicht um einen
Code:
LayoutManager2
handelt.



removeLayoutComponent(Component)

Wird aufgerufen, sobald ein
Code:
Component
von den Parent entfernt wird.



preferredLayoutSize(Container)

Gibt die optimale Größe des Parents wieder. Diese kann extern einen jeden
Code:
Component
en gesetzt werden, besitzt er jedoch einen
Code:
LayoutManager
(und wird sie nicht extra gesetzt) wird die Methode des
Code:
LayoutManager
s verwendet. Z. B. benutzen
Code:
JScrollPane
s die Größe, um den Bescrollbaren Bereiches festzustellen.
Code:
Label
s geben z. B. die benötigte Größe für den Text oder das Bild (oder gegebenfalls beides) zurück. Grundsätzlich ist die preferredLayoutSize die, nach der man sich bei der Anordnung der Komponenten richtet.



minimumLayoutSize(Container)

Gibt die minimale Größe des Parents zurück. Da es meistens keinen Grund gibt die Größe zu beschränken ist das meistens 0/0.


layoutContainer(Container)

Die wohl wichtigste Methode. Hier werden die
Code:
Component
s neu angeordnet.



Code:
LayoutManager2
erweitert
Code:
LayoutManager
und besitzt damit die selben Methoden, zuzüglich dieser:

addLayoutComponent(Component, Object)

Diese Add-Methode wird statt
Code:
addLayoutComponent(String, Component)
aufgerufen, wenn es sich um einen
Code:
LayoutManager2
handelt und ein
Code:
Component
hinzugefügt wird (egal welche der oben genannten Methoden). In Object steht der
Code:
Constraint
des add-Aufrufes oder gegebenfalls null.



maximumLayoutSize(Container)

Gibt die maximale Layout-Größe an. Oft wird einfach
Code:
Integer.MAX_VALUE
verwendet.



getLayoutAlignmentX(Container)

Gibt die Ausrichtung der X-Achse des
Code:
Component
s wieder. 0 bedeutet die "normale" Ausrichtung, 0.5 zentriert und 1 am weitesten von der "normalen" Ausrichtung entfernt.



getLayoutAlignmentY(Container target)

Genauso wie
Code:
getLayoutAlignmentX
, nur natürlich über die Y-Achse.


invalidateLayout(Container target)

Wird immer aufgerufen, wenn der Parent als
Code:
invalid
gekenntzeichnet wird, also es einer neuen Anordnung bedarf.
 
Zuletzt bearbeitet:

Volvagia

Top Contributor
1. Theorie
2. Praxis - LayoutManager
3. LayoutManager2

Ich habe mich für einen LayoutManager entschieden, der die
Code:
Compontent
s diagional anordnet (DiagonalLayout). Das heißt, der nächste
Code:
Component
wird genau ein Pixel weiter unten/rechts als die untere rechte Ecke des vorherigen platziert. Da dieser Manager keine
Code:
Constraint
s abspeichert, implementieren wir das Interface
Code:
LayoutManager
. Da die add-Methode des
Code:
LayoutManager
s nur in seltensten Fällen sinnvoll ist, holen wir die
Code:
Component
s direkt vom Parent.

Zuerst müssen wir einmal wissen, was genau geschrieben werden soll. Wir überlegen also, was in den verschiedenen Methoden wie gemacht werden soll:


[c]addLayoutComponent(String, Component)[/c]

Da wir keinen String oder Constrains übergeben, wird dazu eigendlich nur add(Component) verwendet. Da die Methode da nicht aufgerufen wir implementieren wir sie leer.


[c]removeLayoutComponent(Component)[/c]

Da nichts gespeichert wird, muss auch nichts entfernt werden, wenn ein Component vom Parent entfernt wird. Daher wird diese Methode ebenfalls leer implementiert.


[c]layoutContainer(Container)[/c]

Hier werden wie gesagt die Componenten angeordnet. Dazu wird zuerst eine Variable definiert, um die aktuelle Position der Anordnung zu speichern. Danach werden die
Code:
Component
en des Parents durchgelaufen, an die aktuelle Position gesetzt und die Position wird um die Preferred Size nach rechts unten verschoben.



[c]preferredLayoutSize(Container)[/c]

Die Komponenten und ihre Abstände sind hier fix zueinander. Deshalb ist die optimale Größe des Parent einfach die addierte optimale Höhe und Breiter aller Childs + der
Code:
Rahmen
(Auf diesen darf man nicht vergessen).



[c]minimumLayoutSize(Container)[/c]

Da ich keinen Grund sehe eine minimale Größe festzulegen, wird hier 0/0 zurückgegeben.


Legen wir uns mal die Klasse an.
In meinen Beispiel im Package org.javaforum.tutorials.layoutmanager, und implementieren gleich die sowieso klaren Methoden:

Java:
package org.javaforum.tutorials.layoutmanager;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;

public class DiagionalLayout implements LayoutManager {
	public void layoutContainer(Container parent) {
		
	}
	public Dimension preferredLayoutSize(Container parent) {
		return null;
	}
	

	public Dimension minimumLayoutSize(Container parent) {
		return(new Dimension(0, 0));
	}
	public void addLayoutComponent(String name, Component comp) {}
	public void removeLayoutComponent(Component comp) {}
}

Ok. Nun müssen wir die Componenten erstmal anordnen. Als erstes (was ich allzu gerne vergesse) ist die Breite des Rahmens. Auch mit Rahmen ist 0/0 ganz links oben (bei normaler Ausrichtung), die
Code:
Component
en würde als über den Rahmen gezeichnet werden. Die Größe des Rahmens wird in den
Code:
Insets
genannten Objekt von einen Component geliefert:

Java:
public void layoutContainer(Container parent) {
	Insets insets = parent.getInsets();
}

Als nächstes brauchen wir natürlich eine Variable, die die aktuelle Position speichert. Diese beginnt direkt nach dem Rahmen:

Java:
public void layoutContainer(Container parent) {
	Insets insets = parent.getInsets();
	Point offs = new Point(insets.left, insets.top);
}

Ein
Code:
Point
ist einfach ein Wrappler für 2
Code:
int
s, in der Regel gibt er einen Punkt auf einen 2-Dimensionalen Raster an. (Die Variablen sind public und heißen x und y.)

Nun müssen wir in einer kleinen Schleife nur alle Componenten durchgehen und an die linke untere Ecke des letzten
Code:
Component
s setzen. Dazu müssen wir nur die Größe auf die eigene bevorzugte Größe setzen und diese zu den Koordinaten (offs) addieren. Außerdem überspringen wir unsichtbare
Code:
Component
s, da sonst Lücken entstehen würden.

Java:
public void layoutContainer(Container parent) {
	Insets insets = parent.getInsets();
	Point offs = new Point(insets.left, insets.top);
	
	for(int i = 0, size = parent.getComponentCount(); i < size; i++) {
		Component c = parent.getComponent(i);
		if(!c.isVisible()) { //Unsichtbare Componenten überspringen.
			continue;
		}
		
		//Position auf das Offset setzen.
		c.setLocation(offs.x, offs.y);
		
		Dimension prefSize = c.getPreferredSize();

		//Auf die vom Component gelieferte optimale Größe setzen.
		c.setSize(prefSize);
		
		//Position verändern
		offs.x+= prefSize.width;
		offs.y+= prefSize.height;
	}
}

Als letztes müssen wir nur noch
Code:
preferredLayoutSize
richtig implementieren. Wir verwenden dafür die selbe Schleife, mit dem Unterschied, dass wir nur die Größe hochzählen und nichts anordnen.

Java:
public Dimension preferredLayoutSize(Container parent) {
	Insets insets = parent.getInsets();
	Dimension parentPrefSize = new Dimension(insets.left + insets.right, insets.top + insets.bottom);
	
	for(int i = 0, size = parent.getComponentCount(); i < size; i++) {
		Component c = parent.getComponent(i);
		if(!c.isVisible()) {
			continue;
		}
		
		Dimension prefSize = c.getPreferredSize();
		
		parentPrefSize.width+= prefSize.width;
		parentPrefSize.height+= prefSize.height;
	}
	return(parentPrefSize);
}

Statt einen
Code:
Point
verwenden wir nun eine
Code:
Dimension
, da wir die Größe und keine Position speichern wollen. Außerdem addieren wir zu der optimalen Größe noch die untere Höhe und rechte Breite des Rahmens hinzu.

Nun schreiben wir uns noch eine kurze Main-Methode, um das
Code:
Layout
auch testen zu können:

Java:
public static void main(String[] args) {
	EventQueue.invokeLater(new Runnable() {
		public void run() {
			JFrame frame = new JFrame("DiagionalLayout Test");
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			frame.setLayout(new DiagionalLayout());
			
			frame.add(new JLabel("Hello"));
			frame.add(new JLabel("World"));
			frame.add(new JLabel("We"));
			frame.add(new JLabel("are"));
			
			JLabel label = new JLabel("all"); //Ein unsichtbarer Component als Test
			label.setVisible(false);
			frame.add(label);
			
			frame.add(new JLabel("diagional"));
			frame.add(new JLabel("located"));
			
			frame.pack();
			frame.setLocationRelativeTo(null);
			frame.setVisible(true);
		}
	});
}

Durch die pref. Size des
Code:
ContentPanes
setzt das
Code:
JFrame
auch die richtige Größe, und zeigt wie erwartet alle
Code:
Component
s in einer diagionalen Linie an:

attachment.php


Dadurch, dass der Text breiter als hoch ist wirkt es eher wie eine Treppe, aber genau so habe ich es ja auch geschrieben. Ansonst könnte ich einfach den höheren der beiden Größenangaben werden und diese verwenden:

Java:
int j = Math.max(prefSize.width, prefSize.height);
offs.setLocation(offs.x + j, offs.y + j);

Aber da es sich um mathematische Grundlagen handelt und solange es nicht wirklich zu kompliziert wird (sollte das der Fall sein denkt darüber nach, ob sich 2 verschaltete
Code:
Component
s mit unterschiedlichen
Code:
Layout
s nicht besser eignen würden) sollte es jeden einfach fallen, sein
Code:
Layout
seinen Wünschen anzupassen.
 

Anhänge

  • diagionallayout.png
    diagionallayout.png
    2,4 KB · Aufrufe: 162
Zuletzt bearbeitet:

Volvagia

Top Contributor
1. Theorie
2. LayoutManager
3. Praxis - LayoutManager2

Jetzt noch ein
Code:
LayoutManager
, der das Interface
Code:
LayoutManager2
implementiert und damit auch Parameteter übergeben werden können. Dafür werden wir einen
Code:
LayoutManager
schreiben, der wie das
Code:
FlowLayout
anordnet. Der Unterschiede ist, dass
Code:
Component
en sowohl links als auch rechts angeordnet werden können. Der einfach heit halber verzichten wir aber auf Dinge wie Zeilenumbruch und lassen bei zu kurzen Parent
Code:
Component
s einfach überlappen, damit der Code als Anschauungsmaterial übersichtlich bleibt. Ich gebe ihm den Namen LeftRightFlowLayout. Nicht sehr einfallsreich, ich weiß.


Zuerst wieder die Klasse und
Code:
LayoutManager2
implementieren.


Folgende Methoden enthalten die Hauptimplemention:

addLayoutComponent(Component, Object)
removeLayoutComponent(Component)
preferredLayoutSize(Container)
layoutContainer(Container)



Folgende Methoden werden leer oder mit Standartrückgabewerten implementiert:

addLayoutComponent(String, Component)
invalidateLayout(Container)
getLayoutAlignmentX(Container)
getLayoutAlignmentY(Container)
maximumLayoutSize(Container)
minimumLayoutSize(Container)



Als Speicherung der
Code:
Component
en sehe ich 2
Code:
LinkedList
s vor. Jeweils eine für die linken und für die rechten
Code:
Component
s. Als Abstand zwischen ihnen definiere ich einen Standart von 2, kann allerdings auch per
Code:
Constructor
gesetzt werden. y ist immer die Höhe des Rahmens. Um die Position (Links oder Rechts) anzugeben verwende ich 2 simple
Code:
Object
e. Ist keines davon angegeben, wird standartgemäß links angeordnet.

Unsere Klasse sieht nun fürs Erste so aus:

Java:
package org.javaforum.tutorials.layoutmanager;

import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager2;
import java.util.LinkedList;
import java.util.List;

public class LeftRightFlowLayout implements LayoutManager2 {
	public static final Object LEFT = new Object();
	public static final Object RIGHT = new Object();
	
	private static final Object DEFAULT_ALIGN = LEFT;
	
	private static final int DEFAULT_X_GAP = 2;
	
	private int xGap;
	
	private List<Component> leftComponents;
	private List<Component> rightComponents;
	
	public LeftRightFlowLayout() {
		this(DEFAULT_X_GAP);
	}
	public LeftRightFlowLayout(int xGap) {
		this.xGap = xGap;
		
		leftComponents = new LinkedList<Component>();
		rightComponents = new LinkedList<Component>();
	}
	
	public void addLayoutComponent(Component comp, Object constraints) {
		// TODO Auto-generated method stub
	}
	public void removeLayoutComponent(Component comp) {
		// TODO Auto-generated method stub
	}
	public Dimension preferredLayoutSize(Container parent) {
		// TODO Auto-generated method stub
		return null;
	}
	public void layoutContainer(Container parent) {
		// TODO Auto-generated method stub
	}

	
	public void addLayoutComponent(String name, Component comp) {}
	public void invalidateLayout(Container target) {}
	
	public float getLayoutAlignmentX(Container target) {
		return 0;
	}
	public float getLayoutAlignmentY(Container target) {
		return 0;
	}
	public Dimension maximumLayoutSize(Container target) {
		return(new Dimension(0, 0));
	}
	public Dimension minimumLayoutSize(Container parent) {
		return(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
	}
}

Widmen wir uns nun den Hinzufügen der
Code:
Component
en.
Eigendlich müssen wir nur prüfen, ob es sich um eins unserer 2 Objekte handelt.

Java:
public void addLayoutComponent(Component comp, Object constraints) {
	if(constraints == LEFT) {
		leftComponents.add(comp);
	} else if(constraints == RIGHT) {
		rightComponents.add(comp);
	} else {
		addLayoutComponent(comp, DEFAULT_ALIGN);
	}
}

Sehr simpel. Ist das
Code:
Object
für Links angegeben wird es der linken Liste hinzugefügt wenn das rechte
Code:
Object
angegeben wurde der rechten Liste und wenn nicht wird die Methode erneut mit dem Wert für die linke Liste (Standart-Wert) aufgerufen. Hier müssen wir nur etwas vorsichtig sein, wenn z. B. DEFAULT_ALIGN weder LEFT noch RIGHT ist gibt es eine Endlosschleife und einen
Code:
StackOverflow
.

removeLayoutComponent wird noch einfacher, wir entfernen den
Code:
Component
aus der linken Liste und wenn es darin nicht vorhanden ist aus der rechten.

Java:
public void removeLayoutComponent(Component comp) {
	if(!leftComponents.remove(comp)) {
		rightComponents.remove(comp);
	}
}

Als nächstes folgt die Anordnung der
Code:
Component
en.

Ich schreibe einfach 2 Schleifen die die
Code:
Component
en anordnet. Zwar ließe sich das sicher durch Methoden abstraktieren, allerdings denke ich, dass der Code dann schwerer zu lesen ist.

Java:
public void layoutContainer(Container parent) {
	Insets insets = parent.getInsets();
	Point offs = new Point(insets.left, insets.top);
	
	for(Component c:leftComponents) {
		if(!c.isVisible()) {
			continue;
		}
		
		Dimension prefSize = c.getPreferredSize();
		c.setSize(prefSize);
		c.setLocation(offs.x, offs.y);
		
		offs.x+= prefSize.width + xGap;
	}
	
	offs = new Point(parent.getWidth() - insets.right, insets.top);
	for(Component c:rightComponents) {
		if(!c.isVisible()) {
			continue;
		}
		
		Dimension prefSize = c.getPreferredSize();
		c.setSize(prefSize);
		c.setLocation(offs.x - prefSize.width, offs.y);

		offs.x-= (prefSize.width + xGap);
	}
}

Die erste Schleife ist im Grunde genau das Selbe, wie wir bereits oben hatten.
Code:
Component
en bekommen nacheinander ihre optimale Größe, werden an die Position des Offsets positioniert, das offs bewegt sich um die Größe + den Abstand und der nächste
Code:
Component
kommt an die Reihe.

Genauso ist die untere Schleife, mit dem Unterschied, dass hier das Offset an der Breite des Parents beginnt und die Position immer abgezogen wird, um das Offset nach links zu verschieben.

Als letztes müssen wir noch die optimale Größe errechnen. Dazu benutzen wir wieder eine modifizierte Version der
Code:
Layout
-Schleifen. Wir zählen einfach die Breite sämtlicher
Code:
Component
s zusammen. Da wir die
Code:
Component
s nur in einer Reihe anordnen benötigen wir nur die Höhe des höchsten
Code:
Component
(+ Rahmenhöhe). Dafür verwenden wir einfach die statische Methode max in Math, die die größere Zahl der beiden Parameter zurückliefert.

Java:
public Dimension preferredLayoutSize(Container parent) {
	Insets insets = parent.getInsets();
	int borderHeight = insets.top + insets.bottom;
	Dimension parentPrefSize = new Dimension(insets.left + insets.right, borderHeight);
	
	for(Component c:leftComponents) {
		Dimension prefSize = c.getPreferredSize();
		parentPrefSize.width+= (prefSize.width + xGap);
		parentPrefSize.height = Math.max(parentPrefSize.height, prefSize.height + borderHeight);
	}
	
	for(Component c:rightComponents) {
		Dimension prefSize = c.getPreferredSize();
		parentPrefSize.width+= (prefSize.width + xGap);
		parentPrefSize.height = Math.max(parentPrefSize.height, prefSize.height + borderHeight);
	}
	return(parentPrefSize);
}

Nun noch eine kurze Main zum testen:

Java:
public static void main(String[] args) {
	EventQueue.invokeLater(new Runnable() {
		public void run() {
			JFrame frame = new JFrame("LeftRightFlowLayout Test");
			frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			frame.setLayout(new LeftRightFlowLayout(15));
			
			frame.add(new JLabel("Links A"), LeftRightFlowLayout.LEFT);
			frame.add(new JLabel("Rechts A"), LeftRightFlowLayout.RIGHT);
			frame.add(new JLabel("Links B"), LeftRightFlowLayout.LEFT);
			frame.add(new JLabel("Links C"));
			frame.add(new JLabel("Rechts B"), LeftRightFlowLayout.RIGHT);
			frame.add(new JLabel("Rechts C"), LeftRightFlowLayout.RIGHT);
			
			frame.pack();
			frame.setLocationRelativeTo(null);
			frame.setVisible(true);
		}
	});
}

attachment.php


Und alles linke wird links und alles rechte rechts angezeigt. ^_^

[TIPP]Falls eure LayoutManager2 eine Zahl als Parameter benötigt (wie das LayeredPane) denkt daran, Objekte (z. B. Integer.valueOf(int)) zu benutzen, da ihr sonst die Höhe des Components und keinen Constraint übergebt.[/TIPP]


Weiterführendes Material
 

Anhänge

  • lrfl.png
    lrfl.png
    2 KB · Aufrufe: 160
Zuletzt bearbeitet:

Neue Themen


Oben