PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [Java] Swing-Layout-Problem


Senior Sanchez
2006-09-28, 21:39:13
So, jetzt sind die Java-Layout Profis gefragt.

Das Problem ist folgendes.

Ich möchte in einem Container an der linken Seite vier Panels übereinander zeigen.
Das ist nicht so schwer.

Die vier Panels sind unterschiedlich groß, sollen aber weiterhin den vollen Platz ausnutzen, der ihnen vertikal insgesamt zur Verfügung steht.

Das besondere ist jetzt aber, dass diese Panels sich auch minimieren lassen. Ein Klick auf ein "-" in der jeweiligen Titelleiste jeden Panels lässt es minimieren, sodass nur die Titelleiste sichtbar ist. Das soll natürlich zur Folge haben, dass der nun wieder verfügbare Platz auf die anderen Panels verteilt werden soll und zwar, dass auch die Bedingung erfüllt ist, dass sämtlicher Platz ausgefüllt ist.

Hat da jemand ne Idee, wie man das machen könnte? Ich habe es jetzt mit GridBagLayout und BoxLayout probiert, aber es erfüllt einfach nicht meine Erwartungen. Kann jemand helfen?

Könnte FormsLayout das vllt besser?

ethrandil
2006-09-28, 23:00:39
Ich hätte es mit den selben Layoutmanagern wie du versucht.
Beim Klick muss eben die maximal- und preffered size aktualisiert werden und die .validate()-Methode des Containers aufgerufen werden.

Klappt das nicht?

- eth

Senior Sanchez
2006-09-28, 23:35:19
Ich hätte es mit den selben Layoutmanagern wie du versucht.
Beim Klick muss eben die maximal- und preffered size aktualisiert werden und die .validate()-Methode des Containers aufgerufen werden.

Klappt das nicht?

- eth

Nein, das klappt nicht.

Das Problem beim GridBagLayout, welches ich immer habe ist, dass es sich zwar resized, aber dafür beim minimieren nicht im Norden der Zelle ist, sondern in der Mitte hängt und das will ich nicht. Es soll sich alles im Norden orientieren.

