【问题标题】:Java Swing Pixels Being InaccurateJava Swing 像素不准确
【发布时间】:2019-05-09 18:00:19
【问题描述】:

我正在使用 Swing 设计一个 Java 应用程序,但我在设计没有布局的 GUI 时遇到了麻烦。

我的目的是设计一个带有一个JPanel 和四个JButtons 的GUI。我已经计算好在正确的位置设置按钮和面板,并编码如下:

import java.awt.*;
import javax.swing.*;

public class MainFrame extends JFrame {
    public MainFrame() {
        this.setTitle("Example Frame");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(null);

        JPanel randomPanel = new JPanel();
        randomPanel.setOpaque(true);
        randomPanel.setBackground(Color.RED);
        randomPanel.setBounds(10, 10, 430, 530);

        JButton addButton = new JButton("Add");
        addButton.setBounds(10, 550, 100, 40);
        addButton.setBackground(Color.GRAY);

        JButton deleteButton = new JButton("Delete");
        deleteButton.setBounds(120, 550, 100, 40);
        deleteButton.setBackground(Color.GRAY);

        JButton refreshButton = new JButton("Refresh");
        refreshButton.setBounds(230, 550, 100, 40);
        refreshButton.setBackground(Color.GRAY);

        JButton devButton = new JButton("Developer");
        devButton.setBounds(340, 550, 100, 40);
        devButton.setBackground(Color.GRAY);

        this.add(randomPanel);
        this.add(addButton);
        this.add(deleteButton);
        this.add(refreshButton);
        this.add(devButton);

        this.setSize(900, 600);
        this.setResizable(false);
        this.setVisible(true);
    }

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

按照代码,组件预计如下放置:

但是,实际的表单显示如下:

组件超出形式,与预期外观不符。

这是什么问题,应该怎么做才能准确放置组件?

【问题讨论】:

  • 不做数学计算,我假设您根据 frame 的大小计算大小,并且没有考虑标题栏和边框的大小。一般来说,您应该使用setBounds 来放置组件。请改用LayoutManager。在这种情况下,您可以拥有一个带有BorderLayout 的“主”面板。红色面板可能位于“主”面板的CENTER 中。这些按钮可以位于带有GridLayout(1,4) 的自己的面板中,而该面板又位于“主”面板的SOUTH 中。
  • @Marco13 我也很困惑,如果尺寸对标题栏很重要,所以我截取了表格并在 Photoshop 应用程序上计算了尺寸。首先宽度和高度都不正确,我将宽度调整为 900px,然后高度为 602px。我认为摇摆应用程序本身是不正确的。我会尝试你的解决方案,但我很怀疑,因为我想要精确的尺寸。
  • @cylee 有一些简单的 API 可以解决所有这些问题,从布局管理 API 开始

标签: java swing


【解决方案1】:

我来晚了,我认为这不再对 OP 有帮助......但对处于同样情况的其他人来说。

正如其他人提到的,当您在 JFrame 上设置大小时,其中包括标题栏和边框。有一种方法可以获取它们的大小值,但是...如果您想在内容窗格中手动布局,为什么不先准备一个内容窗格,然后将其添加到 JFrame 中?

class MainPanel extends JPanel {
    public MainPanel() {
        setLayout(null);
        setPreferredSize(new Dimension(900, 600));
        // JFrame will have some layouting going on,
        // it won't listen to setSize

        JPanel randomPanel = new JPanel();
        randomPanel.setOpaque(true);
        randomPanel.setBackground(Color.RED);
        randomPanel.setBounds(10, 10, 430, 530);

        JButton addButton = new JButton("Add");
        addButton.setBounds(10, 550, 100, 40);
        addButton.setBackground(Color.GRAY);

        JButton deleteButton = new JButton("Delete");
        deleteButton.setBounds(120, 550, 100, 40);
        deleteButton.setBackground(Color.GRAY);

        JButton refreshButton = new JButton("Refresh");
        refreshButton.setBounds(230, 550, 100, 40);
        refreshButton.setBackground(Color.GRAY);

        JButton devButton = new JButton("Developer");
        devButton.setBounds(340, 550, 100, 40);
        devButton.setBackground(Color.GRAY);

        this.add(randomPanel);
        this.add(addButton);
        this.add(deleteButton);
        this.add(refreshButton);
        this.add(devButton);
    }

    public static void main(String[] args) {
        JFrame mainFrame = new JFrame();
        mainFrame.setTitle("Example Frame");
        mainFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        mainFrame.setContentPane(new MainPanel());

        mainFrame.pack();
        mainFrame.setResizable(false);
        mainFrame.setVisible(true);
    }
}

如果您直接与 JFrame 混淆,您就是在绕过组件系统。而这种方式,你做的组件就好了!现在,您有一个适合单个子面板的 JFrame,其中有一些手动布局的内容。

在这种情况下,我通常是这样做的。

附: “不要手动布局,只使用布局管理器”不是你可以在任何地方应用的东西。有时您可能需要自定义组件,尤其是对于像视频游戏这样的游戏,您需要自定义渲染的游戏屏幕。在游戏屏幕内,您将进行手动布局。只要你知道哪个是哪个,它们就可以共存。

【解决方案2】:

主要有两个问题...

