Swing: vertically stacked components without MigLayout
I finally got the behavior I want for vertically stacking components that have a preferred height that changes with time. But I needed to use MigLayout.
Is there a way to do this w/o MigLayout? (It's for a library and I don't want to force the dependency unless I have to)
Here's the behavior I'm looking for (which my test program achieves):
In vertical order, there's a resize button, "empty space" (well, a JLabel marked as such), a red rectangle, and a green square. The resize button has fixed height. The red square has a random size that can change at arbitrary times. The green square sets its preferred height to match its width, and I want to expand its width to fill the container. The empty space expands horizontally and vertically to fill the remaining space in the container.
What would work instead of MigLayout?
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import net.miginfocom.swing.MigLayout;
public class AutoResizeDemo extends JPanel
{
static private class ResizingPanel extends JPanel
{
final private Color color;
private Dimension dpref = new Dimension(100,100);
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeig开发者_Python百科ht();
g.setColor(this.color);
g.fillRect(0, 0, w, h);
g.setColor(Color.BLACK);
g.drawRect(0, 0, w-1, h-1);
String s = this.dpref.width+"x"+this.dpref.height;
FontMetrics fm = g.getFontMetrics();
g.drawString(s, 0, fm.getHeight());
}
public ResizingPanel(Color color, boolean isSquare)
{
this.color = color;
if (isSquare)
{
addComponentListener(new ComponentAdapter() {
@Override public void componentResized(ComponentEvent e) {
doResize(getWidth(), getWidth());
}
});
}
}
@Override public Dimension getPreferredSize() {
return this.dpref;
}
public void doResize(int w, int h)
{
this.dpref = new Dimension(w, h);
revalidate();
}
}
public AutoResizeDemo()
{
super(new MigLayout("","[grow]",""));
setPreferredSize(new Dimension(200, 800));
final ResizingPanel resizingPanelRandom = new ResizingPanel(Color.RED, false);
ResizingPanel resizingPanelSquare = new ResizingPanel(Color.GREEN, true);
JPanel buttonPanel = new JPanel(new FlowLayout());
final Random rand = new Random();
addButton(buttonPanel, "resize",new Runnable() {
@Override public void run() {
resizingPanelRandom.doResize(
rand.nextInt(100)+100,
rand.nextInt(100)+100
);
}
});
add(buttonPanel, "wrap");
JLabel spaceLabel = new JLabel("empty space");
spaceLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
add(spaceLabel, "push, grow, wrap");
add(resizingPanelRandom, "wrap");
add(resizingPanelSquare,"pushx, growx, wrap");
}
private void addButton(JPanel panel, String title, final Runnable r) {
JButton button = new JButton(title);
button.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent e) {
r.run();
}
});
panel.add(button);
}
public static void main(String[] args) {
JFrame frame = new JFrame(AutoResizeDemo.class.getSimpleName());
frame.setContentPane(new AutoResizeDemo());
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Use a BoxLayout.
You would use Box.createVerticalGlue() for the empty space.
BoxLayout respects the maximum size of a component, so you would probably need to override the getMaximumSize() method to return the preferred size for the red and green boxes.
For the green box you would also need to Override getPreferredSize() to keep the height in sync with the width.
You can solve this using SpringLayout
by wiring all your compenents together and to the edges of their container.
Button Panel
left and top of the button panel to left and top of the container panel
Green Panel
left, right and bottom to the left, right and bottom of the container panel
Red Panel
left to left of container panel and bottom to top of the green panel
Space Label
top to south of the button panel, left and right to left and right of the container panel, bottom to top of the red panel
Edit: I love SpringLayout, there's nothing it can't do.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SpringLayout;
public class AutoResizeDemo2 extends JPanel {
static private class ResizingPanel extends JPanel {
final private Color color;
private Dimension dpref = new Dimension(100, 100);
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int w = getWidth();
int h = getHeight();
g.setColor(this.color);
g.fillRect(0, 0, w, h);
g.setColor(Color.BLACK);
g.drawRect(0, 0, w - 1, h - 1);
String s = this.dpref.width + "x" + this.dpref.height;
FontMetrics fm = g.getFontMetrics();
g.drawString(s, 0, fm.getHeight());
}
public ResizingPanel(Color color, boolean isSquare) {
this.color = color;
if (isSquare) {
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
doResize(getWidth(), getWidth());
}
});
}
}
@Override
public Dimension getPreferredSize() {
return this.dpref;
}
public void doResize(int w, int h) {
this.dpref = new Dimension(w, h);
revalidate();
}
}
public AutoResizeDemo2() {
SpringLayout layout = new SpringLayout();
setLayout(layout);
setPreferredSize(new Dimension(200, 800));
final ResizingPanel resizingPanelRandom = new ResizingPanel(Color.RED, false);
ResizingPanel resizingPanelSquare = new ResizingPanel(Color.GREEN, true);
JPanel buttonPanel = new JPanel(new FlowLayout());
final Random rand = new Random();
addButton(buttonPanel, "resize", new Runnable() {
@Override
public void run() {
resizingPanelRandom.doResize(rand.nextInt(100) + 100, rand.nextInt(100) + 100);
}
});
add(buttonPanel);
layout.putConstraint(SpringLayout.NORTH, buttonPanel, 5, SpringLayout.NORTH, this);
layout.putConstraint(SpringLayout.WEST, buttonPanel, 5, SpringLayout.WEST, this);
JLabel spaceLabel = new JLabel("empty space");
spaceLabel.setBorder(BorderFactory.createLineBorder(Color.BLACK));
add(resizingPanelSquare);
layout.putConstraint(SpringLayout.SOUTH, resizingPanelSquare, -5, SpringLayout.SOUTH, this);
layout.putConstraint(SpringLayout.WEST, resizingPanelSquare, 5, SpringLayout.WEST, this);
layout.putConstraint(SpringLayout.EAST, resizingPanelSquare, -5, SpringLayout.EAST, this);
add(resizingPanelRandom);
layout.putConstraint(SpringLayout.SOUTH, resizingPanelRandom, -5, SpringLayout.NORTH, resizingPanelSquare);
layout.putConstraint(SpringLayout.WEST, resizingPanelRandom, 5, SpringLayout.WEST, this);
add(spaceLabel);
layout.putConstraint(SpringLayout.NORTH, spaceLabel, 5, SpringLayout.SOUTH, buttonPanel);
layout.putConstraint(SpringLayout.WEST, spaceLabel, 5, SpringLayout.WEST, this);
layout.putConstraint(SpringLayout.EAST, spaceLabel, -5, SpringLayout.EAST, this);
layout.putConstraint(SpringLayout.SOUTH, spaceLabel, -5, SpringLayout.NORTH, resizingPanelRandom);
}
private void addButton(JPanel panel, String title, final Runnable r) {
JButton button = new JButton(title);
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
r.run();
}
});
panel.add(button);
}
public static void main(String[] args) {
JFrame frame = new JFrame(AutoResizeDemo2.class.getSimpleName());
frame.setContentPane(new AutoResizeDemo2());
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
SpringLayout is difficult to determine how it is laid out without a lot of analyzing. Try TableLayout. The only tricky part of your layout is the green square's height being equal to its width. This is a bit unusual for a layout manager to support, so I would just special case it. A runnable example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.esotericsoftware.tablelayout.swing.Table;
public class Test extends JFrame {
JButton button;
JPanel red, green;
public Test () {
button = new JButton("Resize");
button.addActionListener(new ActionListener() {
public void actionPerformed (ActionEvent e) {
red.setPreferredSize(new Dimension(138, new Random().nextInt(190) + 10));
red.revalidate();
}
});
red = new JPanel();
red.setPreferredSize(new Dimension(138, 145));
red.setBackground(Color.red);
green = new JPanel();
green.setPreferredSize(new Dimension(100, 100));
green.setBackground(Color.green);
// The DSL can be much easier to describe complex hierarchies.
boolean dsl = false;
if (dsl)
dsl();
else
javaApi();
setSize(160, 400);
setDefaultCloseOperation(DISPOSE_ON_CLOSE);
setLocationRelativeTo(null);
setVisible(true);
}
private void javaApi () {
final Table table = new Table() {
public void layout () {
green.setPreferredSize(new Dimension(getWidth(), getWidth()));
super.layout();
}
};
table.pad(10).defaults().left().space(5);
table.addCell(button);
table.row();
table.addCell().expandY();
table.row();
table.addCell(red);
table.row();
table.addCell(green).expandX().fillX();
getContentPane().add(table);
}
private void dsl () {
final Table table = new Table() {
public void layout () {
green.setPreferredSize(new Dimension(getWidth(), getWidth()));
super.layout();
}
};
table.register("button", button);
table.register("red", red);
table.register("green", green);
table.parse("pad:10 * left space:5 " //
+ "[button] ---" //
+ "[] expandy ---" //
+ "[red] ---" //
+ "[green] expandx fillx" //
);
getContentPane().add(table);
}
public static void main (String[] args) throws Exception {
new Test();
}
}
Being table based, it is easy to get an idea of the layout at a glance. I included code for using the Java API and also the DSL. The Java API is nice since you get completion. Here is just the layout code:
table.pad(10).defaults().left().space(5);
table.addCell(button);
table.row();
table.addCell().expandY();
table.row();
table.addCell(red);
table.row();
table.addCell(green).expandX().fillX();
The DSL is nice for describing hierarchies, probably not necessary for this example. Unfortunately Java doesn't have a verbatim string, though a large UI could be described in a file. The DSL for this example without the Java string quotes would be:
pad:10 * left space:5
[button]
---
[] expandy
---
[red]
---
[green] expandx fillx
精彩评论