3

I would like my jframe to change its size and contents depending on the x coordinate. Initially the jframe appears in the 'primary zone' (x <= 1400) witn a panel sized 500x500 added to the jframe's content pane.
Desired: When it is dragged and leaves the 'primary zone' and enters the 'secondary zone' everything is removed from the content pane, the panel gets wrapped into a jscrollpane sized 200x200, and the jscrollpane is added the content pane. When the jframe leaves the 'secondary zone' the jscrollpane is removed from the content pane and the panel is added back.
Actual: Results are not stable. When the jframe leaves the primary zone I can see some flipping. Scrollbars appear but and the frame changes its size but then immediately resized back to the previous size. Stopping at breakpoints inside runnables in the changeSizeAndContent invokeLater codeblocks (not a good practice actually) brings the desired result and so does a conditional breakpoint.
There is some Swing multithreading taking place which I do not understand. I can see the EDT calling EventQueue's dispatchEvent and the COMPONENT_RESIZED (new, correct size) events triggered by runnables in the changeSizeAndContent are followed by COMPONENT_MOVED (old, now incorrect size) events which reference the component with its old size.
Tried with Java 8 and 11.

package Jna;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

/* 0------------------1200-1400--------->
 *                      +---------------+
 *                      |   SECONDARY   |
 * +--------------------+---+           |                     
 * |    PRIMARY         | B | x: 1200-  |
 * |    x: 0-1400       | U |           |
 * |                    | F |           |
 * |                    | F |           |
 * |                    | E |           |
 * |                    | R |           |
 * |                    +---+-----------+
 * |                        |
 * +------------------------+
 */
public class FrameDemo3 {

    static final JPanel panel;
    static final JScrollPane jsp;
    
    static {
        panel = getInitializedPanel();
        jsp = getInitilalizedJScrollPane();
    }
    
    static boolean isPrimaryZone = true;
    static boolean isCurrentPrimaryZone = true;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("FrameDemo");

        frame.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentMoved(ComponentEvent e) {
                int x = frame.getX();

                if (x > 1400) {
                    isCurrentPrimaryZone = false;
                } else if (x < 1200) {
                    isCurrentPrimaryZone = true;
                }

                if (isPrimaryZone != isCurrentPrimaryZone) {
                    isPrimaryZone = isCurrentPrimaryZone;
                    changeSizeAndContent(frame);
                }
            }
        });
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(panel);
        frame.pack();
        frame.setVisible(true);
    }

    public static void changeSizeAndContent(JFrame frame) {
        if (isPrimaryZone) {
            SwingUtilities.invokeLater(() -> {
                frame.getContentPane().removeAll();
                frame.getContentPane().add(panel);
                frame.pack();
            });
        } else {
            SwingUtilities.invokeLater(() -> {
                frame.getContentPane().removeAll();
                frame.getContentPane().add(jsp);
                frame.pack();
            });            
        }
    }

    private static JPanel getInitializedPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBackground(Color.WHITE);

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                panel.add(new JLabel(getLabelText(i, j)), getConstraints(i, j));
            }
        }
        panel.setPreferredSize(new Dimension(500, 500));
        return panel;
    }
    
    private static JScrollPane getInitilalizedJScrollPane() {
        JScrollPane jsp = new JScrollPane(panel,
                JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
                JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
        jsp.setPreferredSize(new Dimension(200, 200));
        return jsp;
    }

    static GridBagConstraints getConstraints(int gridX, int gridY) {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = gridX;
        gbc.gridx = gridY;
        gbc.ipady = 40;
        gbc.ipadx = 40;
        return gbc;
    }

    static String getLabelText(int gridX, int gridY) {
        StringBuilder sb = new StringBuilder("EXAMPLE ")
                .append(gridX)
                .append(',')
                .append(gridY);
        return sb.toString();
    }

}

  • @user15358848 _wouldn't it be better to have it always added to the scroll pane and just change its scroll policies?_ Leaving only jscrollpane as the panel's parent and the only contents of the jframe did not fix it (resizing the frame) for me unfortunately. Still there are subsequent COMPONENT_MOVED events that reference the old jframe size. The user camickr has also faced it so this may not or may not be reproduced easily. – Andrey Radchenko Apr 02 '21 at 18:18

1 Answers1

0

As mentioned in the comments, a component can only have a single parent, so an easier solution might be to add the component to the scroll pane and change the scrollbar policy as required. The code below demonstrates this.

There is some Swing multithreading taking place which I do not understand?

