1

I have a JFrame where some elements (one, for now) have to be centered manually on the contentPane when resizing the window or changing the window state. Resizing event (componentResized) works fine but the windowStateChanged event is causing problems because it doesn't seem to "update" the contentPane's new size properly - it keeps the previous value. This can be seen by simply printing the result of getSize called on contentPane.

Centering is done by programmatically changing the constraint of the component (using putConstraint, SpringLayout). The issue is that getWidth used in that method returns "wrong" values which results in an uncentered component. Maybe another listener is needed here?

Additional info: Eclipse with WindowBuilder, Linux Mint 15

Any kind of advice is appreciated.

addWindowStateListener(new WindowStateListener()
{
    public void windowStateChanged(WindowEvent e)
    {
        System.out.println(contentPane.getSize()); // returns old size
        tfArrayPanelCenter();
    }
});

public void tfArrayPanelCenter()
{
    int padding = (contentPane.getWidth()
            - tfArrayPanel.getPreferredSize().width - HGAP) / 2;
    sl_contentPane.putConstraint(SpringLayout.WEST, tfArrayPanel, padding,
            SpringLayout.WEST, contentPane);
}

As requested I'm posting more code - a simple game of hangman. I think the JFrame constructor should be enough (other stuff is non-GUI):

/**
 * Create the frame.
 */
public MainWindow()
{
    addWindowStateListener(new WindowStateListener()
    {
        // no "@Override" was generated but it is the same with it
        public void windowStateChanged(WindowEvent e)
        {
            System.out.println("EVENT: " + contentPane.getSize() + ", "
                    + getExtendedState() + ", " + e.getOldState()); // amusingly, states are actually correct - interchanging between 0 (Frame.NORMAL) and 6 (Frame.MAXIMIZED_BOTH) when I maximize and "unmaximize"
            tfArrayPanelCenter();
        }
    });
    addComponentListener(new ComponentAdapter()
    {
        @Override
        public void componentResized(ComponentEvent e)
        {
            tfArrayPanelCenter();
        }
    });
    setTitle("Hangman");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setBounds(100, 100, 790, 620);
    setLocationRelativeTo(null);
    GameMechanics.setMainWindow(this);
    contentPane = new JPanel();
    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
    setContentPane(contentPane);
    sl_contentPane = new SpringLayout(); // I've declared this as a field, it is normally generated as a local variable
    contentPane.setLayout(sl_contentPane);

    newGame = new JButton("New Game");
    newGame.addMouseListener(new MouseAdapter()
    {
        @Override
        public void mouseClicked(MouseEvent e)
        {
            newGame.setVisible(false);
            lblGame.setVisible(true);
            lblGame.setBtnHintStatus(true);
            lblGame.setLblTriesLeftCountText(Integer
                    .toString(GameMechanics.TRIES));
            lblGame.paint(triesLeft);
            inputChars = new ArrayList<Character>();
            GameMechanics.loadWord();
            initControls(GameMechanics.getGuessingWord().length());
            lblTest.setText(GameMechanics.getGuessingWord().toUpperCase());
        }
    });
    newGame.setFont(new Font("Tempus Sans ITC", Font.BOLD, 12));
    sl_contentPane.putConstraint(SpringLayout.NORTH, newGame, 10,
            SpringLayout.NORTH, contentPane);
    sl_contentPane.putConstraint(SpringLayout.WEST, newGame, 10,
            SpringLayout.WEST, contentPane);
    newGame.setPreferredSize(new Dimension(100, 40));
    newGame.setFocusPainted(false);
    contentPane.add(newGame);

    tfArrayPanel = new JPanel();
    sl_contentPane.putConstraint(SpringLayout.SOUTH, tfArrayPanel, -10,
            SpringLayout.SOUTH, contentPane);
    sl_contentPane.putConstraint(SpringLayout.WEST, tfArrayPanel, 400,
            SpringLayout.WEST, contentPane);
    contentPane.add(tfArrayPanel);
    tfArrayPanel.setLayout(new FlowLayout(FlowLayout.CENTER, HGAP, VGAP));

    lblTest = new JLabel("New label");
    sl_contentPane.putConstraint(SpringLayout.NORTH, lblTest, 15,
            SpringLayout.NORTH, contentPane);
    sl_contentPane.putConstraint(SpringLayout.WEST, lblTest, 416,
            SpringLayout.WEST, contentPane);
    contentPane.add(lblTest);

    lblGame = new GameLabels(); // custom JPanel imported into the Palette and used from there on the MainWindow
    sl_contentPane.putConstraint(SpringLayout.SOUTH, lblGame, -60,
            SpringLayout.SOUTH, contentPane);
    sl_contentPane.putConstraint(SpringLayout.WEST, lblGame, 10,
            SpringLayout.WEST, contentPane);
    lblGame.setPreferredSize(new Dimension(315, 130));
    lblGame.setLayout(null);
    lblGame.setVisible(false);
    lblGame.setMainWindow(this);
    contentPane.add(lblGame);
}
Venom
  • 1,107
  • 4
  • 21
  • 46
  • 1
    You can easily achieve constant centering by just wrapping everything in a `GridBagLayout` – Paul Samsotha Feb 12 '14 at 02:44
  • 1
    You may to late in the chain of events, the layout manager may have already made the changes it wants to do the constraints. Personally, I'd just use a `GridBagLayout`... – MadProgrammer Feb 12 '14 at 02:45
  • could you post the full code? – JavaTechnical Feb 12 '14 at 06:29
  • @JavaTechnical I'll try to cut out the non-GUI part (wish that could be separated somehow). I'm sorry it took me this long, I went to bed straight after posting the question. – Venom Feb 12 '14 at 14:16
  • What are HGAP and VGAP values? – JavaTechnical Feb 13 '14 at 08:58
  • _where some elements (one, for now) have to be centered manually_ no, they don't **have to**, you **decided** to do it that way, which was the wrong decision: all sizing/locating is the exclusive task of a suitable LayoutManager. – kleopatra Feb 13 '14 at 10:25
  • @kleopatra You are absolutely right, I've managed to do the centering by setting the EAST anchor too (not just WEST and SOUTH). Works great now. Make that suggestion an answer so I can accept it, if you want of course. – Venom Feb 13 '14 at 16:30
  • you solved it yourself after reading my comment - you can answer (and accept) the question yourself :-) – kleopatra Feb 13 '14 at 17:03

