Java Swing API - GroupLayout неправильно изменяет размер всех JPanels компонентов

Я только вчера начал использовать Java Swing, так что простите меня, если это не правильный вопрос. Я пытался создать простой пользовательский интерфейс с сеткой и панелью, которая позволяет пользователю указывать размер размеров сетки.

У меня есть два класса, расширяющих JPanel: GridSizePanel и GridBoxPanel. GridSizePanel определяет заголовок, границу, метки и поля, участвующие в разработке панели, что позволяет пользователю изменять размеры. GridBoxPanel отображает фактическую сетку (взято из здесь). GridSizePanel использует GroupLayout в качестве LayoutManager, а GridBoxPanel использует GridBagLayout. Родительский класс JFrame (MazeSolverInterface), который управляет этими субпанелями, использует GroupLayout в качестве LayoutManager.

Проблема в том, что если я добавляю только GridSizePanel в GroupLayout MazeSolverInterface, когда я изменяю размер окна вручную, я вижу, что GridSizePanel изменение размера происходит автоматически. Все хорошо.

Но когда я добавляю GridBoxPanel к MazeSolverInterface, теперь, когда я вручную изменяю размер окна, кажется, что изменяется только GridBoxPanel. GridSizePanel вообще не меняет размер!

Вот мой код:

GridSizePanel:

public class GridSizePanel extends JPanel implements PropertyChangeListener {


    public GridSizePanel() throws ParseException {

        // set the border properties
        TitledBorder title = BorderFactory.createTitledBorder("Grid Size");
        title.setTitleColor(Color.BLACK);
        title.setBorder(BorderFactory.createBevelBorder(BevelBorder.LOWERED,
                Color.DARK_GRAY, Color.GRAY));
        this.setBorder(title);

        // wire up the group layout and panel to
        // each other
        GroupLayout gl = new GroupLayout(this);
        this.setLayout(gl);

        // Turn on automatically adding gaps between components
        gl.setAutoCreateGaps(true);

        // Turn on automatically creating gaps between components that touch
        // the edge of the container and the container.
        gl.setAutoCreateContainerGaps(true);

        JLabel numRowsLabel = new JLabel("rows");
        JLabel numColsLabel = new JLabel("columns");

        MaskFormatter textMask = new MaskFormatter("##");
        textMask.setPlaceholder("16");
        JFormattedTextField rowsText = new JFormattedTextField(textMask);
        JFormattedTextField colsText = new JFormattedTextField(textMask);

        // configure the text fields
        rowsText.setColumns(50);
        colsText.setColumns(50);
        rowsText.addPropertyChangeListener("value", this);
        colsText.addPropertyChangeListener("value", this);

        GroupLayout.SequentialGroup horGroup = gl.createSequentialGroup();
        horGroup.addGroup(gl.createParallelGroup().addComponent(numRowsLabel).addComponent(numColsLabel))
                .addGroup(gl.createParallelGroup().addComponent(rowsText).addComponent(colsText));
        gl.setHorizontalGroup(horGroup);

        GroupLayout.SequentialGroup verGroup = gl.createSequentialGroup();
        verGroup.addGroup(gl.createParallelGroup(GroupLayout.Alignment.BASELINE).addComponent(numRowsLabel).addComponent(rowsText))
                .addGroup(gl.createParallelGroup(GroupLayout.Alignment.BASELINE).addComponent(numColsLabel).addComponent(colsText));
        gl.setVerticalGroup(verGroup);
    }

    //public GridSize getSize() {
    //    return new GridSize()
    //}

    @Override
    public void propertyChange(PropertyChangeEvent propertyChangeEvent) {
        // TODO: fill this with logic to relay grid dimensions to the model
    }
}

GridBoxPanel:

public class GridBoxPanel extends JPanel {

    public GridBoxPanel() {
        setLayout(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints();
        for (int row = 0; row < 32; row++) {
            for (int col = 0; col < 32; col++) {
                gbc.gridx = col;
                gbc.gridy = row;

                GridCell gridCell = new GridCell();
                Border border = null;
                if (row < 4) {
                    if (col < 4) {
                        border = new MatteBorder(1, 1, 0, 0, Color.GRAY);
                    } else {
                        border = new MatteBorder(1, 1, 0, 1, Color.GRAY);
                    }
                } else {
                    if (col < 4) {
                        border = new MatteBorder(1, 1, 1, 0, Color.GRAY);
                    } else {
                        border = new MatteBorder(1, 1, 1, 1, Color.GRAY);
                    }
                }
                gridCell.setBorder(border);
                add(gridCell, gbc);
            }
        }
    }
}

MazeSolverInterface:

public class MazeSolverInterface extends JFrame {

