Kujoen
Kujoen

Reputation: 173

Have a JScrollPanel resize around a static JPanel

I am making an editor for a game i am creating, and i have problems implementing a scroll-able level-overview. The overview is supposed to have this layout :

enter image description here

The Canvas is supposed to always have a size of 1280x720, so i want to use a JScrollPane to be able to still view the entire canvas when the JFrame gets smaller. Now to achieve this i used a GridBagLayout and set a preferred size on the canvas. The problem is that expanding the JFrame will eventually allow you to expand the canvas beyond its preferred size. To stop this i set the layout of the scrollpanes viewport to a FlowLayout to prevent it from resizing the view its holding. This seems to create two problems:

1 : The GridBagLayout no longer distributes the space correctly when expanded to full screeen.

Problem1

2 : When you make the ScrollPane smaller than its viewport view, the view gets cut off, also scrolling doesn't seem to work.

enter image description here

To Summarize my Questions :

Is there another/better way to implement this UI, and if not how can i solve the issues of the gridbaglayout not distributing space correctly when expanded to fullscreen and cutting off the viewports view ?

Here the code i used for the examples :

public class Main extends JFrame {

public Main() {
    this.setSize(800, 400);
    this.setMinimumSize(new Dimension(400, 400));
    this.setLocationRelativeTo(null);

    JPanel optionsPanel = new JPanel();
    optionsPanel.setBackground(Color.red);

    JPanel scrollContentPane = new JPanel();
    scrollContentPane.setPreferredSize(new Dimension(700,700));
    scrollContentPane.setBorder(BorderFactory.createLineBorder(Color.red));

    JScrollPane scrollPane = new JScrollPane();
    scrollPane.getViewport().setLayout(new FlowLayout());
    scrollPane.getViewport().setView(scrollContentPane);
    scrollPane.setHorizontalScrollBarPolicy(scrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
    scrollPane.setVerticalScrollBarPolicy(scrollPane.VERTICAL_SCROLLBAR_ALWAYS);


    JPanel contentPanel = new JPanel();
    contentPanel.setLayout(new GridBagLayout());

    GridBagConstraints gc = new GridBagConstraints();
    gc.fill = gc.BOTH;
    gc.weightx = 1.0;
    gc.weighty = 1.0;

    gc.gridx = 0;
    gc.gridy = 0;
    contentPanel.add(optionsPanel, gc);

    gc.gridx = 1;
    contentPanel.add(scrollPane, gc);

    this.setContentPane(contentPanel);
    this.setVisible(true);

}

public static void main(String[] args) {
    Main main = new Main();
}

}

Here is the actual code i am using in my programm :

    previewPanel = new PreviewPanel(this);
    controlPanel = new ControlPanel(this);

    centerPanel = new JPanel();
    centerPanel.setLayout(new GridBagLayout());

    GridBagConstraints c = new GridBagConstraints();
    c.fill = c.BOTH;
    c.weightx = 1;
    c.weighty = 1;

    // #HACK, fill grids with empty labels
    c.gridx = 0;
    c.gridy = 0;
    centerPanel.add(new JLabel(), c);

    c.gridx = 1;
    centerPanel.add(new JLabel(), c);

    c.gridx = 2;
    centerPanel.add(new JLabel(), c);

    c.gridx = 3;
    centerPanel.add(new JLabel(), c);

    c.gridx = 0;
    c.gridy = 0;
    c.gridwidth = 1;

    centerPanel.add(controlPanel, c);

    c.gridx = 1;
    c.gridy = 0;
    c.gridwidth = 3;

    centerPanel.add(previewPanel, c);


    setLayout(new BorderLayout());
    add(centerPanel, BorderLayout.CENTER);
    add(menuBar, BorderLayout.NORTH);
}

Upvotes: 0

Views: 129

Answers (2)

MatheM
MatheM

Reputation: 811

Border cut off; this is caused because the JScrollPane doesn't recognize the border as a part of the component. So when you scroll all the way to the edge you will see the first non border pixel of your component. To solve this instead of setting layout on the viewport create another panel add the canvas to this new panel and set this new panel as the viewport in your scroll pane.

JPanel scrollContentPane = new JPanel();
// to set the size properly see later
JPanel scrollContentPaneViewport = new JPanel(new FlowLayout());
scrollContentPaneViewport.add(scrollContentPane);
JScrollPane scrollPane = new JScrollPane();
scrollPane.getViewport().setView(scrollContentPaneViewport);

To restrict the size of your canvas (scrollContentPane) you shouldn't set sizing hints set[minimum|preferred|maximum]size (reason is here) instead you should subclass the panel and override the get[minimum|preferred|maximum]size methods, override the ones you need but if you want to make the panel always fixed size you should override all three.

JPanel scrollContentPane = new JPanel() {
    // this is important never return this dimension always make a copy
    private final Dimension d = new Dimension(700, 700);

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(d);
    }
    @Override
    public Dimension getMinimumSize() {
        return new Dimension(d);
    }
    @Override
    public Dimension getMaximumSize() {
        return new Dimension(d);
    }
};

To make sure that the optionsPanel and the scrollPane are always equally divided use GridLayout instead of GridBagLayout.

Edit: GridBagLayout component relative distribution

You should provide the layout with information on what to do with the extra space. This is done by setting the rowWeights and columnWeights fields.

JPanel contentPanel = new JPanel();
GridBagLayout gbl_contentPanel = new GridBagLayout();
// This tells the layout that first row should take all available space (there is only one row)
gbl_contentPanel.rowWeights = new double[]{1.0};
// This tells the layout that second column should be 3 times bigger than first column (3 is 3 times bigger than 1)
gbl_contentPanel.columnWeights = new double[]{1.0, 3.0};
contentPanel.setLayout(gbl_contentPanel);

This should tell the GridBagLayout how to distribute extra space but it still won't work.

It is because GridBagLayout is one of the layouts that respects preferred size returned from components. The two components you added are "telling" the layout their minimum maximum and preferred size and layout takes it into account when calculating how much space the components should get.

To solve that you should override getPreferredSize() in both components to return equal dimensions. (I like to return 0, 0)

JPanel optionsPanel = new JPanel() {
    @Override
    public Dimension getPreferredSize() {
        return new Dimension();
    }
};

Upvotes: 2

camickr
camickr

Reputation: 324098

Don't play with the layout manager of the viewport

If you want to control the size of the panel added to the viewport, then add your panel to a wrapper panel instead. Something like:

JScrollPane scrollPane = new JScrollPane();
//scrollPane.getViewport().setLayout(new FlowLayout());
//scrollPane.getViewport().setView(scrollContentPane);
JPanel wrapper = new JPanel();
wrapper.add( scrollContentPane );
scrollPane.getViewport().setView(wrapper);

Upvotes: 2

Related Questions