How to set a JFrame size to fit the CardLayout displayed JPanel?
Asked Answered
R

4

6

I have a JFrame containing a set of JPanels in a CardLayout. Each JPanel has a different size and I want the JFrame to adapt to the size of the currently displayed JPanel (not the JPanel to adapt to the size of the JFrame).

How can I achieve this?

Rea answered 26/11, 2011 at 10:35 Comment(6)
"I want the JFrame to adapt to the size of the currently displayed JPanel" It would feel odd to the user to have a frame change size just because the displayed content changes.Lindell
Having a fix size JFrame is pretty ugly when the inner panel does not match. I tried it.Bordie
I'm not sure this will work but you can try this: frame.setSize(panel.getPreferredSize());Minsk
"I tried it." Show me. I have a feeling it is more that the extra space of the panels is not being assigned in a 'visually pleasing' manner. Doesn't mean it is impossible to do.Lindell
@HarryJoy From vague memory the preferred size of the panel with CL is that of the largest pref. size of any component in it.Lindell
please show a SSCCE to explain exactly what you want to achieveBottoms
B
16

The general is: if you have a layout problem, always solve it with an appropriate LayoutManager. Never tweak a component's sizing hint to reach your goal.

In this case, it's particularly easy to adjust the CardLayout. By default, it calculates its prefSize to the max of prefSizes of all cards. Simply subclass and implement to return the prefSize (plus insets) of the currently visible card:

public static class MyCardLayout extends CardLayout {

    @Override
    public Dimension preferredLayoutSize(Container parent) {

        Component current = findCurrentComponent(parent);
        if (current != null) {
            Insets insets = parent.getInsets();
            Dimension pref = current.getPreferredSize();
            pref.width += insets.left + insets.right;
            pref.height += insets.top + insets.bottom;
            return pref;
        }
        return super.preferredLayoutSize(parent);
    }

    public Component findCurrentComponent(Container parent) {
        for (Component comp : parent.getComponents()) {
            if (comp.isVisible()) {
                return comp;
            }
        }
        return null;
    }

}

Using that (borrowing @mKorbel's example), the main method cleanly shrinks down:

private static void createAndShowUI() {
    final CardLayout cardLayout = new MyCardLayout();
    final JPanel cardHolder = new JPanel(cardLayout);
    final JFrame frame = new JFrame("MultiSizedPanels");
    JLabel[] labels = {
        new JLabel("Small Label", SwingConstants.CENTER),
        new JLabel("Medium Label", SwingConstants.CENTER),
        new JLabel("Large Label", SwingConstants.CENTER)};

    for (int i = 0; i < labels.length; i++) {
        int padding = 50 * (i + 1);
        Border lineBorder = BorderFactory.createCompoundBorder(
            BorderFactory.createLineBorder(Color.blue),
            BorderFactory.createEmptyBorder(padding, padding, padding, padding));
        labels[i].setBorder(lineBorder);
        JPanel containerPanel = new JPanel();
        containerPanel.add(labels[i]);
        cardHolder.add(containerPanel, String.valueOf(i));
    }
    JButton nextButton = new JButton("Next");
    nextButton.addActionListener(new ActionListener() {

        @Override
        public void actionPerformed(ActionEvent e) {
            cardLayout.next(cardHolder);
            frame.pack();
        }
    });
    JPanel btnHolder = new JPanel();
    btnHolder.add(nextButton);

    frame.add(cardHolder, BorderLayout.CENTER);
    frame.add(btnHolder, BorderLayout.SOUTH);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setLocation(150, 150);
    frame.setVisible(true);
}
Bottoms answered 27/11, 2011 at 15:40 Comment(1)
Thanks. That's more clean and exactly what I wanted.Bordie
K
4

Here is the SSCCE.

1) For continuous increase/decrease of size, you should look for some animation API.

or

2) If isn't there any background task with output to the GUI, you can to pause while sizing the JFrame. In that case, you would delay the increase/decrease by using Thread#sleep(int) wrapped into Runnable thread.

3) Notice (not for OP) using Thread#sleep(int) directly during EDT would be freeze GUI until the delay ended.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.Border;
//based on HFOE https://mcmap.net/q/1631196/-change-size-of-jpanel-using-cardlayout/5414770#5414770
public class MultiSizedPanels {