    public MazeSolverInterface(String[] args) throws ParseException {
        checkArgs(args);
        initMaze(args);
    }

    public void initMaze(String[] args) throws ParseException {
        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        // create required panels to integrate
        GridSizePanel gridSizeComponent = new GridSizePanel();
        GridBoxPanel gridDrawComponent = new GridBoxPanel();

        gl.setHorizontalGroup(gl.createSequentialGroup().addComponent(gridDrawComponent).addGap(50).addComponent(gridSizeComponent));
        gl.setVerticalGroup(gl.createParallelGroup().addComponent(gridDrawComponent).addGap(50).addComponent(gridSizeComponent));
        pack();

        setTitle("v0.0.1");
        setSize(700, 700);  // TODO: change to something configurable
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public void checkArgs(String[] args) {
        // TODO: fill with logic to check valid arguments (initial window dimensions)
    }
}

Основной:

public class Main {

    public static void main(final String[] args) {

        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                MazeSolverInterface ex = null;
                try {
                    ex = new MazeSolverInterface(args);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
                ex.setVisible(true);
            }
        });
    }
}

Вот как выглядит пользовательский интерфейс до добавления GridBoxLabel в MazeSolverInterface:

введите описание изображения здесь

... и после добавления GridBoxLabel в MazeSolverInterface:

введите описание изображения здесь

Любая / вся помощь приветствуется. Спасибо!

РЕДАКТИРОВАТЬ: Как видно выше, GridBoxPanel использует класс GridCell. Я забыл добавить это в этот пост, так что вот оно. Надеюсь это поможет!

GridCell:

public class GridCell extends JPanel {

    private Color defaultBackground;

    public GridCell() {
        addMouseListener(new MouseAdapter() {
            @Override
            public void mouseEntered(MouseEvent e) {
                defaultBackground = getBackground();
                setBackground(Color.BLUE);
            }

            @Override
            public void mouseExited(MouseEvent e) {
                setBackground(defaultBackground);
            }
        });
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(50, 50);
    }
}

person Nishant Kelkar    schedule 30.08.2014    source источник
comment
Вы не должны использовать GroupLayout вручную. GroupLayout предназначены для инструментов GUI Builder. Использование GroupLayout вручную будет очень сложным и подверженным ошибкам. Вы можете использовать любой конструктор графического интерфейса, если хотите использовать GroupLayout.   -  person afzalex    schedule 31.08.2014
comment
Вы имеете в виду, что если бы я удалил любые / все зависимости GroupLayout из приведенного выше кода и перезапустил, мои проблемы с изменением размера исчезли бы?   -  person Nishant Kelkar    schedule 31.08.2014
comment
Тогда однозначно сработает. Это ваша панель с групповой компоновкой, которая создает проблему, потому что вы пытались вручную реализовать GroupLayout. И если вы все еще хотите сделать это с помощью GroupLayout вручную, отключите все autoCreate...Gaps() и вместо этого вручную добавьте containerGap (..) по мере необходимости. Только так вы можете изменить размер панели GroupLayout.   -  person afzalex    schedule 31.08.2014
comment
Возможно, для решения вашей проблемы с макетом использование BorderLayout - самый простой способ. Одна панель по центру, другая панель справа. (Если я правильно понимаю ваш макет)   -  person Ben    schedule 31.08.2014
comment
Спасибо за все комментарии! Я не собираюсь отвечать на этот вопрос, так как сам не нашел, но обнаружена ошибка: в GridCell я устанавливаю предпочтительный размер 50x50. Для сетки 32x32 это 1600 по одному измерению. Но в MazeSolverInterface одна сторона всего 700! Вот почему лабиринт такой крошечный и находится в центре. Я изменил getPreferredSize на что-то вроде 15x15, и теперь он выглядит намного лучше. Проблема с изменением размера, похоже, тоже исчезла из-за этого. Я также добавил границу к GridBoxPanel, чтобы измерить потери пространства вдоль границ сетки.   -  person Nishant Kelkar    schedule 31.08.2014
comment
Кстати: судя по виду, GridCell было бы лучше заменить на JButton с использованием значков разного цвета (размером 50x50) для значка по умолчанию и значка при наведении курсора.   -  person Andrew Thompson    schedule 01.09.2014


Ответы (1)