3 Answers3

2

"Any kind of advice is appreciated."

Not sure if it fully fits your requirements, but if all you want to do is keep a set of components constantly centered (no matter size of the containing frame), as comments pointed out, you can just wrap everything in a GridBagLayout

enter image description here

import java.awt.GridBagLayout;
import javax.swing.*;
import javax.swing.border.TitledBorder;

public class CenteringWithGridBag {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable(){
            public void run() {
                JPanel gridBagPanel = new JPanel(new GridBagLayout());
                gridBagPanel.setBorder(new TitledBorder("JPanel with GridBagLayout"));

                JPanel innerPanel = new JPanel();
                innerPanel.setBorder(new TitledBorder("JPanel Wrap"));
                innerPanel.add(new JButton("Button"));
                gridBagPanel.add(innerPanel);

                JFrame frame = new JFrame("GridBagLayout Test");
                frame.add(gridBagPanel);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • Sorry I couldn't answer sooner but I went to sleep straight after posting the question to give you guys some time to anwer but you are really fast. Anyway, I'll try this solution once I've exhausted most of the possible solution that don't include changing the layout manager. – Venom Feb 12 '14 at 14:22
1

I don't think you need WindowStateListener for this because whenever you maximize or minimize or get the window in normal state the componentResized is called. Just remove the WindowStateListener and it works fine. You said componentResized is working for you, then you probably don't need WindowStateListener

addComponentListener(new ComponentAdapter()
    {
        @Override
        public void componentResized(ComponentEvent e)
        {
            tfArrayPanelCenter();
        }
    });
JavaTechnical
  • 8,846
  • 8
  • 61
  • 97
0

Upon reading kleopatra's suggestion I approached the problem in another way. All I've done was putting the EAST constraint too (there were only WEST and SOUTH before) on the JPanel that needed to be centered. JPanel's FlowLayout handles the actual centering of the elements located within it.

To be honest, this doesn't truly answer the question itself but it is a solution to my troubles regarding the centering of components. Good enough, I guess.

Venom
  • 1,107
  • 4
  • 21
  • 46