This is a follow-up to this question. Any viable answer will also answer that one.
What layout may be used with as little modification as possible to replicate the aligning nature of a FlowLayout, but never linebreak and also be available in a from-top-to-bottom flavour?
The obvious candidate, BoxLayout, does not work nicely with JPanels. Consider the following two examples:
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
class App
{
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS);
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
box.add(label);
}
box.add(Box.createVerticalGlue());
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
This will properly display a vertical line of labels, beginning at the top and stretching as far towards the bottom as the labels take space. Good.
Modifying this, however, just a tiny bit:
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS);
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
box.add(Box.createVerticalGlue());
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
This will stretch all components of the Box to the same height, placing the labels far away from each other. Bad.
Overriding the JPanel's getPreferredSize and getMaximumSize methods (with getMinimumSize) has no effect and would be a bad way to fix it, because it relied on the components rather than the container and its layout.
Addendum:
Here is an already pretty successful attempt using GroupLayout. Unfortunately it did not seem to occur to the designer that among DEFAULT_SIZE and PREFERRED_SIZE a choice MINIMUM_SIZE would have been a good idea.
Furthermore if it is possible to invert the sequence of GroupLayout.SequentialGroup, the API is no help to figure out how. I for one certainly have no clue how to even extend that class.
import java.awt.Component;
import java.awt.ComponentOrientation;
import java.awt.Container;
import javax.swing.GroupLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
class LineLayout extends GroupLayout
{
public LineLayout(Container owner, int axis)
{
super(owner);
this.direction = axis;
this.direction |= owner.getComponentOrientation() != ComponentOrientation.LEFT_TO_RIGHT
? LineLayout.RIGHT_TO_LEFT : LineLayout.LEFT_TO_RIGHT;
this.setupGroups();
}
public LineLayout(Container owner, int axis, int orientation)
{
super(owner);
this.direction = axis;
this.direction |= orientation;
this.setupGroups();
}
#Override // to replicate FlowLayout functionality : this method is called from owner.add
public void addLayoutComponent(Component component, Object constraints)
{
if(constraints == null)
{
// REALLY surprised that this works, considering that overriding the JPanel's
// getMaximumSize method with getPreferredSize had no effect
this.horizontal.addComponent(component, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE);
this.vertical.addComponent (component, GroupLayout.DEFAULT_SIZE,
GroupLayout.DEFAULT_SIZE,
GroupLayout.PREFERRED_SIZE);
}
// TODO: else
}
protected void setupGroups()
{
super.setAutoCreateGaps(false); // does nothing
if((this.direction & LineLayout.AXIS) == LineLayout.Y_AXIS)
{
this.horizontal = super.createParallelGroup();
this.vertical = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
? this.createSequentialInvertedGroup() : super.createSequentialGroup();
}
else
{
this.horizontal = (this.direction & LineLayout.ORIENTATION) == LineLayout.RIGHT_TO_LEFT
? this.createSequentialInvertedGroup() : super.createSequentialGroup();
this.vertical = super.createParallelGroup();
}
super.setHorizontalGroup(this.horizontal);
super.setVerticalGroup (this.vertical);
}
// How!?
// protected LineLayout.SequentialInvertedGroup createSequentialInvertedGroup() { return new LineLayout.SequentialInvertedGroup(); }
protected GroupLayout.SequentialGroup createSequentialInvertedGroup() { return super.createSequentialGroup(); } // placeholder
protected int direction;
protected GroupLayout.Group horizontal;
protected GroupLayout.Group vertical;
// not sure how reliable the constant field values of BoxLayout are, whether it's smart to assume them unchanging over the ages
public static final int AXIS = 0b1;
public static final int X_AXIS = 0b0; // = BoxLayout.X_AXIS;
public static final int Y_AXIS = 0b1; // = BoxLayout.Y_AXIS;
public static final int ORIENTATION = 0b10;
public static final int LEFT_TO_RIGHT = 0b00; // also top to bottom
public static final int RIGHT_TO_LEFT = 0b10; // also bottom to top
// No idea how; only has "add" methods; cannot actually do anything with the added components!?
//protected static class SequentialInvertedGroup extends GroupLayout.SequentialGroup
//{}
}
class Applikation
{
public static void main(String[] args)
{
JFrame window = new JFrame();
JPanel box = new JPanel();
box.setLayout(new LineLayout(box, LineLayout.Y_AXIS));
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
If you try this out, you will note that there are still notable border spaces between the "XX" labels, taking up about 2/3 of an extra label per gap. While already much better than in the BoxLayout example, I do not think there is a good way to improve this spacing further.
private static int MAX_HEIGHT = 40;
private static final Dimension DIMENSION = new Dimension(Integer.MAX_VALUE, MAX_HEIGHT);
public static void main(String[] args)
{
JFrame window = new JFrame();
Box box = new Box(BoxLayout.Y_AXIS){
private static final long serialVersionUID = 1L;
#Override
public Component add(Component comp) {
comp.setMaximumSize(DIMENSION);
return super.add(comp);
}
};
for(int i = 0; i < 5; ++i)
{
JLabel label = new JLabel("XX");
JPanel panel = new JPanel();
panel.add(label);
box.add(panel);
}
window.add(box);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.pack();
window.setVisible(true);
}
You are using a Box for adding your components into. And the Documentation says:
a Box can use only a BoxLayout.
Now lets look into the Documentation for BoxLayout. It says:
BoxLayout pays attention to a component's requested minimum, preferred, and maximum sizes.
Now we have found the reason for the different outputs of your two examples. In your first example you are adding JLabels directly to your Box. Since they have a default maximumSize depending on their content they are not scaled by the Box.
In your second example you are adding JPanels to the Box that have your JLabels in it. A JPanel does not have a default maximumSize and so it is scaled by the Box.
So if you want to get the same output with JPanels as without you need your JPanels to have a maximumSize depending on their content means the JLabels.
So you could set a maximumSize manually. Something like that:
panel.setMaximumSize(new Dimension(100,20));
Or you use a different LayoutManager with your JPanels. One that calculates its size depending on its components. One that pays attention to a component's requested minimum, preferred, and maximum sizes.
Does this sound familiar to you? Right its from the Documentation of BoxLayout. So try to use a BoxLayout on your JPanels and you will get exactly the same result as your first example.
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
Related
I am trying to hide a JSplitPane with animation. By hide, I mean to setDividerLocation(0) so its left component is invisible (technically it is visible, but with zero width):
public class SplitPaneTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
// rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
timer.stop();
});
timer.start();
}
}
If you run it, will see that everything seems good, and the animation runs smooth.
However, in the real application the right of the JSplitPane is a JPanel with CardLayout and each card has a lot of components.
If you uncomment this line in order to simulate the number of components:
// rightPanel.add(new JLabel("s"));
and re-run the above example, you will see that the animation no longer runs smoothly. So, the question is, is is possible to make it smooth(-ier)?
I have no idea how to approach a solution - if any exists.
Based on my research, I registered a global ComponentListener:
Toolkit.getDefaultToolkit()
.addAWTEventListener(System.out::println, AWTEvent.COMPONENT_EVENT_MASK);
and saw the tons of events that are being fired. So, I think the source of the problem is the tons of component events that are being fired for each component. Also, it seems that components with custom renderers (like JList - ListCellRenderer and JTable - TableCellRenderer), component events are firing for all of the renderers. For example, if a JList has 30 elements, 30 events (component) will be fired only for it. It also seems (and that's why I mentioned it) that for CardLayout, events are taking place for the "invisible" components as well.
I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.
I know that 60*60 might sound crazy to you, but in a real application (mine has ~1500) as it makes sense, the painting is heavier.
The layout manager is invoked every time the divider location is changed which would add a lot of overhead.
One solution might be to stop invoking the layout manager as the divider is animating. This can be done by overriding the doLayout() method of the right panel:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SplitPaneTest2 {
public static boolean doLayout = true;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60))
{
#Override
public void doLayout()
{
if (SplitPaneTest2.doLayout)
super.doLayout();
}
};
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
SplitPaneTest2.doLayout = false;
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
SplitPaneTest2.doLayout = true;
splitPane.getRightComponent().revalidate();
}
});
timer.start();
}
}
Edit:
I was not going to include my test on swapping out the panel full of components with a panel that uses an image of components since I fell the animation is the same, but since it was suggested by someone else here is my attempt for your evaluation:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.awt.image.*;
public class SplitPaneTest2 {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
JPanel rightPanel = new JPanel(new GridLayout(60, 60));
for (int i = 0; i < 60 * 60; i++) {
rightPanel.add(new JLabel("s"));
}
rightPanel.setBorder(BorderFactory.createLineBorder(Color.red));
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, rightPanel);
frame.add(splitPane);
JButton button = new JButton("Press me to hide");
button.addActionListener(e -> hideWithAnimation(splitPane));
leftPanel.add(button, BorderLayout.PAGE_START);
frame.setMaximumSize(new Dimension(800, 800));
frame.setSize(800, 800);
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
private static void hideWithAnimation(JSplitPane splitPane) {
Component right = splitPane.getRightComponent();
Dimension size = right.getSize();
BufferedImage bi = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = bi.createGraphics();
right.paint( g );
g.dispose();
JLabel label = new JLabel( new ImageIcon( bi ) );
label.setHorizontalAlignment(JLabel.LEFT);
splitPane.setRightComponent( label );
splitPane.setDividerLocation( splitPane.getDividerLocation() );
final Timer timer = new Timer(10, null);
timer.addActionListener(e -> {
splitPane.setDividerLocation(Math.max(0, splitPane.getDividerLocation() - 3));
if (splitPane.getDividerLocation() == 0)
{
timer.stop();
splitPane.setRightComponent( right );
}
});
timer.start();
}
}
#GeorgeZ. I think the concept presented by #camickr has to do with when you actually do the layout. As an alternative to overriding doLayout, I would suggest subclassing the GridLayout to only lay out the components at the end of the animation (without overriding doLayout). But this is the same concept as camickr's.
Although if the contents of your components in the right panel (ie the text of the labels) remain unchanged during the animation of the divider, you can also create an Image of the right panel when the user clicks the button and display that instead of the actual panel. This solution, I would imagine, involves:
A CardLayout for the right panel. One card has the actual rightPanel contents (ie the JLabels). The second card has only one JLabel which will be loaded with the Image (as an ImageIcon) of the first card.
As far as I know, by looking at the CardLayout's implementation, the bounds of all the child components of the Container are set during layoutContainer method. That would probably mean that the labels would be layed out inspite being invisible while the second card would be shown. So you should probably combine this with the subclassed GridLayout to lay out only at the end of the animation.
To draw the Image of the first card, one should first create a BufferedImage, then createGraphics on it, then call rightPanel.paint on the created Graphics2D object and finally dispose the Graphics2D object after that.
Create the second card such that the JLabel would be centered in it. To do this, you just have to provide the second card with a GridBagLayout and add only one Component in it (the JLabel) which should be the only. GridBagLayout always centers the contents.
Let me know if such a solution could be useful for you. It might not be useful because you could maybe want to actually see the labels change their lay out profile while the animation is in progress, or you may even want the user to be able to interact with the Components of the rightPanel while the animation is in progress. In both cases, taking a picture of the rightPanel and displaying it instead of the real labels while the animation takes place, should not suffice. So it really depends, in this case, on how dynamic will be the content of the rightPanel. Please let me know in the comments.
If the contents are always the same for every program run, then you could probably pre-create that Image and store it. Or even, a multitude of Images and store them and just display them one after another when the animation turns on.
Similarly, if the contents are not always the same for every program run, then you could also subclass GridLayout and precalculate the bounds of each component at startup. Then that would make GridLayout a bit faster in laying out the components (it would be like encoding a video with the location of each object), but as I am testing it, GridLayout is already fast: it just calculates about 10 variables at the start of laying out, and then imediately passes over to setting the bounds of each Component.
Edit 1:
And here is my attempt of my idea (with the Image):
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.IntBinaryOperator;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class SplitPaneTest {
//Just a Timer which plays the animation of the split pane's divider going from side to side...
public static class SplitPaneAnimationTimer extends Timer {
private final JSplitPane splitPane;
private int speed, newDivLoc;
private IntBinaryOperator directionf;
private Consumer<SplitPaneAnimationTimer> onFinish;
public SplitPaneAnimationTimer(final int delay, final JSplitPane splitPane) {
super(delay, null);
this.splitPane = Objects.requireNonNull(splitPane);
super.setRepeats(true);
super.setCoalesce(false);
super.addActionListener(e -> {
splitPane.setDividerLocation(directionf.applyAsInt(newDivLoc, splitPane.getDividerLocation() + speed));
if (newDivLoc == splitPane.getDividerLocation()) {
stop();
if (onFinish != null)
onFinish.accept(this);
}
});
speed = 0;
newDivLoc = 0;
directionf = null;
onFinish = null;
}
public int getSpeed() {
return speed;
}
public JSplitPane getSplitPane() {
return splitPane;
}
public void play(final int newDividerLocation, final int speed, final IntBinaryOperator directionf, final Consumer<SplitPaneAnimationTimer> onFinish) {
if (newDividerLocation != splitPane.getDividerLocation() && Math.signum(speed) != Math.signum(newDividerLocation - splitPane.getDividerLocation()))
throw new IllegalArgumentException("Speed needs to be in the direction towards the newDividerLocation (from the current position).");
this.directionf = Objects.requireNonNull(directionf);
newDivLoc = newDividerLocation;
this.speed = speed;
this.onFinish = onFinish;
restart();
}
}
//Just a GridLayout subclassed to only allow laying out the components only if it is enabled.
public static class ToggleGridLayout extends GridLayout {
private boolean enabled;
public ToggleGridLayout(final int rows, final int cols) {
super(rows, cols);
enabled = true;
}
#Override
public void layoutContainer(final Container parent) {
if (enabled)
super.layoutContainer(parent);
}
public void setEnabled(final boolean enabled) {
this.enabled = enabled;
}
}
//How to create a BufferedImage (instead of using the constructor):
private static BufferedImage createBufferedImage(final int width, final int height, final boolean transparent) {
final GraphicsEnvironment genv = GraphicsEnvironment.getLocalGraphicsEnvironment();
final GraphicsDevice gdev = genv.getDefaultScreenDevice();
final GraphicsConfiguration gcnf = gdev.getDefaultConfiguration();
return transparent
? gcnf.createCompatibleImage(width, height, Transparency.TRANSLUCENT)
: gcnf.createCompatibleImage(width, height);
}
//This is the right panel... It is composed by two cards: one for the labels and one for the image.
public static class RightPanel extends JPanel {
private static final String CARD_IMAGE = "IMAGE",
CARD_LABELS = "LABELS";
private final JPanel labels, imagePanel; //The two cards.
private final JLabel imageLabel; //The label in the second card.
private final int speed; //The speed to animate the motion of the divider.
private final SplitPaneAnimationTimer spat; //The Timer which animates the motion of the divider.
private String currentCard; //Which card are we currently showing?...
public RightPanel(final JSplitPane splitPane, final int delay, final int speed, final int rows, final int cols) {
super(new CardLayout());
super.setBorder(BorderFactory.createLineBorder(Color.red));
spat = new SplitPaneAnimationTimer(delay, splitPane);
this.speed = Math.abs(speed); //We only need a positive (absolute) value.
//Label and panel of second card:
imageLabel = new JLabel();
imageLabel.setHorizontalAlignment(JLabel.CENTER);
imageLabel.setVerticalAlignment(JLabel.CENTER);
imagePanel = new JPanel(new GridBagLayout());
imagePanel.add(imageLabel);
//First card:
labels = new JPanel(new ToggleGridLayout(rows, cols));
for (int i = 0; i < rows * cols; ++i)
labels.add(new JLabel("|"));
//Adding cards...
final CardLayout clay = (CardLayout) super.getLayout();
super.add(imagePanel, CARD_IMAGE);
super.add(labels, CARD_LABELS);
clay.show(this, currentCard = CARD_LABELS);
}
//Will flip the cards.
private void flip() {
final CardLayout clay = (CardLayout) getLayout();
final ToggleGridLayout labelsLayout = (ToggleGridLayout) labels.getLayout();
if (CARD_LABELS.equals(currentCard)) { //If we are showing the labels:
//Disable the laying out...
labelsLayout.setEnabled(false);
//Take a picture of the current panel state:
final BufferedImage pic = createBufferedImage(labels.getWidth(), labels.getHeight(), true);
final Graphics2D g2d = pic.createGraphics();
labels.paint(g2d);
g2d.dispose();
imageLabel.setIcon(new ImageIcon(pic));
imagePanel.revalidate();
imagePanel.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_IMAGE);
}
else { //Else if we are showing the image:
//Enable the laying out...
labelsLayout.setEnabled(true);
//Revalidate and repaint so as to utilize the laying out of the labels...
labels.revalidate();
labels.repaint();
//Flip the cards:
clay.show(this, currentCard = CARD_LABELS);
}
}
//Called when we need to animate fully left motion (ie until reaching left side):
public void goLeft() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
minDivLoc = splitPane.getMinimumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc > minDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(minDivLoc, -speed, Math::max, ignore -> flip()); //Start the animation to the left.
}
}
//Called when we need to animate fully right motion (ie until reaching right side):
public void goRight() {
final JSplitPane splitPane = spat.getSplitPane();
final int currDivLoc = splitPane.getDividerLocation(),
maxDivLoc = splitPane.getMaximumDividerLocation();
if (CARD_LABELS.equals(currentCard) && currDivLoc < maxDivLoc) { //If the animation is stopped:
flip(); //Show the image label.
spat.play(maxDivLoc, speed, Math::min, ignore -> flip()); //Start the animation to the right.
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.setBorder(BorderFactory.createLineBorder(Color.green));
int rows, cols;
rows = cols = 60;
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
final RightPanel rightPanel = new RightPanel(splitPane, 10, 3, rows, cols);
splitPane.setLeftComponent(leftPanel);
splitPane.setRightComponent(rightPanel);
JButton left = new JButton("Go left"),
right = new JButton("Go right");
left.addActionListener(e -> rightPanel.goLeft());
right.addActionListener(e -> rightPanel.goRight());
final JPanel buttons = new JPanel(new GridLayout(1, 0));
buttons.add(left);
buttons.add(right);
frame.add(splitPane, BorderLayout.CENTER);
frame.add(buttons, BorderLayout.PAGE_START);
frame.setSize(1000, 800);
frame.setMaximumSize(frame.getSize());
frame.setLocationByPlatform(true);
frame.setVisible(true);
splitPane.setDividerLocation(0.5);
});
}
}
I created a website that acts as a software.
Now i'm trying to do the exact same thing with Java.
is there a way to draw some objects like "div" in html, that i can change x and y position (absolute), background-image, background-color, and put other object into it, [...] with Java ?
I tried this code :
import javax.swing.*;
import javax.swing.border.*;
import javax.accessibility.*;
import java.awt.*;
import java.awt.event.*;
/*
* LayeredPaneDemo.java requires
* images/dukeWaveRed.gif.
*/
public class Demo extends JPanel implements ActionListener, MouseMotionListener{
private String[] layerStrings = { "Yellow (0)", "Magenta (1)",
"Cyan (2)", "Red (3)",
"Green (4)" };
private Color[] layerColors = { Color.yellow, Color.magenta,
Color.cyan, Color.red,
Color.green };
private JLayeredPane layeredPane;
private JLabel dukeLabel;
private JCheckBox onTop;
private JComboBox layerList;
//Action commands
private static String ON_TOP_COMMAND = "ontop";
private static String LAYER_COMMAND = "layer";
//Adjustments to put Duke's toe at the cursor's tip.
private static final int XFUDGE = 40;
private static final int YFUDGE = 57;
public Demo() {
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
//Create and load the duke icon.
final ImageIcon icon = createImageIcon("images/dukeWaveRed.gif");
//Create and set up the layered pane.
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(300, 310));
layeredPane.setBorder(BorderFactory.createTitledBorder(
"Move the Mouse to Move Duke"));
layeredPane.addMouseMotionListener(this);
//This is the origin of the first label added.
Point origin = new Point(10, 20);
//This is the offset for computing the origin for the next label.
int offset = 35;
//Add several overlapping, colored labels to the layered pane
//using absolute positioning/sizing.
for (int i = 0; i < layerStrings.length; i++) {
JLabel label = createColoredLabel(layerStrings[i], layerColors[i], origin);
layeredPane.add(label, new Integer(i));
origin.x += offset;
origin.y += offset;
}
//Create and add the Duke label to the layered pane.
dukeLabel = new JLabel(icon);
if (icon != null) {
dukeLabel.setBounds(15, 225, icon.getIconWidth(), icon.getIconHeight());
} else {
System.err.println("Duke icon not found; using black square instead.");
dukeLabel.setBounds(15, 225, 30, 30);
dukeLabel.setOpaque(true);
dukeLabel.setBackground(Color.BLACK);
}
layeredPane.add(dukeLabel, new Integer(2), 0);
//Add control pane and layered pane to this JPanel.
add(Box.createRigidArea(new Dimension(0, 10)));
add(createControlPanel());
add(Box.createRigidArea(new Dimension(0, 10)));
add(layeredPane);
}
/** Returns an ImageIcon, or null if the path was invalid. */
protected static ImageIcon createImageIcon(String path) {
java.net.URL imgURL = Demo.class.getResource(path);
if (imgURL != null) {
return new ImageIcon(imgURL);
} else {
System.err.println("Couldn't find file: " + path);
return null;
}
}
//Create and set up a colored label.
private JLabel createColoredLabel(String text, Color color, Point origin) {
JLabel label = new JLabel(text);
label.setVerticalAlignment(JLabel.TOP);
label.setHorizontalAlignment(JLabel.CENTER);
label.setOpaque(true);
label.setBackground(color);
label.setForeground(Color.black);
label.setBorder(BorderFactory.createLineBorder(Color.black));
label.setBounds(origin.x, origin.y, 140, 140);
return label;
}
//Create the control pane for the top of the frame.
private JPanel createControlPanel() {
onTop = new JCheckBox("Top Position in Layer");
onTop.setSelected(true);
onTop.setActionCommand(ON_TOP_COMMAND);
onTop.addActionListener(this);
layerList = new JComboBox(layerStrings);
layerList.setSelectedIndex(2); //cyan layer
layerList.setActionCommand(LAYER_COMMAND);
layerList.addActionListener(this);
JPanel controls = new JPanel();
controls.add(layerList);
controls.add(onTop);
controls.setBorder(BorderFactory.createTitledBorder("Choose Duke's Layer and Position"));
return controls;
}
//Make Duke follow the cursor.
public void mouseMoved(MouseEvent e) {
dukeLabel.setLocation(e.getX()-XFUDGE, e.getY()-YFUDGE);
}
public void mouseDragged(MouseEvent e) {} //do nothing
//Handle user interaction with the check box and combo box.
public void actionPerformed(ActionEvent e) {
String cmd = e.getActionCommand();
if (ON_TOP_COMMAND.equals(cmd)) {
if (onTop.isSelected())
layeredPane.moveToFront(dukeLabel);
else
layeredPane.moveToBack(dukeLabel);
}
else if (LAYER_COMMAND.equals(cmd)) {
int position = onTop.isSelected() ? 0 : 1;
layeredPane.setLayer(dukeLabel,layerList.getSelectedIndex(),position);
}
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("LayeredPaneDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new Demo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
This is the result :
It looks to run, but I can not put JLabel into JLabel (will have i to create my own object ? with a JLabel and with sons ?
And finally when i put an addMouseListener to a JLabel , if an other JLabel is above it, i can click it through the other :-/
Use JavaFX with Java 8.
It is a bit slower in development until learned. Due to lambdas and component complexity.
The styling can be done with CSS.
There are animated effects.
Since the question targets Swing, I'll add a Swing-oriented answer anyway for the people who find this question and really must use Swing for any reason.
The trick is to use a fitting look & feel to do it for you in Swing.
Swing tutorial documentation on the subject.
That alone doesn't answer the question as this is about make it pretty and the default look & feel selections are not exactly all that great to look at / didn't stand the test of time. But there are also third party look & feels available for download, you're not necessarily limited to what is bundled with the runtime by default.
As an example, one can use Insubstantial (formerly known as Substance) to pretty up Swing applications. It is too naive to think that you can just plop this look & feel into an existing program and make it look good in an instant, but when you design your application with one of these look & feels from the beginning, the application can look very slick indeed.
But one should really use Swing only when it is a must. Swing is an aging technology and whatever extensions were available for download for it in its glory days are slowly disappearing from the internet / no longer maintained. On top of that it is basically the difference between choosing a pre-designed look & feel or having flexibility to do styling yourself, which JavaFX allows you to do with quite some flexibility.
I almost have the repaint() Jcomponent working. I had it working and then tried to make an absolute positioning and now it doesn't work.
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
public class DDHGenericFrame {
private static final long serialVersionUID = 1L;
DDHGenericPanel d = new DDHGenericPanel();
//DDHCircleOne o = new DDHCircleOne();
public DDHGenericFrame() {
initUI();
}
public final void initUI() {
JFrame frame = new JFrame("AbsoluteLayoutDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Draw Circle");
frame.setBackground(Color.green);
frame.setLayout(null);
frame.setSize(300,425);
frame.add(d);
// frame.add(o);//didn't work neither
frame.setVisible(true);
}
public static void main(String[] args) {
DDHGenericFrame ex = new DDHGenericFrame();
}
}
Class 2: (This is the JComponent that I am trying to set)
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JComponent;
public class DDHTestDraw extends JComponent {
public DDHTestDraw () {
settPreferredSize();
}
public void settPreferredSize() {
Dimension d = new Dimension(25,25);
setPreferredSize(d);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Text",20,20);
}
}
I added my component to the Container and then added the container to the JPanel and then the JPanel is added to the JFrame. I would think that this should work. I have a set preferred size. I had it working once and now it doesn't work.
I want to be able to make a component that is a circle. I want that circle to be able to be drawn any where on a Jframe, and then I want that circle to be able to move based on a a certain length of time. I am going to make a game that has circles dropping from the top and then falling to the bottom. When I had it working I did have the circle being written to the JPanel which is a complex piece of code. But I have had to go back to the old simple method of writing a single graphical word.
When you use null layout, you are completely responsible for making sure that components added have proper location and size (not preferredSize) set.
You should almost never use null layout.
Wouldn't this sort of thing work better by creating a logical class to represent the Circle, not a component? Then your drawing JPanel could hold a collection of logical circles, and the drawing JPanel could be responsible for drawing each Circle in its paintComponent method.
Edit
Your comments/my replies:
when you say never use an absolute layout, the company that I worked for always used a absolute layout only.
There are times when it is useful, but not for creating a typical component-filled GUI. Otherwise the GUI becomes very hard to modify and maintain.
When you mean a logical class you mean a class that just creates one circle.
Yes, and that holds all the necessary properties of that circle such as its Color, location, movement, etc..
Then the Jpanel would draw each circle.
Yes. I would imagine the drawing JPanel having an ArrayList<MyCircle> of them, and the paintComponent method iterating througgh this List.
when you say Size this is a property in JComponent.
I think that it is a property of Component, JComponent's parent. If you use null layout, then all components must have their size and location specified. Otherwise the component defaults to a location of [0, 0] and a size of [0, 0].
Edit 2
public Dimension Size(int a, int b) {
Dimension d = new Dimension();
d.width = a;
d.height = b;
return d;
}
This is the code that I used for the preferred size. I am at a lost why this doesn't work.
This code has no effect on either the size or the preferredSize properties of Component/JComponent. It doesn't surprise me that it will not help you. You would either have to override getSize() or getPreferredSize() or explicitly call setSize(...) or getPreferredSize(...) to change the state of the properties.
I am going to try it with a different layout manager and see but I would see the difference between one layout manager or another.
I'm not sure how to interpret this.
Edit 3
You state:
I worked at one company and we used absulute layouts all of the time. How would an absolute layout not work as good as, say BorderLayout(). To me the BorderLayout() are harder to implement. Or is it that you use a Jframe() with a BorderLayout, and then insert a Jpanel into an already existing position that is already also a BorderLayout(). I always have trouble getting my buttions and positions correct in a layout that is something different than a BorderLayout(). Can you post an example that would be easier to use than
I'm guessing you want an example of where use of layout managers is easier than using absolute positioning.
Let's take the example of a very simple calculator, one with buttons for numeric input and simple operations. Again this example is very basic, and is non-functional, but serves to illustrate the use of layouts. I could easily place my buttons in a GridLayout-using JPanel, and then place that button JPanel into a BorderLayout-using JPanel at the BorderLayout.CENTER position with the JTextField, display, placed in the same BorderLayout-using JPanel at the BorderLayout.PAGE_START position:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.*;
public class CalcEg {
private static final float BTN_FONT_SIZE = 20f;
private static final String[][] BTN_LABELS = {
{"7", "8", "9", "-"},
{"4", "5", "6", "+"},
{"1", "2", "3", "/"},
{"0", ".", " ", "="}
};
private static final int GAP = 4;
private JPanel mainPanel = new JPanel(new BorderLayout(GAP, GAP));
private JPanel buttonPanel = new JPanel();
private JTextField display = new JTextField();
public CalcEg() {
int rows = BTN_LABELS.length;
int cols = BTN_LABELS[0].length;
buttonPanel.setLayout(new GridLayout(rows, cols, GAP, GAP));
for (String[] btnLabelRow : BTN_LABELS) {
for (String btnLabel : btnLabelRow) {
if (btnLabel.trim().isEmpty()) {
buttonPanel.add(new JLabel());
} else {
JButton btn = createButton(btnLabel);
buttonPanel.add(btn);
}
}
}
display.setFont(display.getFont().deriveFont(BTN_FONT_SIZE));
display.setEditable(false);
display.setFocusable(false);
display.setBackground(Color.white);
mainPanel.setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
mainPanel.add(buttonPanel, BorderLayout.CENTER);
mainPanel.add(display, BorderLayout.PAGE_START);
}
private JButton createButton(String btnLabel) {
JButton button = new JButton(btnLabel);
button.setFont(button.getFont().deriveFont(BTN_FONT_SIZE));
return button;
}
public JComponent getMainComponent() {
return mainPanel;
}
private static void createAndShowGui() {
CalcEg mainPanel = new CalcEg();
JFrame frame = new JFrame("CalcEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
This would result in a calculator that looks like so:
Now, sure you could say that you could produce this with null layout and setbounds(...), and that's all well and good, but now say that you're not satisfied with this calculator, and now desire that it has some scientific calculation functionality. Say you now want to add buttons for square, square root, exponential, and logarithm, but not only that, say you wish to add the buttons below the display and above the numeric and basic operations buttons. If you were to do this with null layout, you would have to reposition all the components below and to the right of any new components added, and you'd have to expand the size of the JTextField, all calculations that are tedious and prone to error.
If you used layout managers, you would instead only need to add one line of code, actually an additional row to an array:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.*;
public class CalcEg {
private static final float BTN_FONT_SIZE = 20f;
private static final String[][] BTN_LABELS = {
{"sqr", "sqrt", "exp", "log"}, // ******* Line Added Here *********
{"7", "8", "9", "-"},
{"4", "5", "6", "+"},
{"1", "2", "3", "/"},
{"0", ".", " ", "="}
};
private static final int GAP = 4;
private JPanel mainPanel = new JPanel(new BorderLayout(GAP, GAP));
private JPanel buttonPanel = new JPanel();
private JTextField display = new JTextField();
public CalcEg() {
int rows = BTN_LABELS.length;
int cols = BTN_LABELS[0].length;
buttonPanel.setLayout(new GridLayout(rows, cols, GAP, GAP));
for (String[] btnLabelRow : BTN_LABELS) {
for (String btnLabel : btnLabelRow) {
if (btnLabel.trim().isEmpty()) {
buttonPanel.add(new JLabel());
} else {
JButton btn = createButton(btnLabel);
buttonPanel.add(btn);
}
}
}
display.setFont(display.getFont().deriveFont(BTN_FONT_SIZE));
display.setEditable(false);
display.setFocusable(false);
display.setBackground(Color.white);
mainPanel.setBorder(BorderFactory.createEmptyBorder(GAP, GAP, GAP, GAP));
mainPanel.add(buttonPanel, BorderLayout.CENTER);
mainPanel.add(display, BorderLayout.PAGE_START);
}
private JButton createButton(String btnLabel) {
JButton button = new JButton(btnLabel);
button.setFont(button.getFont().deriveFont(BTN_FONT_SIZE));
return button;
}
public JComponent getMainComponent() {
return mainPanel;
}
private static void createAndShowGui() {
CalcEg mainPanel = new CalcEg();
JFrame frame = new JFrame("CalcEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
which would result in this GUI:
Again this is a very simplistic example, but the general principles apply to any GUI that holds components such as JButtons, JTextComponents, etc.
With Swing animation, the Timer class is an exceptional option.
//this class is a JPanel that implements ActionListener`
Timer t = new Timer(1000,this);//the first arg is how many times it repeats in milliseconds
//Then in the constructor...
t.start();
//the ActionPerformed function normally increments a variable then calls the repaint method.
rectangle_x++;
repaint(); //calls paintComponent
Another good idea is to cast g to a Graphics2D object- it's a lot safer and more capable.
Another way to use the Timer class:
Timer t = new Timer(510, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
rectangle_x++;
repaint();
}
})
...
t.start();
You still need to override actionPerformed() in your main class though.
I'm having a problem with GridLayout and the entire JPanel not being filled. I have a N * M Grid, and I'm filling it with N * M Tiles (They extend JPanel). After adding all these tiles there is still space on the bottom and the right side of the JPanel. I thought GridLayout was supposed to fill up the entire JPanel, and make everything in it evenly sized?
Edit: I didn't want to post all the code since there are multiple classes. Thought maybe there was a simple solution.
public class MainFrame extends JFrame {
private static final long serialVersionUID = 3985493842977428355L;
private final int FRAME_HEIGHT = 800;
private final int FRAME_WIDTH = 700;
private MainPanel mainPanel;
public MainFrame() {
super("Test");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(FRAME_HEIGHT, FRAME_WIDTH);
setLocationRelativeTo(null);
mainPanel = new MainPanel();
getContentPane().add(mainPanel);
setVisible(true);
}
}
public class MainPanel extends JPanel {
/**
*
*/
private static final long serialVersionUID = 3421071253693531472L;
private RoutePanel routePanel;
private ControlPanel controlPanel;
private GridBagConstraints gridBagConstraints;
private GridBagLayout gridBagLayout;
public MainPanel() {
routePanel = new RoutePanel();
controlPanel = new ControlPanel();
setBackground(Color.black);
gridBagLayout = new GridBagLayout();
setLayout(gridBagLayout);
gridBagConstraints = new GridBagConstraints();
createLayout();
}
private void createLayout() {
gridBagConstraints.weightx = 1;
gridBagConstraints.weighty = 1;
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 0;
gridBagConstraints.anchor = GridBagConstraints.NORTH;
gridBagConstraints.fill = GridBagConstraints.BOTH;
gridBagLayout.setConstraints(routePanel, gridBagConstraints);
add(routePanel);
gridBagConstraints.weightx = 1;
gridBagConstraints.weighty = .05;
gridBagConstraints.gridx = 0;
gridBagConstraints.gridy = 1;
gridBagConstraints.anchor = GridBagConstraints.SOUTH;
gridBagConstraints.fill = GridBagConstraints.BOTH;
gridBagLayout.setConstraints(controlPanel, gridBagConstraints);
add(controlPanel);
}
}
public class RoutePanel extends JPanel{
/**
*
*/
private static final long serialVersionUID = 4366703088208967594L;
private final int GRID_ROWS = 30;
private final int GRID_COLS = 47;
public RoutePanel() {
setLayout(new GridLayout(GRID_ROWS, GRID_COLS, 0, 0));
for(int i = 0; i < GRID_ROWS * GRID_COLS; i++) {
add(new Tile());
}
setBackground(Color.orange);
}
}
public ControlPanel() {
}
public Tile() {
setBackground(Color.gray);
Border blackline;
blackline = BorderFactory.createLineBorder(Color.black);
setBorder(blackline);
}
Since you're not posting code, we are forced to guess (and this is not good).
My guess: you may be setting the size or the preferred sizes of some of your components or the GUI, and this is causing gaps at the edges of your GUI.
Solution: don't do this. Let the components size themselves via their preferred sizes and the layout managers when pack() is called on the GUI.
For more helpful help, create and post your sscce. For a question like this, code is really mandatory, and I'm a bit surprised actually that you didn't post yours.
Edit 1
For example, note the difference in the GUI's produced:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.*;
public class GapExample {
private static final int M = 5;
private static final int N = 6;
private static final int PREF_W = 700;
private static final int PREF_H = 500;
#SuppressWarnings("serial")
private static void createAndShowGui() {
// *** attempt 1: set preferredSize of the mainPanel JPanel ***
JPanel mainPanel = new JPanel(new GridLayout(M, N));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.red));
mainPanel.setPreferredSize(new Dimension(PREF_W, PREF_H));
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
String text = String.format("Foo [%d, %d]", i, j);
JLabel label = new JLabel(text, SwingConstants.CENTER);
label.setBorder(BorderFactory.createLineBorder(Color.blue));
mainPanel.add(label);
}
}
JOptionPane.showMessageDialog(null, mainPanel,
"Attempt 1, setPreferredSize of mainPane",
JOptionPane.PLAIN_MESSAGE);
// *** attempt 2: override the getPreferredSize of the JLabel cells in the
// grid ***
mainPanel = new JPanel(new GridLayout(M, N));
mainPanel.setBorder(BorderFactory.createLineBorder(Color.red));
final Dimension labelPrefSize = new Dimension(PREF_W / N, PREF_H / M);
for (int i = 0; i < M; i++) {
for (int j = 0; j < N; j++) {
String text = String.format("Foo [%d, %d]", i, j);
JLabel label = new JLabel(text, SwingConstants.CENTER) {
#Override
public Dimension getPreferredSize() {
return labelPrefSize;
}
};
label.setBorder(BorderFactory.createLineBorder(Color.blue));
mainPanel.add(label);
}
}
JOptionPane
.showMessageDialog(null, mainPanel,
"Attempt 2, override getPreferredSize of the JLabel cells in the grid",
JOptionPane.PLAIN_MESSAGE);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
In the second dialog, I have the JLabels return a preferred size and let the containers that hold them set the best size to display the GUI, and you'll with the second attempt, the grid fits much better.
This displays for the first (bad) attempt a gap between the inner and outer components:
And the second (good) attempt shows no such gap:
GridLayout was supposed to fill up the entire JPanel, and make
everything in it evenly sized
these two are opposing forces, the manager can do both at the same time only if the panel width is a multiple of the column width:
panel.width == column.width * column.count
If that's not possible, it favors the equal-size constraint, by sizing the children to the largest width to make them equal and positions the whole block in the center of parent, thus exposing the parent's background. Basically, you are seeing the effects of integer math :-)
As to the discussion in the other answer,
First off: my take on setXXSize is well known - it's always wrong to use in application code I think it's a design accident, should never have happened in the first place because most of the time it introduces more problems than it seems (superficially!) to solve.
Next-nearest solution seems to override getXXSize: if done to return an arbitrary hard-coded fixed size, it's only marginally better than calling setXXSize (in not propagating worst practices :): the return value should be related to internal state. It should keep internal calculation (if any) of super intact and can modify it. Plus strictly speaking, it should comply to super's contract which indeed states (a bit hidden in setXXSize) that the dimension applied via setXXSize takes precedence
Sets the preferred size of this component to a constant value.
Subsequent calls to getPreferredSize will always return this value.
In code:
// always wrong in application code
someComponent.setPreferredSize(labelPrefSize);
// suboptimal: arbitrary hard-coded fixed size
#Override
public Dimension getPreferredSize() {
return new Dimension(labelPrefSize);
}
// good: fixed value based on internal state
#Override
public Dimension getPreferredSize() {
return new Dimension(labelPrefSize);
}
// the size has some meaning f.i. when painting
#Override
protected void paintComponent(Graphics g) {
g.drawRect(0, 0, labelPrefSize.width, labelPrefSize.height);
}
// good: prefsize relative to super's calculation
#Override
public Dimension getPreferredSize() {
Dimension labelPrefSize = super.getPreferredSize();
int maxSide = Math.max(labelPrefSize.width, labelPrefSize.height);
labelPrefSize.setSize(maxSide, maxSide);
return labelPrefSize;
}
// better: prefsize relative to default calculation
// _and_ comply to super's contract
#Override
public Dimension getPreferredSize() {
Dimension labelPrefSize = super.getPreferredSize();
if (isPreferredSizeSet()) return labelPrefSize;
// any of the good thingies ...
}
Ideally, though, you would not touch the internal calculation of sizing hints at all. Instead, use a LayoutManager that allows to fine tune the layout to exactly suit your needs. Remember: the manager takes the hints and is free to size however she likes anyway :-)
Update
edited the above trying to emphasize the "related to internal state" thingy: sizing hints should return their own isolated, ego-centric requirement without caring about context. So overriding to return something that pleases an outside need is suboptimal.
I have only JTabbedPane inside JFrame. JTabbedPane sets its dimensions to biggest page width/height.
As pages has different size is it possible to force JTabbedPane to change its dimensions when selecting other page?
http://grab.by/3hIg
Top one is how it behave now and bottom one is how i want it to behave (i resized frame by hand)
This is fairly simple. It involves dynamic calculation of differences between your pages dimensions and the using them to force preferred size on you JTabbedPane. I did a quick experiment and it worked. So instead of putting a lot of text here - here is the code. It is not perfect but you should get an idea. Questions are welcome, of course.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
private static int maxW = 0;
private static int maxH = 0;
public static void main(String[] args) {
final JFrame f = new JFrame();
final JTabbedPane tabs = new JTabbedPane();
tabs.add( createPanel(Color.RED, 100, 100), "Red");
tabs.add( createPanel(Color.GREEN, 200, 200), "Green");
tabs.add( createPanel(Color.BLUE, 300, 300), "Blue");
final Dimension originalTabsDim = tabs.getPreferredSize();
tabs.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
Component p = ((JTabbedPane) e.getSource()).getSelectedComponent();
Dimension panelDim = p.getPreferredSize();
Dimension nd = new Dimension(
originalTabsDim.width - ( maxW - panelDim.width),
originalTabsDim.height - ( maxH - panelDim.height) );
tabs.setPreferredSize(nd);
f.pack();
}
});
f.setContentPane(tabs);
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static final JPanel createPanel( Color color, int w, int h ) {
JPanel p = new JPanel();
p.setBackground(color);
p.setPreferredSize( new Dimension(w, h));
maxW = Math.max(w, maxW);
maxH = Math.max(h, maxH);
return p;
}
}
I think another option is to dynamically change the panels of each tab when the tab is selected:
install a listener on JTabbedPane selection
install an empty panel on every tab but the selected tab by default (that contains the real panel for that tab)
in the selection listener:
remove the panel from the previously selected tab (ie, replace it with an empty panel)
change the empty panel by the real panel in the newly selected tab
call pack() on the window/dialog containing the JTabbedPane
Disclaimer: I haven't tested this approach but I believe it should work according to what you want.
Please also note that dynamically changing the size of the dialog based on the selected tab is not very user-friendly from a pure GUI viewpoint.
How about this?
tabbedPane.addChangeListener(new ChangeListener(){
#Override
public void stateChanged(ChangeEvent arg0) {
Component mCompo=tabbedPane.getSelectedComponent();
tabbedPane.setPreferredSize(mCompo.getPreferredSize());
BasicFrame.this.pack();
}
});