  • setLayout(null)
  • setSize

您没有考虑到的事实是,窗口内容可用的空间量是窗口大小减去框架装饰。

像素完美布局是现代 UI 开发中的一种错觉,最好避免。

你可以看看:

了解更多详情。

更好的解决方案是使用一个或多个可用的布局管理器。下面的示例简单地使用了BorderLayoutGridLayout,并在EmptyBorder 的帮助下提供了一些填充

详情请见Laying Out Components Within a Container

好处

  • 自适应布局:
    • 该示例使用pack 自动“打包”内容周围的窗口,而无需您使代码适应当前运行的操作系统(或不同外观和感觉提供的框架装饰)
    • 用户可以更改窗口的大小,内容会自动调整大小 - 对用户的奖励。
    • 布局将适应用户的系统设置,因此如果他们使用的字体比您设计的更大,它不会完全在您的脸上炸开
    • 想要添加更多按钮?不用担心,把自己搞砸,只需添加更多按钮,布局会自动适应,无需在屏幕上“像素推送”任何组件

可运行示例...

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;

public class Test {

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

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() {
            setLayout(new BorderLayout());
            setBorder(new EmptyBorder(10, 10, 10, 10));
            add(new SizablePane(430, 530));

            JPanel buttonPane = new JPanel(new GridLayout(1, 3, 20, 0));
            buttonPane.setBorder(new EmptyBorder(10, 0, 0, 0));
            buttonPane.add(new JButton("Add"));
            buttonPane.add(new JButton("Delete"));
            buttonPane.add(new JButton("Refresh"));
            buttonPane.add(new JButton("Developer"));

            add(buttonPane, BorderLayout.SOUTH);
        }

    }

    public class SizablePane extends JPanel {

        private Dimension size;

        public SizablePane(int width, int height) {
            size = new Dimension(width, height);
            setBackground(Color.RED);
        }

        @Override
        public Dimension getPreferredSize() {
            return size;
        }

    }

}

需要添加更多按钮?简单...

JPanel buttonPane = new JPanel(new GridLayout(1, 0, 20, 0));
buttonPane.setBorder(new EmptyBorder(10, 0, 0, 0));
buttonPane.add(new JButton("Add"));
buttonPane.add(new JButton("Delete"));
buttonPane.add(new JButton("Refresh"));
buttonPane.add(new JButton("Developer"));
buttonPane.add(new JButton("Some"));
buttonPane.add(new JButton("More"));
buttonPane.add(new JButton("Buttons"));

【讨论】:

  • 这是我在meta.stackoverflow.com/q/373852/3182664 中提到的案例之一。 (坦率地说:这就是为什么我犹豫不决的原因。但我确实没有犹豫不决当前接受的一个...... *叹息*)。对于“为什么不应该使用 setBounds,而是使用 LayoutManger”,我们是否有一个很好的规范问题?如果没有,那么通常的嫌疑人(你、气垫船、camickr 和……我,就此而言)肯定应该创造一个!
  • @Marco13 我已经为 Windows、MacOS、Linux、iOS 和 Android 编写了软件,在所有这些情况下(不包括游戏)我没有找到不使用平台/api 布局系统的理由.不这样做所涉及的工作量只是不值得努力
  • @Marco13 甚至在“非传统”game based concept 中使用它们。关键是,布局管理器会处理很多可变问题,包括 DPI、屏幕分辨率、字体指标和渲染管道差异……我“可能”唯一一次不使用布局管理器的时候,是我可能想要将组件从一个位置动画化到另一个位置(跨组件边界),但是一旦组件降落在目标组件中,它将再次处于布局管理器的控制之下
  • @Marco13 您还必须考虑,在许多情况下,问题是关于“如何”实现结果,事实是,在这种情况下,这个答案只是描述(恕我直言)解决过度问题的更好方法,所以我认为没有一个“你应该使用布局管理器” q/a 可以涵盖所有可能的场景,但它肯定会让它变得更好,然后不得不一直重复自己:P
【解决方案3】:

您需要覆盖底层 JFrame 的 getInsets() 方法。

@Override
public Insets getInsets() {
    return new Insets(0, 0, 0, 0);
}

查看此question 了解更多信息。

【讨论】:

  • 没问题。另一种方法是从 JFrame 中获取插图,然后将它们用作组件位置的偏移量,记住调整 JFrame 的大小以适应这些插图所需的额外空间边界。
  • 绝对没有理由为这样的事情使用空布局。这是您尝试使用像素时遇到的问题类型。布局管理器将在几秒钟内解决问题。
  • @cylee - 其他人是对的。更典型的是,您会使用 Swing(或现在的 JavaFX)中的布局管理器来管理您的组件布局。如果您需要,这将为您提供自适应/响应式界面。但是,由于您已经表现出对其他 LayoutManager 的认识,并且似乎明确决定不使用其中之一,所以我按照您的要求回答了您的问题,而不是对您说教..
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-04-04
  • 1970-01-01
  • 2013-10-22
  • 2023-03-14
相关资源
最近更新 更多