DiagonalLayout: Unterschied zwischen den Versionen
K |
K |
||
(8 dazwischenliegende Versionen von 2 Benutzern werden nicht angezeigt) | |||
Zeile 1: | Zeile 1: | ||
=Theorie= | =Theorie= | ||
− | Es besteht neben den vordefinierten {{JAPI|LayoutManager}}n der JRE-Standartbibliothek und externen APIs natürlich die Möglichkeit, eigene Manager zu entwickeln. Jeder, der schon einmal mit "Null-Layout" gearbeitet hat, weiß, dass | + | Es besteht neben den vordefinierten {{JAPI|LayoutManager}}n der JRE-Standartbibliothek und externen APIs natürlich die Möglichkeit, eigene Manager zu entwickeln. Jeder, der schon einmal mit "Null-Layout" gearbeitet hat, weiß, dass Components mit den [[Methode|Methoden]] <code>setLocation()</code> und <code>setSize()</code> bzw. gleichzeitig per <code>setBounds()</code> ihre Position und Größe vorgegeben werden. Der 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 einem {{JAPI|ComponentListener}} erreichen, der dafür bei Weitem nicht optimal ist. |
Um einen LayoutManager zu schreiben, gibt es prinzipiell 2 Wege:<br> | Um einen LayoutManager zu schreiben, gibt es prinzipiell 2 Wege:<br> | ||
− | Man implementiert das Interface {{JAPI|LayoutManager}} oder {{JAPI|LayoutManager2}}. Dabei wird '''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 Components direkt vom Parent, da sie keine weiteren Informationen abspeichern müssen. Hingegen wird '''LayoutManager2''' benutzt, falls ein Parameter (genannt | + | Man implementiert das Interface {{JAPI|LayoutManager}} oder {{JAPI|LayoutManager2}}. Dabei wird '''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 Components direkt vom Parent, da sie keine weiteren Informationen abspeichern müssen. Hingegen wird '''LayoutManager2''' benutzt, falls ein Parameter (genannt Constraints) wärend des Hinzufügens übergeben werden soll. Ein Beispiel wäre das [[BorderLayout]], welches mit 5 Strings arbeitet, um die Position des Components anzugeben. Für den Parent des '''LayoutManager'''s spielt es keine Rolle, welches [[Interface]] benutzt wird. Es wird meistens von dem Component auf dem das Layout sitzt geprüft, um welches Interface es sich handelt und so die demensprechende Action durchgeführt. '''LayoutManager2''' erweitert '''LayoutManager''' und hat so auch Methoden, die es eigendlich nicht benötigt. |
==Methoden von {{JAPI|LayoutManager}}== | ==Methoden von {{JAPI|LayoutManager}}== | ||
LayoutManager definiert folgende Methoden: | LayoutManager definiert folgende Methoden: | ||
− | < | + | <syntaxhighlight lang="java">addLayoutComponent(String, Component)</syntaxhighlight> |
Es gibt bei {{JAPI|Container}}n allgemein 5 add()-Methoden für Components. | Es gibt bei {{JAPI|Container}}n allgemein 5 add()-Methoden für Components. | ||
− | < | + | <syntaxhighlight lang="java">add(Component)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">add(Component, int)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">add(Component, Object)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">add(String, Component)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Diese Methode wird aufgerufen, sobald ein Component dem Parent mit einen der letzten 3 Methoden hinzugefügt wird und es sich nicht um einen '''LayoutManager2''' handelt.*/ | /*Diese Methode wird aufgerufen, sobald ein Component dem Parent mit einen der letzten 3 Methoden hinzugefügt wird und es sich nicht um einen '''LayoutManager2''' handelt.*/ | ||
− | add(Component, String, Object)</ | + | add(Component, String, Object)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Wird aufgerufen, sobald ein Component von den Parent entfernt wird.*/ | /*Wird aufgerufen, sobald ein Component von den Parent entfernt wird.*/ | ||
− | removeLayoutComponent(Component)</ | + | removeLayoutComponent(Component)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Gibt die optimale Größe des Parents wieder.*/ | /*Gibt die optimale Größe des Parents wieder.*/ | ||
− | preferredLayoutSize(Container)</ | + | preferredLayoutSize(Container)</syntaxhighlight> |
Diese kann extern für jeden Component gesetzt werden, besitzt er jedoch einen LayoutManager (und wird sie nicht extra gesetzt) wird die Methode des LayoutManagers verwendet. Z. B. benutzen {{JAPI|JScrollPane}}s die Größe, um den Bescrollbaren Bereiches festzustellen. {{JAPI|JLabel}}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. | Diese kann extern für jeden Component gesetzt werden, besitzt er jedoch einen LayoutManager (und wird sie nicht extra gesetzt) wird die Methode des LayoutManagers verwendet. Z. B. benutzen {{JAPI|JScrollPane}}s die Größe, um den Bescrollbaren Bereiches festzustellen. {{JAPI|JLabel}}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. | ||
− | < | + | <syntaxhighlight lang="java"> |
/*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.*/ | /*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.*/ | ||
− | minimumLayoutSize(Container)</ | + | minimumLayoutSize(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Die wohl wichtigste Methode. Hier werden die Components neu angeordnet.*/ | /*Die wohl wichtigste Methode. Hier werden die Components neu angeordnet.*/ | ||
− | layoutContainer(Container)</ | + | layoutContainer(Container)</syntaxhighlight> |
Zeile 41: | Zeile 41: | ||
'''LayoutManager2''' erweitert wie oben bereits gesagt '''LayoutManager''' und besitzt damit die selben Methoden, zuzüglich dieser: | '''LayoutManager2''' erweitert wie oben bereits gesagt '''LayoutManager''' und besitzt damit die selben Methoden, zuzüglich dieser: | ||
− | < | + | <syntaxhighlight lang="java"> |
/*Diese add()-Methode wird statt '''addLayoutComponent(String, Component)''' aufgerufen, wenn es sich um einen '''LayoutManager2''' handelt und ein | /*Diese add()-Methode wird statt '''addLayoutComponent(String, Component)''' aufgerufen, wenn es sich um einen '''LayoutManager2''' handelt und ein | ||
Component hinzugefügt wird (egal welche der oben genannten Methoden). In Object steht der Constraint des add-Aufrufes oder gegebenfalls <code>null</code>.*/ | Component hinzugefügt wird (egal welche der oben genannten Methoden). In Object steht der Constraint des add-Aufrufes oder gegebenfalls <code>null</code>.*/ | ||
− | addLayoutComponent(Component, Object)</ | + | addLayoutComponent(Component, Object)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Gibt die maximale Layout-Größe an. Oft wird einfach <code>Integer.MAX_VALUE</code> verwendet.*/ | /*Gibt die maximale Layout-Größe an. Oft wird einfach <code>Integer.MAX_VALUE</code> verwendet.*/ | ||
− | maximumLayoutSize(Container)</ | + | maximumLayoutSize(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Gibt die Ausrichtung der X-Achse des Component wieder. 0 bedeutet die "normale" Ausrichtung, 0.5 zentriert und 1 am weitesten von der "normalen" Ausrichtung entfernt.*/ | /*Gibt die Ausrichtung der X-Achse des Component wieder. 0 bedeutet die "normale" Ausrichtung, 0.5 zentriert und 1 am weitesten von der "normalen" Ausrichtung entfernt.*/ | ||
− | getLayoutAlignmentX(Container)</ | + | getLayoutAlignmentX(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Genauso wie '''getLayoutAlignmentX()''', nur natürlich über die Y-Achse.*/ | /*Genauso wie '''getLayoutAlignmentX()''', nur natürlich über die Y-Achse.*/ | ||
− | getLayoutAlignmentY(Container target)</ | + | getLayoutAlignmentY(Container target)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java"> |
/*Wird immer aufgerufen, wenn der Parent als invalid gekenntzeichnet wird, also es einer neuen Anordnung bedarf.*/ | /*Wird immer aufgerufen, wenn der Parent als invalid gekenntzeichnet wird, also es einer neuen Anordnung bedarf.*/ | ||
− | invalidateLayout(Container target)</ | + | invalidateLayout(Container target)</syntaxhighlight> |
− | |||
=Praxis - LayoutManager= | =Praxis - LayoutManager= | ||
− | Ich habe mich für einen LayoutManager entschieden, der die Compontents diagional anordnet (DiagonalLayout). Das heißt, der nächste Component wird genau ein Pixel weiter unten/rechts als die untere rechte Ecke des vorherigen platziert. Da dieser Manager keine Constraints abspeichert, implementieren wir das Interface LayoutManager . Da die add-Methode des LayoutManagers nur in seltensten Fällen sinnvoll ist, holen wir die Components direkt vom Parent. | + | Ich habe mich für einen LayoutManager entschieden, der die Compontents diagional anordnet (DiagonalLayout). Das heißt, der nächste Component wird genau ein Pixel weiter unten/rechts als die untere rechte Ecke des vorherigen platziert. Da dieser Manager keine Constraints abspeichert, implementieren wir das Interface LayoutManager . Da die add()-Methode des LayoutManagers nur in seltensten Fällen sinnvoll ist, holen wir die Components 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: | Zuerst müssen wir einmal wissen, was genau geschrieben werden soll. Wir überlegen also, was in den verschiedenen Methoden wie gemacht werden soll: | ||
− | < | + | <syntaxhighlight lang="java">addLayoutComponent(String, Component)</syntaxhighlight> |
− | Da wir keinen String oder | + | Da wir keinen {{JAPI|String}} oder {{JAPI|Constraints}} übergeben, wird dazu eigendlich nur <code>add(Component)</code> verwendet. Da die Methode da nicht aufgerufen wir implementieren wir sie leer. |
− | < | + | <syntaxhighlight lang="java">removeLayoutComponent(Component)</syntaxhighlight> |
Da nichts gespeichert wird, muss auch nichts entfernt werden, wenn ein Component vom Parent entfernt wird. Daher wird diese Methode ebenfalls leer implementiert. | Da nichts gespeichert wird, muss auch nichts entfernt werden, wenn ein Component vom Parent entfernt wird. Daher wird diese Methode ebenfalls leer implementiert. | ||
− | < | + | <syntaxhighlight lang="java">layoutContainer(Container)</syntaxhighlight> |
− | Hier werden wie gesagt die | + | Hier werden wie gesagt die Components angeordnet. Dazu wird zuerst eine Variable definiert, um die aktuelle Position der Anordnung zu speichern. Danach werden die Components des Parents durchgelaufen, an die aktuelle Position gesetzt und die Position wird um die PreferredSize nach rechts unten verschoben. |
− | < | + | <syntaxhighlight lang="java">preferredLayoutSize(Container)</syntaxhighlight> |
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 Rahmen (Auf diesen darf man nicht vergessen). | 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 Rahmen (Auf diesen darf man nicht vergessen). | ||
− | < | + | <syntaxhighlight lang="java">minimumLayoutSize(Container)</syntaxhighlight> |
− | Da ich keinen Grund sehe eine minimale Größe festzulegen, wird hier 0/0 zurückgegeben. | + | Da ich keinen Grund sehe, eine minimale Größe festzulegen, wird hier 0/0 zurückgegeben. |
Legen wir uns mal die Klasse an. | Legen wir uns mal die Klasse an. | ||
In meinen Beispiel im Package org.javaforum.tutorials.layoutmanager, und implementieren gleich die sowieso klaren Methoden: | In meinen Beispiel im Package org.javaforum.tutorials.layoutmanager, und implementieren gleich die sowieso klaren Methoden: | ||
− | < | + | <syntaxhighlight lang="java"> |
package org.javaforum.tutorials.layoutmanager; | package org.javaforum.tutorials.layoutmanager; | ||
Zeile 114: | Zeile 113: | ||
public void removeLayoutComponent(Component comp) {} | public void removeLayoutComponent(Component comp) {} | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
− | Ok. Nun müssen wir die | + | Ok. Nun müssen wir die Components 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 Components würde als über den Rahmen gezeichnet werden. Die Größe des Rahmens wird in den Insets genannten Objekt von einem Component geliefert: |
− | < | + | <syntaxhighlight lang="java"> |
public void layoutContainer(Container parent) { | public void layoutContainer(Container parent) { | ||
Insets insets = parent.getInsets(); | Insets insets = parent.getInsets(); | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
Als nächstes brauchen wir natürlich eine Variable, die die aktuelle Position speichert. Diese beginnt direkt nach dem Rahmen: | Als nächstes brauchen wir natürlich eine Variable, die die aktuelle Position speichert. Diese beginnt direkt nach dem Rahmen: | ||
− | < | + | <syntaxhighlight lang="java"> |
public void layoutContainer(Container parent) { | public void layoutContainer(Container parent) { | ||
Insets insets = parent.getInsets(); | Insets insets = parent.getInsets(); | ||
Point offs = new Point(insets.left, insets.top); | Point offs = new Point(insets.left, insets.top); | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
− | Ein Point ist einfach ein | + | Ein {{JAPI|Point}} ist einfach ein Wrapper für 2 int's, in der Regel gibt er einen Punkt auf einem 2-Dimensionalen Raster an. (Die Variablen sind public und heißen x und y.) |
− | Nun müssen wir in einer kleinen Schleife nur alle | + | Nun müssen wir in einer kleinen Schleife nur alle Components durchgehen und an die linke untere Ecke des letzten Components 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 Components, da sonst Lücken entstehen würden. |
− | < | + | <syntaxhighlight lang="java"> |
public void layoutContainer(Container parent) { | public void layoutContainer(Container parent) { | ||
Insets insets = parent.getInsets(); | Insets insets = parent.getInsets(); | ||
Zeile 155: | Zeile 154: | ||
} | } | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
Als letztes müssen wir nur noch preferredLayoutSize richtig implementieren. Wir verwenden dafür die selbe Schleife, mit dem Unterschied, dass wir nur die Größe hochzählen und nichts anordnen. | Als letztes müssen wir nur noch preferredLayoutSize richtig implementieren. Wir verwenden dafür die selbe Schleife, mit dem Unterschied, dass wir nur die Größe hochzählen und nichts anordnen. | ||
− | < | + | <syntaxhighlight lang="java"> |
public Dimension preferredLayoutSize(Container parent) { | public Dimension preferredLayoutSize(Container parent) { | ||
Insets insets = parent.getInsets(); | Insets insets = parent.getInsets(); | ||
Zeile 175: | Zeile 174: | ||
return(parentPrefSize); | return(parentPrefSize); | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
− | Statt einen Point verwenden wir nun eine 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. | + | Statt einen {{JAPI|Point}} verwenden wir nun eine {{JAPI|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 | + | Nun schreiben wir uns noch eine kurze main()-Methode, um das Layout auch testen zu können: |
− | < | + | <syntaxhighlight lang="java"> |
public static void main(String[] args) { | public static void main(String[] args) { | ||
EventQueue.invokeLater(new Runnable() { | EventQueue.invokeLater(new Runnable() { | ||
Zeile 205: | Zeile 204: | ||
}); | }); | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
− | Durch die pref. Size des ContentPanes setzt das JFrame auch die richtige Größe, und zeigt wie erwartet alle | + | Durch die pref. Size des ContentPanes setzt das {{JAPI|JFrame}} auch die richtige Größe, und zeigt wie erwartet alle Components in einer diagionalen Linie an: |
[[Datei:DiagonalLayout.png]] | [[Datei:DiagonalLayout.png]] | ||
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: | 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: | ||
− | < | + | <syntaxhighlight lang="java"> |
int j = Math.max(prefSize.width, prefSize.height); | int j = Math.max(prefSize.width, prefSize.height); | ||
offs.setLocation(offs.x + j, offs.y + j); | offs.setLocation(offs.x + j, offs.y + j); | ||
− | </ | + | </syntaxhighlight> |
− | 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 | + | 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 Components mit unterschiedlichen Layouts nicht besser eignen würden) sollte es jeden einfach fallen, sein Layout seinen Wünschen anzupassen. |
− | |||
=Praxis - LayoutManager2= | =Praxis - LayoutManager2= | ||
Zeile 225: | Zeile 223: | ||
Folgende Methoden enthalten die Hauptimplemention: | Folgende Methoden enthalten die Hauptimplemention: | ||
− | < | + | <syntaxhighlight lang="java">addLayoutComponent(Component, Object)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">removeLayoutComponent(Component)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">preferredLayoutSize(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">layoutContainer(Container)</syntaxhighlight> |
Folgende Methoden werden leer oder mit Standartrückgabewerten implementiert: | Folgende Methoden werden leer oder mit Standartrückgabewerten implementiert: | ||
− | < | + | <syntaxhighlight lang="java">addLayoutComponent(String, Component)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">invalidateLayout(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">getLayoutAlignmentX(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">getLayoutAlignmentY(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">maximumLayoutSize(Container)</syntaxhighlight> |
− | < | + | <syntaxhighlight lang="java">minimumLayoutSize(Container)</syntaxhighlight> |
Zeile 243: | Zeile 241: | ||
Unsere Klasse sieht nun fürs Erste so aus: | Unsere Klasse sieht nun fürs Erste so aus: | ||
− | < | + | <syntaxhighlight lang="java"> |
package org.javaforum.tutorials.layoutmanager; | package org.javaforum.tutorials.layoutmanager; | ||
Zeile 309: | Zeile 307: | ||
− | </ | + | </syntaxhighlight> |
Widmen wir uns nun den Hinzufügen der Component en. | Widmen wir uns nun den Hinzufügen der Component en. | ||
Eigendlich müssen wir nur prüfen, ob es sich um eins unserer 2 Objekte handelt. | Eigendlich müssen wir nur prüfen, ob es sich um eins unserer 2 Objekte handelt. | ||
− | < | + | <syntaxhighlight lang="java"> |
public void addLayoutComponent(Component comp, Object constraints) { | public void addLayoutComponent(Component comp, Object constraints) { | ||
if(constraints == LEFT) { | if(constraints == LEFT) { | ||
Zeile 322: | Zeile 320: | ||
} | } | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
Sehr simpel. Ist das Object für Links angegeben wird es der linken Liste hinzugefügt wenn das rechte 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 StackOverflow . | Sehr simpel. Ist das Object für Links angegeben wird es der linken Liste hinzugefügt wenn das rechte 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 StackOverflow . | ||
removeLayoutComponent wird noch einfacher, wir entfernen den Component aus der linken Liste und wenn es darin nicht vorhanden ist aus der rechten. | removeLayoutComponent wird noch einfacher, wir entfernen den Component aus der linken Liste und wenn es darin nicht vorhanden ist aus der rechten. | ||
− | < | + | <syntaxhighlight lang="java"> |
public void removeLayoutComponent(Component comp) { | public void removeLayoutComponent(Component comp) { | ||
if(!leftComponents.remove(comp)) { | if(!leftComponents.remove(comp)) { | ||
Zeile 332: | Zeile 330: | ||
} | } | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
Als nächstes folgt die Anordnung der Componenten. | Als nächstes folgt die Anordnung der Componenten. | ||
Ich schreibe einfach 2 Schleifen die die Component en anordnet. Zwar ließe sich das sicher durch Methoden abstrahieren, allerdings denke ich, dass der Code dann schwerer zu lesen ist. | Ich schreibe einfach 2 Schleifen die die Component en anordnet. Zwar ließe sich das sicher durch Methoden abstrahieren, allerdings denke ich, dass der Code dann schwerer zu lesen ist. | ||
− | < | + | <syntaxhighlight lang="java"> |
public void layoutContainer(Container parent) { | public void layoutContainer(Container parent) { | ||
Insets insets = parent.getInsets(); | Insets insets = parent.getInsets(); | ||
Zeile 366: | Zeile 364: | ||
} | } | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
Die erste Schleife ist im Grunde genau das Selbe, wie wir bereits oben hatten. 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 Component kommt an die Reihe. | Die erste Schleife ist im Grunde genau das Selbe, wie wir bereits oben hatten. 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 Component kommt an die Reihe. | ||
Zeile 372: | Zeile 370: | ||
Als letztes müssen wir noch die optimale Größe errechnen. Dazu benutzen wir wieder eine modifizierte Version der Layout -Schleifen. Wir zählen einfach die Breite sämtlicher Component s zusammen. Da wir die Component s nur in einer Reihe anordnen benötigen wir nur die Höhe des höchsten Component (+ Rahmenhöhe). Dafür verwenden wir einfach die statische Methode max in Math, die die größere Zahl der beiden Parameter zurückliefert. | Als letztes müssen wir noch die optimale Größe errechnen. Dazu benutzen wir wieder eine modifizierte Version der Layout -Schleifen. Wir zählen einfach die Breite sämtlicher Component s zusammen. Da wir die Component s nur in einer Reihe anordnen benötigen wir nur die Höhe des höchsten Component (+ Rahmenhöhe). Dafür verwenden wir einfach die statische Methode max in Math, die die größere Zahl der beiden Parameter zurückliefert. | ||
− | < | + | <syntaxhighlight lang="java"> |
public Dimension preferredLayoutSize(Container parent) { | public Dimension preferredLayoutSize(Container parent) { | ||
Insets insets = parent.getInsets(); | Insets insets = parent.getInsets(); | ||
Zeile 391: | Zeile 389: | ||
return(parentPrefSize); | return(parentPrefSize); | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
Nun noch eine kurze Main zum testen: | Nun noch eine kurze Main zum testen: | ||
− | < | + | <syntaxhighlight lang="java"> |
public static void main(String[] args) { | public static void main(String[] args) { | ||
EventQueue.invokeLater(new Runnable() { | EventQueue.invokeLater(new Runnable() { | ||
Zeile 414: | Zeile 412: | ||
}); | }); | ||
} | } | ||
− | </ | + | </syntaxhighlight> |
[[Datei:lrfl.png]] | [[Datei:lrfl.png]] | ||
Zeile 423: | Zeile 421: | ||
=Quellen und Weiterführendes Material= | =Quellen und Weiterführendes Material= | ||
*http://docs.oracle.com/javase/tutorial/uiswing/layout/custom.html | *http://docs.oracle.com/javase/tutorial/uiswing/layout/custom.html | ||
+ | *[[LayoutManager der Standard-Java-API]] | ||
+ | |||
+ | [[Kategorie:LayoutManager]] | ||
+ | [[Kategorie:Tutorials (Java)]] | ||
+ | [[Kategorie:Swing]] | ||
--[[Benutzer:Volvagia|Volvagia]] (06.11.2012, 04:01 Uhr) | --[[Benutzer:Volvagia|Volvagia]] (06.11.2012, 04:01 Uhr) |
Aktuelle Version vom 2. April 2018, 16:04 Uhr
Inhaltsverzeichnis
Theorie
Es besteht neben den vordefinierten LayoutManager
n der JRE-Standartbibliothek und externen APIs natürlich die Möglichkeit, eigene Manager zu entwickeln. Jeder, der schon einmal mit "Null-Layout" gearbeitet hat, weiß, dass Components mit den Methoden setLocation()
und setSize()
bzw. gleichzeitig per setBounds()
ihre Position und Größe vorgegeben werden. Der 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 einem ComponentListener
erreichen, der dafür bei Weitem nicht optimal ist.
Um einen LayoutManager zu schreiben, gibt es prinzipiell 2 Wege:
Man implementiert das Interface LayoutManager
oder LayoutManager2
. Dabei wird 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 Components direkt vom Parent, da sie keine weiteren Informationen abspeichern müssen. Hingegen wird LayoutManager2 benutzt, falls ein Parameter (genannt Constraints) wärend des Hinzufügens übergeben werden soll. Ein Beispiel wäre das BorderLayout, welches mit 5 Strings arbeitet, um die Position des Components anzugeben. Für den Parent des LayoutManagers spielt es keine Rolle, welches Interface benutzt wird. Es wird meistens von dem Component auf dem das Layout sitzt geprüft, um welches Interface es sich handelt und so die demensprechende Action durchgeführt. LayoutManager2 erweitert LayoutManager und hat so auch Methoden, die es eigendlich nicht benötigt.
Methoden von LayoutManager
LayoutManager definiert folgende Methoden:
addLayoutComponent(String, Component)
Es gibt bei Container
n allgemein 5 add()-Methoden für Components.
add(Component)
add(Component, int)
add(Component, Object)
add(String, Component)
/*Diese Methode wird aufgerufen, sobald ein Component dem Parent mit einen der letzten 3 Methoden hinzugefügt wird und es sich nicht um einen '''LayoutManager2''' handelt.*/
add(Component, String, Object)
/*Wird aufgerufen, sobald ein Component von den Parent entfernt wird.*/
removeLayoutComponent(Component)
/*Gibt die optimale Größe des Parents wieder.*/
preferredLayoutSize(Container)
Diese kann extern für jeden Component gesetzt werden, besitzt er jedoch einen LayoutManager (und wird sie nicht extra gesetzt) wird die Methode des LayoutManagers verwendet. Z. B. benutzen JScrollPane
s die Größe, um den Bescrollbaren Bereiches festzustellen. JLabel
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.
/*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.*/
minimumLayoutSize(Container)
/*Die wohl wichtigste Methode. Hier werden die Components neu angeordnet.*/
layoutContainer(Container)
Methoden von LayoutManager2
LayoutManager2 erweitert wie oben bereits gesagt LayoutManager und besitzt damit die selben Methoden, zuzüglich dieser:
/*Diese add()-Methode wird statt '''addLayoutComponent(String, Component)''' aufgerufen, wenn es sich um einen '''LayoutManager2''' handelt und ein
Component hinzugefügt wird (egal welche der oben genannten Methoden). In Object steht der Constraint des add-Aufrufes oder gegebenfalls <code>null</code>.*/
addLayoutComponent(Component, Object)
/*Gibt die maximale Layout-Größe an. Oft wird einfach <code>Integer.MAX_VALUE</code> verwendet.*/
maximumLayoutSize(Container)
/*Gibt die Ausrichtung der X-Achse des Component wieder. 0 bedeutet die "normale" Ausrichtung, 0.5 zentriert und 1 am weitesten von der "normalen" Ausrichtung entfernt.*/
getLayoutAlignmentX(Container)
/*Genauso wie '''getLayoutAlignmentX()''', nur natürlich über die Y-Achse.*/
getLayoutAlignmentY(Container target)
/*Wird immer aufgerufen, wenn der Parent als invalid gekenntzeichnet wird, also es einer neuen Anordnung bedarf.*/
invalidateLayout(Container target)
Praxis - LayoutManager
Ich habe mich für einen LayoutManager entschieden, der die Compontents diagional anordnet (DiagonalLayout). Das heißt, der nächste Component wird genau ein Pixel weiter unten/rechts als die untere rechte Ecke des vorherigen platziert. Da dieser Manager keine Constraints abspeichert, implementieren wir das Interface LayoutManager . Da die add()-Methode des LayoutManagers nur in seltensten Fällen sinnvoll ist, holen wir die Components 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:
addLayoutComponent(String, Component)
Da wir keinen String
oder Constraints
übergeben, wird dazu eigendlich nur add(Component)
verwendet. Da die Methode da nicht aufgerufen wir implementieren wir sie leer.
removeLayoutComponent(Component)
Da nichts gespeichert wird, muss auch nichts entfernt werden, wenn ein Component vom Parent entfernt wird. Daher wird diese Methode ebenfalls leer implementiert.
layoutContainer(Container)
Hier werden wie gesagt die Components angeordnet. Dazu wird zuerst eine Variable definiert, um die aktuelle Position der Anordnung zu speichern. Danach werden die Components des Parents durchgelaufen, an die aktuelle Position gesetzt und die Position wird um die PreferredSize nach rechts unten verschoben.
preferredLayoutSize(Container)
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 Rahmen (Auf diesen darf man nicht vergessen).
minimumLayoutSize(Container)
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:
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 Components 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 Components würde als über den Rahmen gezeichnet werden. Die Größe des Rahmens wird in den Insets genannten Objekt von einem Component geliefert:
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:
public void layoutContainer(Container parent) {
Insets insets = parent.getInsets();
Point offs = new Point(insets.left, insets.top);
}
Ein Point
ist einfach ein Wrapper für 2 int's, in der Regel gibt er einen Punkt auf einem 2-Dimensionalen Raster an. (Die Variablen sind public und heißen x und y.)
Nun müssen wir in einer kleinen Schleife nur alle Components durchgehen und an die linke untere Ecke des letzten Components 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 Components, da sonst Lücken entstehen würden.
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 preferredLayoutSize richtig implementieren. Wir verwenden dafür die selbe Schleife, mit dem Unterschied, dass wir nur die Größe hochzählen und nichts anordnen.
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 Point
verwenden wir nun eine 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 Layout auch testen zu können:
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 ContentPanes setzt das JFrame
auch die richtige Größe, und zeigt wie erwartet alle Components in einer diagionalen Linie an:
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:
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 Components mit unterschiedlichen Layouts nicht besser eignen würden) sollte es jeden einfach fallen, sein Layout seinen Wünschen anzupassen.
Praxis - LayoutManager2
Jetzt noch ein LayoutManager , der das Interface LayoutManager2 implementiert und damit auch Parameteter übergeben werden können. Dafür werden wir einen LayoutManager schreiben, der wie das FlowLayout anordnet. Der Unterschiede ist, dass 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 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 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 Component en sehe ich 2 LinkedList s vor. Jeweils eine für die linken und für die rechten Component s. Als Abstand zwischen ihnen definiere ich einen Standart von 2, kann allerdings auch per Constructor gesetzt werden. y ist immer die Höhe des Rahmens. Um die Position (Links oder Rechts) anzugeben verwende ich 2 simple Object e. Ist keines davon angegeben, wird standartgemäß links angeordnet.
Unsere Klasse sieht nun fürs Erste so aus:
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 Component en. Eigendlich müssen wir nur prüfen, ob es sich um eins unserer 2 Objekte handelt.
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 Object für Links angegeben wird es der linken Liste hinzugefügt wenn das rechte 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 StackOverflow .
removeLayoutComponent wird noch einfacher, wir entfernen den Component aus der linken Liste und wenn es darin nicht vorhanden ist aus der rechten.
public void removeLayoutComponent(Component comp) {
if(!leftComponents.remove(comp)) {
rightComponents.remove(comp);
}
}
Als nächstes folgt die Anordnung der Componenten.
Ich schreibe einfach 2 Schleifen die die Component en anordnet. Zwar ließe sich das sicher durch Methoden abstrahieren, allerdings denke ich, dass der Code dann schwerer zu lesen ist.
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. 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 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 Layout -Schleifen. Wir zählen einfach die Breite sämtlicher Component s zusammen. Da wir die Component s nur in einer Reihe anordnen benötigen wir nur die Höhe des höchsten Component (+ Rahmenhöhe). Dafür verwenden wir einfach die statische Methode max in Math, die die größere Zahl der beiden Parameter zurückliefert.
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:
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);
}
});
}
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.
Quellen und Weiterführendes Material
- http://docs.oracle.com/javase/tutorial/uiswing/layout/custom.html
- LayoutManager der Standard-Java-API
--Volvagia (06.11.2012, 04:01 Uhr)