    private static void createAndShowUI() {
        final CardLayout cardLayout = new CardLayout();
        final JPanel cardHolder = new JPanel(cardLayout);
        final JFrame frame = new JFrame("MultiSizedPanels");
        JLabel[] labels = {
            new JLabel("Small Label", SwingConstants.CENTER),
            new JLabel("Medium Label", SwingConstants.CENTER),
            new JLabel("Large Label", SwingConstants.CENTER)};

        for (int i = 0; i < labels.length; i++) {
            int padding = 50;
            Dimension size = labels[i].getPreferredSize();
            size = new Dimension(size.width + 2 * (i + 1) * padding, size.height + 2 * (i + 1) * padding);
            labels[i].setPreferredSize(size);
            Border lineBorder = BorderFactory.createLineBorder(Color.blue);
            labels[i].setBorder(lineBorder);
            JPanel containerPanel = new JPanel();
            if (i == 1) {
                containerPanel.setPreferredSize(new Dimension(300, 200));
                containerPanel.add(labels[i], BorderLayout.CENTER);
                cardHolder.add(containerPanel, String.valueOf(i));
            } else if (i == 2) {
                containerPanel.setPreferredSize(new Dimension(600, 400));
                containerPanel.add(labels[i], BorderLayout.CENTER);
                cardHolder.add(containerPanel, String.valueOf(i));
            } else {
                containerPanel.setPreferredSize(new Dimension(800, 600));
                containerPanel.add(labels[i], BorderLayout.CENTER);
                cardHolder.add(containerPanel, String.valueOf(i));
            }
        }
        JButton nextButton = new JButton("Next");
        nextButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent e) {
                cardLayout.next(cardHolder);
                Dimension dim = new Dimension();
                for (Component comp : cardHolder.getComponents()) {
                    if (comp.isVisible() == true) {
                        dim = comp.getPreferredSize();
                    }
                }
                frame.setPreferredSize(dim);
                java.awt.EventQueue.invokeLater(new Runnable() {

                    public void run() {
                        frame.pack();
                    }
                });
            }
        });
        JPanel btnHolder = new JPanel();
        btnHolder.add(nextButton);

        frame.add(cardHolder, BorderLayout.CENTER);
        frame.add(btnHolder, BorderLayout.SOUTH);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocation(150, 150);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            public void run() {
                createAndShowUI();
            }
        });
    }
}
Kurman answered 26/11, 2011 at 16:51 Comment(1)
-1 for excessive use of setPrefSize: the general rule is (as you well know :-) to solver sizing problems by using an appropriate LayoutManager. If you don't find one that's doing what you want, write/adjust a LayoutManager that does ...Bottoms
R
2

I think you may have the wrong layout manager for what you want to do (leaving aside, for the moment, that I am not sure you want to do what you say you want to do).

I have not yet found documentation on what I suspect, which is that CardLayout resizes all of its children to the size of its container. I did find the following comment for its "layoutContainer" method:

         * Each component in the <code>parent</code> container is reshaped
         * to be the size of the container, minus space for surrounding
         * insets, horizontal gaps, and vertical gaps.

I think this is reasonable behavior, since it would be very annoying for a user to have the panel size jump around as panels are traversed.

If this is really the behavior you want, then I think you want a different layout manager. And since it is unusual behavior for a UI in general, you may well not find a standard one to do this particular 'feature', so you may have to write your own.

Robert answered 26/11, 2011 at 12:38 Comment(0)
F
0
Try this code this may help you.




protected void paintComponent(Graphics g) {
if (getModel().isArmed()) {
      g.setColor(Color.lightGray);
} else {
     g.setColor(getBackground());
}
g.fillOval(0, 0, getSize().width-1, getSize().height-1);
System.out.println("width is "+getSize().width+"height is "+getSize().height);
super.paintComponent(g);
}
protected void paintBorder(Graphics g) {
g.setColor(getForeground());
g.drawOval(0, 0, getSize().width-1, getSize().height-1);
}
Shape shape;
public boolean contains(int x, int y) {
if (shape == null || !shape.getBounds().equals(getBounds())) {
     shape = new Ellipse2D.Float(0, 0, getWidth(), getHeight());
}
return shape.contains(x, y);
}}
Fusiform answered 5/2, 2013 at 7:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.