The issues appears to be the title bar of the frame.

(Note I changed the resizing behaviour to work at 1000/800 instead of 1400, 1200)

When the frame is moved to location 1001 your logic is invoked and the frame is resized as expected.

However, the issue is that additional events on the frame will always be generated.

For example if you move the frame to 1001 and hold the mouse down the frame will remain at the correct size. However as soon as you release the mouse it goes back to the previous size.

Or if you move the frame to location 1002 or greater it goes back to the previous size.

In both of the above cases code in your application is not executed to change the frame size.

I have no idea how to fix this as this logic is controlled by the OS frame widget. This may explain why it works for user153... I use Windows 10 and I see the problems you describe.

In the code below I added a Swing Timer to reset the frame size to the expected size after a delay of 2 seconds. It is not a practical solution, but it does demonstrate that your code is working as expected, you just can't control the external behaviour of the frame.

import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

public class FrameDemo4 {

    static final JPanel panel;
    static final JScrollPane jsp;
    static Dimension preferredSize;

    static {
        panel = getInitializedPanel();
        jsp = getInitilalizedJScrollPane();
    }

    static boolean isPrimaryZone = true;
    static boolean isCurrentPrimaryZone = true;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGUI());
    }

    private static void createAndShowGUI() {
        JFrame frame = new JFrame("FrameDemo");

        frame.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentMoved(ComponentEvent e) {
                int x = frame.getX();
                System.out.println(x);

                if (x > 1000) {
                    isCurrentPrimaryZone = false;
                } else if (x < 800) {
                    isCurrentPrimaryZone = true;
                }

                if (isPrimaryZone != isCurrentPrimaryZone) {
                    isPrimaryZone = isCurrentPrimaryZone;
                    changeSizeAndContent(frame);
                }
            }
        });

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//        frame.getContentPane().add(panel);
        frame.getContentPane().add(jsp);
        frame.pack();
        frame.setLocation(1000, 0);
        frame.setVisible(true);
    }

    public static void changeSizeAndContent(JFrame frame) {
        System.out.println(isPrimaryZone);

        if (isPrimaryZone) {
                //frame.getContentPane().removeAll();
                //frame.getContentPane().add(panel);
                jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
                jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_NEVER);
                jsp.setPreferredSize( panel.getPreferredSize() );
                frame.pack();
                preferredSize = frame.getPreferredSize();
        } else {
                //frame.getContentPane().removeAll();
                //frame.getContentPane().add(jsp);
                jsp.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
                jsp.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
                jsp.setPreferredSize( new Dimension(200, 200));
                frame.pack();
                preferredSize = frame.getPreferredSize();
        }

        Timer timer = new Timer(2000, (e) -> frame.setSize( preferredSize.width, preferredSize.height ) );
        timer.start();
    }

    private static JPanel getInitializedPanel() {
        JPanel panel = new JPanel(new GridBagLayout());
        panel.setBackground(Color.WHITE);

        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < 2; j++) {
                panel.add(new JLabel(getLabelText(i, j)), getConstraints(i, j));
            }
        }
        panel.setPreferredSize(new Dimension(500, 500));
        return panel;
    }

    private static JScrollPane getInitilalizedJScrollPane() {
//        JScrollPane jsp = new JScrollPane(panel, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
//        jsp.setPreferredSize(new Dimension(200, 200));
        JScrollPane jsp = new JScrollPane(panel);
        jsp.setPreferredSize(panel.getPreferredSize());
        return jsp;
    }

    static GridBagConstraints getConstraints(int gridX, int gridY) {
        GridBagConstraints gbc = new GridBagConstraints();
        gbc.gridx = gridX;
        gbc.gridx = gridY;
        gbc.ipady = 40;
        gbc.ipadx = 40;
        return gbc;
    }

    static String getLabelText(int gridX, int gridY) {
        StringBuilder sb = new StringBuilder("EXAMPLE ")
                .append(gridX)
                .append(',')
                .append(gridY);
        return sb.toString();
    }

}

The only solution I can think of is to:

  1. Maybe use the Metal LAF. It uses an undecorated frame with a custom title bar
  2. Create your own title bar to use on an undecorated frame.
camickr
  • 321,443
  • 19
  • 166
  • 288
  • _It is not a practical solution, but it does demonstrate that your code is working as expected, you just can't control the external behaviour of the frame._ If nothing helps, I was thinking of intercepting additional events and updating the component size referenced by the event - before actually applying the said event. – Andrey Radchenko Apr 02 '21 at 17:46