Westbahr är en komplett IT-leverantör med lång erfarenhet och med ett ständigt fokus på modern teknik, affärer och god kvalité.
Läs mer »Marcus skriver om hur du gör en egen layouthanterare.
När man programmerar användargränssnitt i Swing är ofta en av de stora stötestenarna att placera ut sina komponenter som man vill ha dem. Förutom att de ska placeras rätt relativt varandra och få bra storlekar måste man också se till att de uppför sig på ett bra sätt när t ex fönstrets storlek ökar eller minskar.
Genom att bygga upp en hierarki av paneler och använda de olika inbyggda layouthanterarna kan man oftast få till layouten av sina komponenter precis som man vill ha den. Särskilt BorderLayout är trots sin enkelhet väldigt mångsidig och användbar. Om allting annat fallerar kan man oftast lösa det med den kraftfulla GridBagLayout, men oftast är det inte nödvändigt.
Vissa nybörjare frestas istället till att helt enkelt undvika layouthanterare (setLayout(null)) och ge absoluta koordinater till alla komponenter genom direkta anrop till metoder som setBounds. Detta är, förutom onödigt jobbigt, också en dålig lösning då komponenterna inte längre anpassar sig när fönstrets storlek ändras. I riktiga skräckexempel kompenseras då detta genom att låsa fönstrets storlek med setResizable(false). Men två fel gör inte ett rätt!
Ett tredje alternativ, som faktiskt är mycket lättare än vad man kanske kan tro, är att skriva sig en egen layouthanterare. En layouthanterare implementerar gränssnittet LayoutManager och ska lösa tre uppgifter:
minimumLayoutSize).preferredLayoutSize).layoutContainer), dvs ge dem x- och y-koordinater, bredd och höjd, genom anrop till t ex setBounds.Till sin hjälp har man också metoderna addLayoutComponent och removeLayoutComponent, som man kan använda för att t ex veta att ett visst barn lades till med ett visst namn.
Vi tar ett exempel. Layouthanteraren GridLayout, som finns inbyggd i Swing, placerar ut alla en komponents barn i ett rutnät och gör dem exakt lika stora. Antalet rader och/eller kolumner är fast. Men om föräldern får annorlunda proportioner, t ex till följd av att fönstrets storlek ändras, kan barnen få ganska märkliga storlekar. I vissa situationer kanske detta inte är önskvärt, utan det kanske hade varit bättre om layouthanteraren hade flyttat om barnen genom att ändra antalet kolumner och rader. Låt oss därför skriva en ny layouthanterare, som vi kan kalla AdaptiveGridLayout, som gör just detta:
public class AdaptiveGridLayout implements LayoutManager {
private int hgap;
private int vgap;
public AdaptiveGridLayout(int hgap, int vgap) {
this.hgap = hgap;
this.vgap = vgap;
}
public AdaptiveGridLayout() {
this(0, 0);
}
Eftersom barnkomponenterna kommer att vara helt likvärdiga, och vi bara kommer att placera ut dem i den ordning som de har lagts till, behöver vi inte göra något särskilt i addLayoutComponent och removeLayoutComponent:
@Override
public void addLayoutComponent(
String name,
Component comp) {
}
@Override
public void removeLayoutComponent(Component comp) {
}
Metoden minimumLayoutSize tittar på alla barnens respektive minimumLayoutSize, och räknar ut ur stor föräldern skulle behöva vara för att uppfylla detta. Eftersom vi inte i detta läget har en aning om vilka proportioner föräldern kommer att få (detta bestäms, förutom av det svar den här metoden kommer att ge, också av i vilken omgivning föräldern är placerad) måste vi göra ett antagande. Jag har valt att anta att vi vill placera komponenterna så att antalet rader är så likt antalet kolumner som möjligt.
@Override
public Dimension minimumLayoutSize(Container parent) {
Dimension childSize =
getBiggestChildMinumumDimensions(parent);
int n =
(int) Math.ceil(Math.sqrt(parent
.getComponentCount()));
Insets insets = parent.getInsets();
return new Dimension(insets.left
+ insets.right
+ n
* (childSize.width + hgap)
- hgap, insets.top
+ insets.bottom
+ n
* (childSize.height + vgap)
- vgap);
}
private Dimension getBiggestChildMinumumDimensions(
Container parent) {
int maxWidth = 0;
int maxHeight = 0;
for (Component child : parent.getComponents()) {
Dimension min = child.getMinimumSize();
if (min != null) {
maxWidth = Math.max(maxWidth, min.width);
maxHeight = Math.max(maxHeight, min.height);
}
}
return new Dimension(maxWidth, maxHeight);
}
Viktigt att komma ihåg är att lägga till förälderns Insets (det utrymme som t ex används för en eventuell ram). Ramar och dylikt ska hamna utanför ytan där barnen placeras.
På motsvarande sätt gör vi preferredLayoutSize:
@Override
public Dimension preferredLayoutSize(Container parent) {
Dimension childSize =
getBiggestChildPreferredDimensions(parent);
int n =
(int) Math.ceil(Math.sqrt(parent
.getComponentCount()));
Insets insets = parent.getInsets();
return new Dimension(insets.left
+ insets.right
+ n
* (childSize.width + hgap)
- hgap, insets.top
+ insets.bottom
+ n
* (childSize.height + vgap)
- vgap);
}
private Dimension getBiggestChildPreferredDimensions(
Container parent) {
int maxWidth = 0;
int maxHeight = 0;
for (Component child : parent.getComponents()) {
Dimension pref = child.getPreferredSize();
if (pref != null) {
maxWidth = Math.max(maxWidth, pref.width);
maxHeight =
Math.max(maxHeight, pref.height);
}
}
return new Dimension(maxWidth, maxHeight);
}
Återstår nu bara att skriva den kanske viktigaste av metoderna, layoutContainer. Denna kommer att automatiskt anropas varje gång barnen behöver placeras ut, t ex när nya barn lagts till förälderkomponenten eller då förälderkomponenten själv ändrat storlek. Viktigt att notera här är att vi inte på något sätt kan förutsätta att förälderkomponenten har fått den storlek som layouthanteraren själv föreslog i preferredLayoutSize! Vi måste jobba med den yta förälderkomponenten faktiskt har, vilken vi kan få genom att anropa getWidth och getHeight på förälderkomponenten. Vi får heller inte glömma att dra bort eventuella Insets. Resten är matematik:
@Override
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
int w =
parent.getWidth()
- insets.left
- insets.right
+ hgap;
int h =
parent.getHeight()
- insets.top
- insets.bottom
+ vgap;
int n = parent.getComponentCount();
if (n > 0) {
double d2 = w * h / (double) n;
Dimension childSize =
getBiggestChildPreferredDimensions(parent);
double cw = childSize.getWidth() + hgap;
double ch = childSize.getHeight() + vgap;
double k = Math.sqrt(d2 / (cw * ch));
int nx = (int) (w / (k * cw));
int ny = (int) (h / (k * ch));
while (nx * ny < n) {
if (ny < nx)
ny++;
else
nx++;
}
int dx = (int) (w / nx);
int dy = (int) (h / ny);
int row = 0;
int col = 0;
for (Component child : parent.getComponents()) {
child.setBounds(
insets.left + col * dx,
insets.top + row * dy,
dx - hgap,
dy - vgap);
col++;
if (col >= nx) {
col = 0;
row++;
}
}
}
}