Я использую GridBagLayout для создания панели, состоящей из 4 подпанелей. 4 подпанели окрашены в пурпурный, синий, красный и голубой цвета, а основная панель — в зеленый. Цель состоит в том, чтобы расположить эти 4 подпанели определенным образом, и теоретически зеленый цвет не должен отображаться. К счастью, у меня есть рабочий пример того, чего я хочу добиться:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) {
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int width = d.width;
int height = 1046;
JFrame frame = new JFrame("4.5 test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel masterPanel = new JPanel(new GridBagLayout());
masterPanel.setBackground(Color.GREEN);
GridBagConstraints c = new GridBagConstraints();
c.weightx = 1.0;
c.weighty = 1.0;
// 1
JPanel leftUpper = new JPanel();
leftUpper.setBackground(Color.MAGENTA);
leftUpper.setPreferredSize(new Dimension(width/3, height/7));
c.gridx = 0;
c.gridy = 0;
c.anchor = GridBagConstraints.NORTH;
masterPanel.add(leftUpper, c);
// 2
JPanel leftLower = new JPanel();
leftLower.setBackground(Color.BLUE);
leftLower.setPreferredSize(new Dimension(width/3, height - height/7));
c.gridheight = 2;
c.anchor = GridBagConstraints.SOUTH;
masterPanel.add(leftLower, c);
// 3
JPanel rightUpper = new JPanel();
rightUpper.setBackground(Color.RED);
rightUpper.setPreferredSize(new Dimension(width - width/3, height - height/5));
c.gridx = 1;
c.gridheight = 1;
masterPanel.add(rightUpper, c);
// 4
JPanel rightLower = new JPanel();
rightLower.setBackground(Color.CYAN);
rightLower.setPreferredSize(new Dimension(width - width/3, height/5));
c.gridy = 1;
masterPanel.add(rightLower, c);
frame.add(masterPanel);
frame.pack();
frame.setVisible(true);
}
}
При запуске это дает этот правильный результат. На самом деле каждая высота ниже 1046 работает как задумано.
Однако момент меняет строку 16 на это:
int height = 1047;
Все летит к черту, и мы получаем это вместо.. Это справедливо и для любой высоты выше 1047. Панели находятся в правильном положении, но далеки от правильных размеров. Обратите внимание, что, в отличие от высоты, изменение ширины не имеет (непреднамеренного) эффекта.
Я бы не был так удивлен такому поведению, если бы оно не было вызвано таким небольшим (произвольным?) ограничением размера. Для контекста я использую среду 1.8 Java RunTime. Разрешение экрана моего компьютера 1920x1080. Почему это происходит, как это можно исправить, и если это жестокая практика, какую подробную альтернативу вы можете предоставить?
GridBagLayout
имеет странное поведение, когда не хватает места для размеров компонентов, по крайней мере, до их предпочтительного размера. Он не деградирует изящно.
Кажется, вы пытаетесь создать абсолютный макет. GridBagLayout
и другие общие LayoutManger
s (другие менеджеры компоновки X-Y) не работают в этих ситуациях.
Придерживаясь GridBagLayout
, обычно лучше использовать его внутри JScrollPane
или, по крайней мере, JPanel
с минимальным размером, взятым из его предпочтительного размера.
@VGR Вопрос в том, какое поведение вы хотите, когда не хватает места. GBL также отбрасывает вставки - я никогда не видел, чтобы это было желательным поведением. Если вы не можете заставить рамку иметь минимальный размер (который вам может понадобиться больше, чем конкретный экран), то что-то должно уступить. JScrollPane
(с полосами по требованию) где-нибудь в дереве компонентов обычно уместно. В качестве альтернативы вы можете захотеть обрезать, но я думаю, что это маловероятно.
@VGR Кажется, это сработало! Я все еще не понимаю, как предпочтительные размеры не могут быть размещены, потому что это выглядит, как будто это должно работать точно, хотя это явно не так. Если это плохой или сложный способ реализации панелей, расположенных, как показано, каковы ваши предложения, как реализовать это иначе? Большое спасибо за оба ваших ответа.
@ Q-Tek Похоже, вы не учли границу кадра. В этом случае я бы использовал вложенные BorderLayouts, установив предпочтительный размер только для меньших компонентов и поместив их в боковые ограничения. Таким образом, голубая и красная панели будут в одном BorderLayout (красный будет центральным компонентом); пурпурная и синяя панели будут находиться в другом BorderLayout (синий будет центральным компонентом). Установите предпочтительные размеры только пурпурной и голубой панелей, чтобы остальные заняли все оставшееся пространство. Точно так же поместите синюю/пурпурную панель в западную позицию «мастера» BorderLayout.
Когда GridBagLayout не может вместить предпочтительные размеры всех дочерних компонентов, он «сдается» и устанавливает минимальный размер каждого отдельного дочернего компонента. Таким образом, одним из решений является установка минимального размера каждого компонента на его предпочтительный размер.
Однако вместо того, чтобы пытаться математически учитывать каждый пиксель на экране, у вас есть более надежные варианты.
Одним из решений является установка размера только одной панели в любом конкретном измерении, чтобы другая панель всегда занимала оставшееся место. Таким образом, вы никогда не сможете «переполнить» доступное пространство. Для этого хорошо подходит BorderLayout, так как его центральный компонент растягивается, чтобы занять все доступное пространство, которое не используют боковые компоненты.
Итак, для ваших пурпурных и синих панелей вы должны установить высоту только пурпурной панели:
JPanel leftPanel = new JPanel(new BorderLayout());
// 1
JPanel leftUpper = new JPanel();
leftUpper.setBackground(Color.MAGENTA);
leftUpper.setPreferredSize(new Dimension(1, height/7));
leftPanel.add(leftUpper, BorderLayout.NORTH);
// 2
JPanel leftLower = new JPanel();
leftLower.setBackground(Color.BLUE);
leftPanel.add(leftLower, BorderLayout.CENTER);
И аналогично для красного и голубого:
JPanel rightPanel = new JPanel(new BorderLayout());
// 3
JPanel rightUpper = new JPanel();
rightUpper.setBackground(Color.RED);
rightPanel.add(rightUpper, BorderLayout.CENTER);
// 4
JPanel rightLower = new JPanel();
rightLower.setBackground(Color.CYAN);
rightLower.setPreferredSize(new Dimension(1, height/5));
rightPanel.add(rightLower, BorderLayout.SOUTH);
А затем вы делаете что-то похожее на сами эти две панели, чтобы соединить их вместе:
JPanel masterPanel = new JPanel(new BorderLayout());
masterPanel.setBackground(Color.GREEN);
leftPanel.setPreferredSize(new Dimension(width / 3,
leftPanel.getPreferredSize().height));
masterPanel.add(leftPanel, BorderLayout.WEST);
masterPanel.add(rightPanel, BorderLayout.CENTER);
А поскольку сообщения о границах, заданных окнам оконным менеджером системы, ненадежны, вы избегаете каких-либо явных математических вычислений и просто увеличиваете окно:
frame.add(masterPanel);
frame.pack();
frame.setExtendedState(Frame.MAXIMIZED_BOTH);
frame.setVisible(true);
Если вам абсолютно необходимо сохранить соотношение между панелями (например, magenta_height : blue_height :: 1 : 6), вы можете сделать это с помощью SpringLayout, но SpringLayout сложнее и обычно не стоит заморачиваться. Это позволяет указать диапазоны размеров, которые могут быть кратны другим диапазонам размеров:
SpringLayout layout = new SpringLayout();
JPanel masterPanel = new JPanel(layout);
masterPanel.setBackground(Color.GREEN);
// 1
JPanel leftUpper = new JPanel();
leftUpper.setBackground(Color.MAGENTA);
masterPanel.add(leftUpper);
// 2
JPanel leftLower = new JPanel();
leftLower.setBackground(Color.BLUE);
masterPanel.add(leftLower);
// 3
JPanel rightUpper = new JPanel();
rightUpper.setBackground(Color.RED);
masterPanel.add(rightUpper);
// 4
JPanel rightLower = new JPanel();
rightLower.setBackground(Color.CYAN);
masterPanel.add(rightLower);
Spring leftUpperHeight = Spring.height(leftUpper);
Spring leftLowerHeight = Spring.scale(leftUpperHeight, 6);
Spring rightLowerHeight = Spring.scale(leftUpperHeight, 7 / 5f);
Spring leftWidth = Spring.width(leftUpper);
Spring rightWidth = Spring.scale(leftWidth, 3);
layout.getConstraints(leftLower).setHeight(leftLowerHeight);
layout.getConstraints(rightLower).setHeight(rightLowerHeight);
layout.getConstraints(rightUpper).setWidth(rightWidth);
layout.getConstraints(rightLower).setWidth(rightWidth);
// Place leftLower beneath leftUpper.
layout.putConstraint(
SpringLayout.NORTH, leftLower, 0,
SpringLayout.SOUTH, leftUpper);
// Make leftLower's width match leftUpper's width.
layout.putConstraint(
SpringLayout.WEST, leftLower, 0,
SpringLayout.WEST, leftUpper);
layout.putConstraint(
SpringLayout.EAST, leftLower, 0,
SpringLayout.EAST, leftUpper);
// Make container high enough to hold both leftLower and leftUpper.
layout.putConstraint(
SpringLayout.SOUTH, masterPanel, 0,
SpringLayout.SOUTH, leftLower);
// Place rightUpper and rightLower to the right of leftUpper.
layout.putConstraint(
SpringLayout.WEST, rightLower, 0,
SpringLayout.EAST, leftUpper);
layout.putConstraint(
SpringLayout.WEST, rightUpper, 0,
SpringLayout.WEST, rightLower);
// Make container wide enough to accommodate rightUpper and rightLower.
layout.putConstraint(
SpringLayout.EAST, masterPanel, 0,
SpringLayout.EAST, rightLower);
// Align bottom of rightLower with bottom of leftLower.
layout.putConstraint(
SpringLayout.SOUTH, rightLower, 0,
SpringLayout.SOUTH, leftLower);
// Place rightUpper above rightLower.
layout.putConstraint(
SpringLayout.SOUTH, rightUpper, 0,
SpringLayout.NORTH, rightLower);
// Stretch rightUpper to reach to the top of the container.
layout.putConstraint(
SpringLayout.NORTH, rightUpper, 0,
SpringLayout.NORTH, masterPanel);
Ваш первый абзац правильный. Но размещение макета в JScrollPane — не очень хорошая идея. Это приводит к довольно плохому удобству использования. На самом деле GridBagLayout возвращает каждый дочерний элемент к его минимальному размеру, когда предпочтительные размеры не могут быть размещены. Таким образом, простым решением является установка минимального размера каждого дочернего компонента на его предпочтительный размер.