GroupLayout действительно был создан для инструментов с графическим интерфейсом пользователя, однако его можно без проблем использовать вручную. Из встроенных менеджеров я бы рекомендовал использовать именно его.

Я немного изменил ваш пример:

MazeSolverInterface.java

import java.awt.Container;
import java.text.ParseException;
import javax.swing.GroupLayout;
import static javax.swing.GroupLayout.Alignment.BASELINE;
import static javax.swing.GroupLayout.Alignment.TRAILING;
import javax.swing.JFormattedTextField;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JLabel;
import javax.swing.text.MaskFormatter;

public class MazeSolverInterface extends JFrame {

    public MazeSolverInterface(String[] args) throws ParseException {

        initMaze(args);
    }

    private void initMaze(String[] args) throws ParseException {

        Container pane = getContentPane();
        GroupLayout gl = new GroupLayout(pane);
        pane.setLayout(gl);

        gl.setAutoCreateContainerGaps(true);

        gl.setAutoCreateGaps(true);

        JLabel numRowsLabel = new JLabel("Rows:");
        JLabel numColsLabel = new JLabel("Columns:");

        MaskFormatter textMask = new MaskFormatter("##");
        //textMask.setPlaceholder("16");
        JFormattedTextField rowsText = new JFormattedTextField(textMask);
        JFormattedTextField colsText = new JFormattedTextField(textMask);

        rowsText.setColumns(20);
        colsText.setColumns(20);

        GridBoxPanel gridDrawComponent = new GridBoxPanel();

        gl.setHorizontalGroup(gl.createParallelGroup()
                .addGroup(gl.createSequentialGroup()
                        .addGroup(gl.createParallelGroup(TRAILING)
                                .addComponent(numRowsLabel)
                                .addComponent(numColsLabel))
                        .addGroup(gl.createParallelGroup()
                                .addComponent(rowsText)
                                .addComponent(colsText)))
                .addComponent(gridDrawComponent));

        gl.setVerticalGroup(gl.createSequentialGroup()
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(numRowsLabel)
                        .addComponent(rowsText))
                .addGroup(gl.createParallelGroup(BASELINE)
                        .addComponent(numColsLabel)
                        .addComponent(colsText))
                .addComponent(gridDrawComponent));

        pack();

        setTitle("v0.0.1");
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
}

Я избавился от GridSizePanel и переместил код в MazeSolverInterface. С точки зрения дизайна, нет необходимости в панели с заголовком, если нет других конкретных панелей. Ярлыки выравниваются по правому краю. Текстовые поля и объект лабиринта увеличиваются или уменьшаются при изменении размера окна.

Метод setSize() был удален, поскольку предпочтительнее использовать метод pack(). Вы либо используете одно, либо другое, но не то и другое одновременно.

GridBoxPanel.java

import java.awt.Color;
import java.awt.GridLayout;
import javax.swing.JPanel;
import javax.swing.border.Border;
import javax.swing.border.MatteBorder;

public class GridBoxPanel extends JPanel {

    public GridBoxPanel() {
        setLayout(new GridLayout(32, 32, 1, 1));

        for (int row = 0; row < 32; row++) {
            for (int col = 0; col < 32; col++) {

                GridCell gridCell = new GridCell();
                Border border = null;
                if (row < 4) {
                    if (col < 4) {
                        border = new MatteBorder(1, 1, 0, 0, Color.GRAY);
                    } else {
                        border = new MatteBorder(1, 1, 0, 1, Color.GRAY);
                    }
                } else {
                    if (col < 4) {
                        border = new MatteBorder(1, 1, 1, 0, Color.GRAY);
                    } else {
                        border = new MatteBorder(1, 1, 1, 1, Color.GRAY);
                    }
                }
                gridCell.setBorder(border);
                add(gridCell);
            }
        }
    }
}

Здесь я использовал GridLayout менеджер вместо GridBagLayout. (Это, возможно, только третий пример, который я нашел, где GridLayout может быть полезным.) Я сделал эту модификацию, чтобы упростить задачу, но лично я бы никогда не использовал GridLayout и создавал решение полностью с GroupLayout или MigLayout менеджерами. (GridLayout не переносится, потому что устанавливает поля в пикселях. Это не оптимально, поскольку выбранное нами фиксированное пространство не подходит для всех вариантов разрешения экрана. То, что подходит для меньшего экрана, не подходит для большего. Как правило, нам не следует устанавливать размеры в пикселях. Это относится и к вашему переопределенному методу getPreferredSize().)

Лабиринт

person Jan Bodnar    schedule 02.09.2014