HellHorse
2006-09-28, 23:35:34
Diese WindowsXP-Task Dinger am selber implementieren? ;)
Gibt's in SWT und heisst ExpandBar, resp ExpandableComposite in Eclipse Forms.
Für Swing gibts z.B. JXTaskPane (Teil von SwingX (https://swingx.dev.java.net/)).
http://swinglabs.org/screenshots/JXTaskPaneWindowsXP.png
(gibt noch andere 3rd Party Swing components die was ähnliches bieten)

Ansonsten siehe eth

Du kannst natürlich auch selbst einen LayoutManager implementieren, der genau das macht was du willst. Sollte nicht so viel Aufwand sein (vielleicht 20 LOC). Tönt etwas wild, habe aber damit bisher immer gute Erfahrungen gemacht.

Senior Sanchez
2006-09-29, 00:16:04
Diese WindowsXP-Task Dinger am selber implementieren? ;)
Gibt's in SWT und heisst ExpandBar, resp ExpandableComposite in Eclipse Forms.
Für Swing gibts z.B. JXTaskPane (Teil von SwingX (https://swingx.dev.java.net/)).
http://swinglabs.org/screenshots/JXTaskPaneWindowsXP.png
(gibt noch andere 3rd Party Swing components die was ähnliches bieten)

Ansonsten siehe eth

Du kannst natürlich auch selbst einen LayoutManager implementieren, der genau das macht was du willst. Sollte nicht so viel Aufwand sein (vielleicht 20 LOC). Tönt etwas wild, habe aber damit bisher immer gute Erfahrungen gemacht.

Ja, genau, in diese Richtung soll das gehen. Ich möchte aber sofern es geht keine externen Komponenten einbinden, sondern das schon mit Bordmitteln basteln.

20 LOC? Das kann ich mir ehrlich gesagt nicht so recht vorstellen, aber du hast mich schon öfter etwas besserem belehrt. Wennde mal Zeit hast, könnteste das mal probieren? Auch wenns nicht klappen sollte, wäre echt super.

Ich habs mittlerweile auch per Forms-Layout probiert. Geht auch nicht so recht.

ethrandil
2006-09-29, 00:37:36
Also ich weiß nicht ob ich dein Problem komplett verstanden habe, aber der folgende Code tut glaub ich sowas, wie du brauchst.


MiniButton.java

package de.ethrandil.minimize;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;

public class MiniButton extends JButton implements ActionListener{
private static final long serialVersionUID = -7270102892981311788L;

private boolean minimized = true;

private final Dimension MINI_SIZE = new Dimension(150,30);
private final Dimension MAXI_SIZE = new Dimension(150,200);

public MiniButton() {
super();

addActionListener(this);

actionPerformed(null);
}

public void actionPerformed(ActionEvent arg0) {
if(minimized)
{
minimized = false;
setMinimumSize(MAXI_SIZE);
setPreferredSize(MAXI_SIZE);
setMaximumSize(MAXI_SIZE);
setText("Minimize Me!");
}
else
{
minimized = true;
setMinimumSize(MINI_SIZE);
setPreferredSize(MINI_SIZE);
setMaximumSize(MINI_SIZE);
setText("Maximize Me!");
}
}

}

MiniTest.java

package de.ethrandil.minimize;

import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class MiniTest extends JFrame implements ActionListener{
private static final long serialVersionUID = -5029764228469016698L;

public MiniTest() throws HeadlessException {
super("MiniButtonTest");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

setLayout(new BoxLayout(this.getContentPane(), BoxLayout.PAGE_AXIS));

MiniButton button = new MiniButton();
button.addActionListener(this);
add(button);
button = new MiniButton();
button.addActionListener(this);
add(button);
button = new MiniButton();
button.addActionListener(this);
add(button);

pack();
}

public void actionPerformed(ActionEvent arg0) {
SwingUtilities.invokeLater(new Runnable(){

public void run() {
validate();
pack();
}});
}

public static void main(String[] args) {
MiniTest mt = new MiniTest();
mt.setVisible(true);
}
}


mfg
- eth

Senior Sanchez
2006-09-29, 07:50:02
Danke für das Beispiel, aber das ist leider nicht ganz das, was ich suche.

Wenn ich einen Button verkleinere, dann soll das Frame nicht kleiner werden, sondern genauso groß bleiben, wie es war. Die Buttons dadrin sollen sich aber dann den zusätzlichen Platz greifen und größer werden.

HellHorse
2006-09-29, 11:56:11
Danke für das Beispiel, aber das ist leider nicht ganz das, was ich suche.

Wenn ich einen Button verkleinere, dann soll das Frame nicht kleiner werden, sondern genauso groß bleiben, wie es war. Die Buttons dadrin sollen sich aber dann den zusätzlichen Platz greifen und größer werden.
dann ändere den Code ab auf:
public void actionPerformed(ActionEvent arg0) {
validate();
}

Senior Sanchez
2006-09-29, 13:27:03
dann ändere den Code ab auf:
public void actionPerformed(ActionEvent arg0) {
validate();
}

Habe ich auch gemacht, aber er resized die Button nicht, da ergibt sich dann einfach ne weiße Lücke.

Das Problem ist ja auch außerdem, dass ich noch keine absoluten Dimension habe, sprich ich kann noch nicht sagen, dass ein bestimmtes Panel so und so groß ist.

HellHorse
2006-09-29, 15:38:49
Dann wollen wir mal:

Zuerst das Interface um rauszufinden ob etwas zusammengeklappt ist:

public interface Collapsable {
public boolean isCollapsed();

public void toggleCollapse();
}

Nun der LayoutManager:

public class CollapsableLayout implements LayoutManager {

public void addLayoutComponent(String name, Component comp) {
// ignore
}

public void layoutContainer(Container parent) {
Dimension preferredSize = this.preferredLayoutSize(parent);
Insets parentInsets = parent.getInsets();
int excessHeight = parent.getHeight() -parentInsets.top - parentInsets.bottom - preferredSize.height;
int componentWidth = parent.getWidth() - parentInsets.left - parentInsets.right;
int expandedHeigth = 0;
int y = parentInsets.top;
int x = parentInsets.left;

// compute total width of expanded components
for (Component each : parent.getComponents()) {
Collapsable collapsable = (Collapsable) each;
if (!collapsable.isCollapsed()) {
expandedHeigth += each.getPreferredSize().height;
}
}

for (Component each : parent.getComponents()) {
int preferredHeight = each.getPreferredSize().height;
Collapsable collapsable = (Collapsable) each;
if (collapsable.isCollapsed()) {
each.setBounds(x, y, componentWidth, preferredHeight);
y += preferredHeight;
} else {
int additionalHeigt = (int) ((float) excessHeight / expandedHeigth * preferredHeight);
each.setBounds(x, y, componentWidth, preferredHeight + additionalHeigt);
y += preferredHeight + additionalHeigt;
}
}

}

public Dimension minimumLayoutSize(Container parent) {
int width = 0;
int height = 0;
for (Component each : parent.getComponents()) {
Dimension min = each.getMinimumSize();
height += min.height;
width = max(width, min.width);
}
return new Dimension(width, height);
}

public Dimension preferredLayoutSize(Container parent) {
int width = 0;
int height = 0;
for (Component each : parent.getComponents()) {
Dimension min = each.getPreferredSize();
height += min.height;
width = max(width, min.width);
}
return new Dimension(width, height);
}

public void removeLayoutComponent(Component comp) {
// ignore
}

}

Ok, vielleicht nicht 20 LOC aber auch nicht dramatisch mehr. Könnte man noch effizienter machen, mit caches und so.
(Habe ich schon mal erwähnt, dass der Java Numbertower scheisse ist?)

ein hübsche Action

public class CollapseAction extends AbstractAction {

private static final long serialVersionUID = -9108650612554266143L;

private static final String ACTION_COMMAND = "collapsable-command";

private Collapsable collapsable;

private static final String collapsed = "+";

private static final String expanded = "-";

public CollapseAction(Collapsable collapsable) {
super(collapsable.isCollapsed() ? collapsed : expanded);
this.putValue(ACTION_COMMAND_KEY, ACTION_COMMAND);
this.collapsable = collapsable;
}

public void actionPerformed(ActionEvent e) {
this.putValue(NAME, !this.collapsable.isCollapsed() ? collapsed : expanded);
this.collapsable.toggleCollapse();
}
}


Und noch etwas Code um das Ganze zu testen:

public class DemoCollapsable extends JPanel implements Collapsable {

private static final long serialVersionUID = 408509222537706199L;

private boolean collapsed;

public DemoCollapsable(String title) {
super();
this.setLayout(new BoxLayout(this, Y_AXIS));

JPanel innerPanel = new JPanel();
innerPanel.setLayout(new BoxLayout(innerPanel, X_AXIS));
innerPanel.add(new JLabel(title));
innerPanel.add(createHorizontalGlue());
innerPanel.add(new JButton(new CollapseAction(this)));

this.add(innerPanel);
this.add(createVerticalGlue());
}

public boolean isCollapsed() {
return this.collapsed;
}

public void toggleCollapse() {
this.collapsed = !this.collapsed;
this.validate();
}

public static JFrame createFrame() {
JFrame frame = new JFrame("HoMM V");
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
((JComponent) frame.getContentPane()).setBorder(createEmptyBorder(12, 12, 11, 11));
frame.setLayout(new CollapsableLayout());

String[] sides = {"Academy", "Dungeon", "Haven", "Inferno", "Necropolis", "Sylvan"};
for (String side : sides) {
frame.add(new DemoCollapsable(side));
}
frame.setSize(200, 400);
return frame;
}

public static void main(String[] args) {
invokeLater(new Runnable() {

public void run() {
JFrame frame = createFrame();
frame.setVisible(true);
}

});
}

}

ethrandil
2006-09-29, 15:43:26
Hallo,
du kannst auch neben der ersten Änderung von HellHorse den entsprechenden Code in MiniButton.java ändern in:

minimized = false;
setMinimumSize(MAXI_SIZE);
setPreferredSize(MAXI_SIZE);
setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
setText("Minimize Me!");

mfg
- eth

Senior Sanchez
2006-09-29, 17:13:56
Hey,

Herzlichen Dank euch beiden! Ihr habt mir echt super geholfen.

Ich habe mich erstmal für den Einsatz von ethrandil entschieden. Nach ein paar Modifikationen an meinem Code läuft das echt sauber.

Auf deinen Ansatz, HellHorse, komme ich bei Zeiten zurück.

Dankeschön :)