为什么在进入全屏时在 JFrame 中设置首选大小会失败?
Why does setting preferred sizes in a JFrame fail when entering fullscreen?
我想为实际屏幕创建一个具有固定 width/heigth 比率的游戏(这样我就可以轻松地缩放游戏元素,而不必为复杂的布局而烦恼)。为此,我创建了一个 JFrame
和一个 BorderLayout
,中间是实际屏幕,两侧是 4 个间距面板。每次调整大小后,我都会重新计算所需的间距并相应地设置首选尺寸,以便屏幕获得适合框架的给定比例的最大矩形。下面的代码完成了这项工作,我用一个简单的蓝色面板替换了实际的屏幕,还添加了输出来检查计算:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class JFrameResize {
private static JFrame frame;
private static JPanel inside = new JPanel();
private static JPanel spacingTop = new JPanel();
private static JPanel spacingBottom = new JPanel();
private static JPanel spacingLeft = new JPanel();
private static JPanel spacingRight = new JPanel();
private static JPanel compound = new JPanel();
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
public static void createAndShowGUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
inside.setBackground(Color.BLUE);
compound.setLayout(new BorderLayout());
compound.add(inside, BorderLayout.CENTER);
compound.add(spacingTop, BorderLayout.NORTH);
compound.add(spacingBottom, BorderLayout.SOUTH);
compound.add(spacingLeft, BorderLayout.WEST);
compound.add(spacingRight, BorderLayout.EAST);
frame.add(compound);
frame.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
calculateSize();
}
});
calculateSize();
frame.setVisible(true);
}
public static void calculateSize() {
double width = frame.getContentPane().getWidth();
double height = frame.getContentPane().getHeight();
double vSpacing = 0, hSpacing = 0;
System.out.println("frame: " + width + ":" + height);
// ratio is hardcoded to 3:4
if ((width * 3 - height * 4) >= 0) {
hSpacing = (width - (height * 4) / 3) / 2;
width = width - 2 * hSpacing;
} else {
vSpacing = (height - (width * 3) / 4) / 2;
height = height - 2 * vSpacing;
}
System.out.println("spacing: " + hSpacing + " + " + width + " + " + hSpacing + " : "
+ vSpacing + " + " + height + " + " + vSpacing);
spacingBottom.setPreferredSize(new Dimension((int) width, (int) vSpacing));
spacingTop.setPreferredSize(new Dimension((int) width, (int) vSpacing));
spacingLeft.setPreferredSize(new Dimension((int) hSpacing, (int) height));
spacingRight.setPreferredSize(new Dimension((int) hSpacing, (int) height));
inside.setPreferredSize(new Dimension((int) width, (int) height));
frame.revalidate();
frame.repaint();
System.out.println("inside: " + inside.getWidth() + ":" + inside.getHeight()
+ ", " + "border: " + spacingLeft.getWidth() + ":"
+ spacingTop.getHeight());
}
}
这在大部分时间都运行良好,我得到的输出类似于
frame: 510.0:445.0
spacing: 0.0 + 510.0 + 0.0 : 31.25 + 382.5 + 31.25
inside: 510:385, border: 0:30
框架显示正确的矩形。但是,如果我将框架设置为全屏,我会得到以下输出:
frame: 1366.0:705.0
spacing: 213.0 + 940.0 + 213.0 : 0.0 + 705.0 + 0.0
inside: 1366:643, border: 0:31
这是不正确的,因为我的公式计算出内部是 940:705,但它变成了 1312:705。矩形也不再正确显示。使用 windows + 箭头键或将框架拖到屏幕两侧也是如此。计算输入是正确的,但 repaint/revalidate 的行为与正常的调整大小有所不同。 revalidate()
和 repaint()
的不同组合或在代码中大量使用它们似乎没有任何改变。
这是怎么发生的,我该如何解决?还是这种方法总体上有缺陷,应该被替换?
不要使用 BorderLayout 并尝试添加间距组件。
相反,我建议您可以将内容面板的布局管理器设置为 GridBagLayout
。使用默认 GridBagConstraints
时,添加到 GridBagLayout
的任何组件都将自动居中。
然后对于添加到框架的 "inside" 面板,您需要覆盖 getPreferredSize()
方法来计算面板的大小。
要确定其首选大小,您需要获取其父容器的大小,然后根据您的规则确定其首选大小。
不需要 ComponentListener,因为只要调整框架大小时就会自动调用布局管理器。
让您入门的简单示例:
import java.awt.*;
import javax.swing.*;
public class JFrameResize
{
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel inside = new JPanel()
{
@Override
public Dimension getPreferredSize()
{
Dimension parent = getParent().getSize();
Dimension child = new Dimension();
int value = (parent.width * 3) - (parent.height * 4);
if (value > 0)
{
child.height = parent.height;
child.width = (int)(child.height * 4 / 3);
}
else
{
child.width = parent.width;
child.height = (int)(child.width * 3 / 4);
}
return child;
}
};
inside.setBackground(Color.BLUE);
frame.setLayout( new GridBagLayout() );
frame.add(inside, new GridBagConstraints());
frame.pack();
frame.setVisible(true);
}
}
关于为什么我的方法不起作用:
This source from this question(感谢@Andrew Thompson 的链接)指出:
[setPreferredSize is] used by LayoutManager as hints to balance the actual layout
所以我的 setPreferredSize
可能被 BorderLayout
覆盖了,为了确保 LayoutManager
不会干扰你必须覆盖 getPreferredSize
我想为实际屏幕创建一个具有固定 width/heigth 比率的游戏(这样我就可以轻松地缩放游戏元素,而不必为复杂的布局而烦恼)。为此,我创建了一个 JFrame
和一个 BorderLayout
,中间是实际屏幕,两侧是 4 个间距面板。每次调整大小后,我都会重新计算所需的间距并相应地设置首选尺寸,以便屏幕获得适合框架的给定比例的最大矩形。下面的代码完成了这项工作,我用一个简单的蓝色面板替换了实际的屏幕,还添加了输出来检查计算:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class JFrameResize {
private static JFrame frame;
private static JPanel inside = new JPanel();
private static JPanel spacingTop = new JPanel();
private static JPanel spacingBottom = new JPanel();
private static JPanel spacingLeft = new JPanel();
private static JPanel spacingRight = new JPanel();
private static JPanel compound = new JPanel();
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
public static void createAndShowGUI() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
inside.setBackground(Color.BLUE);
compound.setLayout(new BorderLayout());
compound.add(inside, BorderLayout.CENTER);
compound.add(spacingTop, BorderLayout.NORTH);
compound.add(spacingBottom, BorderLayout.SOUTH);
compound.add(spacingLeft, BorderLayout.WEST);
compound.add(spacingRight, BorderLayout.EAST);
frame.add(compound);
frame.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
calculateSize();
}
});
calculateSize();
frame.setVisible(true);
}
public static void calculateSize() {
double width = frame.getContentPane().getWidth();
double height = frame.getContentPane().getHeight();
double vSpacing = 0, hSpacing = 0;
System.out.println("frame: " + width + ":" + height);
// ratio is hardcoded to 3:4
if ((width * 3 - height * 4) >= 0) {
hSpacing = (width - (height * 4) / 3) / 2;
width = width - 2 * hSpacing;
} else {
vSpacing = (height - (width * 3) / 4) / 2;
height = height - 2 * vSpacing;
}
System.out.println("spacing: " + hSpacing + " + " + width + " + " + hSpacing + " : "
+ vSpacing + " + " + height + " + " + vSpacing);
spacingBottom.setPreferredSize(new Dimension((int) width, (int) vSpacing));
spacingTop.setPreferredSize(new Dimension((int) width, (int) vSpacing));
spacingLeft.setPreferredSize(new Dimension((int) hSpacing, (int) height));
spacingRight.setPreferredSize(new Dimension((int) hSpacing, (int) height));
inside.setPreferredSize(new Dimension((int) width, (int) height));
frame.revalidate();
frame.repaint();
System.out.println("inside: " + inside.getWidth() + ":" + inside.getHeight()
+ ", " + "border: " + spacingLeft.getWidth() + ":"
+ spacingTop.getHeight());
}
}
这在大部分时间都运行良好,我得到的输出类似于
frame: 510.0:445.0
spacing: 0.0 + 510.0 + 0.0 : 31.25 + 382.5 + 31.25
inside: 510:385, border: 0:30
框架显示正确的矩形。但是,如果我将框架设置为全屏,我会得到以下输出:
frame: 1366.0:705.0
spacing: 213.0 + 940.0 + 213.0 : 0.0 + 705.0 + 0.0
inside: 1366:643, border: 0:31
这是不正确的,因为我的公式计算出内部是 940:705,但它变成了 1312:705。矩形也不再正确显示。使用 windows + 箭头键或将框架拖到屏幕两侧也是如此。计算输入是正确的,但 repaint/revalidate 的行为与正常的调整大小有所不同。 revalidate()
和 repaint()
的不同组合或在代码中大量使用它们似乎没有任何改变。
这是怎么发生的,我该如何解决?还是这种方法总体上有缺陷,应该被替换?
不要使用 BorderLayout 并尝试添加间距组件。
相反,我建议您可以将内容面板的布局管理器设置为 GridBagLayout
。使用默认 GridBagConstraints
时,添加到 GridBagLayout
的任何组件都将自动居中。
然后对于添加到框架的 "inside" 面板,您需要覆盖 getPreferredSize()
方法来计算面板的大小。
要确定其首选大小,您需要获取其父容器的大小,然后根据您的规则确定其首选大小。
不需要 ComponentListener,因为只要调整框架大小时就会自动调用布局管理器。
让您入门的简单示例:
import java.awt.*;
import javax.swing.*;
public class JFrameResize
{
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel inside = new JPanel()
{
@Override
public Dimension getPreferredSize()
{
Dimension parent = getParent().getSize();
Dimension child = new Dimension();
int value = (parent.width * 3) - (parent.height * 4);
if (value > 0)
{
child.height = parent.height;
child.width = (int)(child.height * 4 / 3);
}
else
{
child.width = parent.width;
child.height = (int)(child.width * 3 / 4);
}
return child;
}
};
inside.setBackground(Color.BLUE);
frame.setLayout( new GridBagLayout() );
frame.add(inside, new GridBagConstraints());
frame.pack();
frame.setVisible(true);
}
}
关于为什么我的方法不起作用:
This source from this question(感谢@Andrew Thompson 的链接)指出:
[setPreferredSize is] used by LayoutManager as hints to balance the actual layout
所以我的 setPreferredSize
可能被 BorderLayout
覆盖了,为了确保 LayoutManager
不会干扰你必须覆盖 getPreferredSize