【问题标题】:JFrame.getInsets weird valueJFrame.getInsets 奇怪的值
【发布时间】:2020-09-10 11:43:51
【问题描述】:

在 Windows 10 上采用 OpenJDK 11。JFrame 后代构造函数:

getGraphicsConfiguration().getBounds()
java.awt.Rectangle[x=0,y=0,width=2560,height=1440]

Toolkit.getDefaultToolkit().getScreenInsets(getGraphicsConfiguration())
java.awt.Insets[top=0,left=0,bottom=40,right=0]

set*Size(new Dimension(2560, 1400));

pack();

getSize();
java.awt.Dimension[width=2560,height=1400]

getInsets();
java.awt.Insets[top=31,left=8,bottom=8,right=8]

getContentPane().getSize()
java.awt.Dimension[width=2544,height=1361]

...但窗口并没有覆盖整个桌面空间减去任务栏(因为那些额外的 8 插图)。为什么?

使用我的答案的示例代码:

package javaapplication2;

import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GraphicsConfiguration;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import javax.swing.JFrame;
import javax.swing.JPanel;

public final class TestFrame extends JFrame {

    private static final long serialVersionUID = 1L;

    private static final class CustomPanel extends JPanel {

        private static final long serialVersionUID = 1L;

        public CustomPanel(Dimension d) {
            setMinimumSize(d);
            setPreferredSize(d);
            setMaximumSize(d);
            //this is just an example, there is custom layout code here, not using Swing, but it needs Dimension d to work
        }

    }

    public TestFrame() {
        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);

        getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 0, 0));

        GraphicsConfiguration gc = getGraphicsConfiguration();
        Rectangle screenBounds = gc.getBounds();
        Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
        Dimension expectedFrameSize = new Dimension(screenBounds.width - screenInsets.left - screenInsets.right, screenBounds.height - screenInsets.top - screenInsets.bottom);

        setExtendedState(JFrame.MAXIMIZED_BOTH);
        setVisible(true);
        Dimension frameSize = getSize();
        Insets frameInsets = getInsets();
        setVisible(false);
        Dimension contentSize = new Dimension(frameSize.width - frameInsets.left - frameInsets.right, frameSize.height - frameInsets.top - frameInsets.bottom);
        getContentPane().add(new CustomPanel(contentSize));

        setVisible(true);
        setResizable(false);

        System.out.println("screenBounds " + screenBounds);
        System.out.println("screenInsets " + screenInsets);
        System.out.println("expectedFrameSize " + expectedFrameSize);
        System.out.println("frameSize " + frameSize);
        System.out.println("frameInsets " + frameInsets);
        System.out.println("contentSize " + contentSize);
        System.out.println("assert expectedFrameSize == frameSize " + expectedFrameSize.equals(frameSize));
    }

    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                new TestFrame().setVisible(true);
            }
        });
    }

}

我希望能够计算Dimension dimension 而不使窗口暂时可见。但是使用 JDK 中可用的方法无法得出正确的大小:

getGraphicsConfiguration().getBounds()
Toolkit.getDefaultToolkit().getScreenInsets(getGraphicsConfiguration())

【问题讨论】:

  • 框架的插图是框架的一部分,因此它确实覆盖了桌面空间。你究竟想要什么? a) maximize the frame, b) a borderless window, 或 c) a fullscreen window?
  • 当前框架并没有像最大化的窗口那样占用整个桌面空间。在左边、右边和底部有几个像素宽(我猜是 8 像素宽)的条带,下面的窗口是可见的。我还需要在显示框架之前计算内容窗格的大小,因为很多事情都取决于这个大小。所以我想计算最大化的大小,并将其设置为那个大小,而不是实际最大化。因此,尽管它说框架是 2560 宽,但我不能在这些波段中看到其他窗口,而且我的显示器肯定是 2560 宽。
  • 操作系统(或其桌面)决定如何表示边框。如果它决定有一个半透明的阴影,它算作边框的一部分,那就是它的方式。同样,不清楚你想要什么。如果您想要与最大化框架相同的行为,那么为什么不最大化它呢?如果你不想有边框,为什么不关闭边框呢?如果您只想在显示之前知道内容窗格的大小,为什么不使用您自己发布的代码,这表明在显示框架之前,pack() 之后的大小是已知的?
  • 然后致电setExtendedState(MAXIMIZED_BOTH)。或者提供一个易于理解的解释,为什么您不能选择使用专门用于该目的的 API。
  • 所以你的问题是setExtendedState(JFrame.MAXIMIZED_BOTH)在框架不可见时不会立即影响框架的大小? size 是屏幕大小减去 screen insets,还要减去标题栏(但不包括上边框),其大小没有查询 API。如果那是你的问题,你应该一开始就这么说,而不是试图模仿setExtendedState

标签: java swing java-11


【解决方案1】:

框架的插图描述了窗口装饰的大小,可能包括半透明边框。在 Microsoft Windows 上,这也与早期版本中窗口边框的厚度为 8 像素这一事实有关。

当您最大化窗口时,它的边界将被设置为使这些插图位于可见区域之外,但标题栏的某些部分除外,它们保持可见。为了使可预测性更差,标题栏会在窗口最大化时更改其布局,从而减少边距空间。

但是,当您正在寻找仅适用于 Windows 的解决方案时,遗留方面可能会有所帮助。考虑文章“Why does a maximized window have the wrong window rectangle?”。正如它所解释的,窗口将始终位于 (-n,-n) 并且具有 (2n×2n) 大于实际可见区域,用于边框大小n,以与旧软件兼容。但是,正如它也解释的那样,最大化模式是特殊的,因为这些边界总是被切断,所以它们不会出现在其他显示器上,也不会出现在任务栏上。

这就是为什么仅通过设置边界来模拟相同的行为是不可能的。不仅标题栏的呈现方式不同,您还会使窗口的某些部分出现在多显示器系统上的其他显示器和任务栏上。

因此,利用这些知识,您可以预测内容窗格的大小:

JFrame frame = new JFrame("Max");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setExtendedState(Frame.MAXIMIZED_BOTH);

Timer t = new Timer(1000, ev -> System.out.println("actual size is "
    +frame.getContentPane().getWidth()+" x "+frame.getContentPane().getHeight()));
t.setRepeats(false);
t.start();

Rectangle scrBounds = frame.getGraphicsConfiguration().getBounds();
Insets scrInsets = frame.getToolkit().getScreenInsets(frame.getGraphicsConfiguration());
Insets winInsets = frame.getInsets();

int width = scrBounds.width - scrInsets.left - scrInsets.right;
int height = scrBounds.height - scrInsets.top - scrInsets.bottom
                              - winInsets.top + winInsets.bottom;

System.out.println("content pane size will be "+width+" x "+height);

frame.setVisible(true);

这假设所有边缘的历史边框厚度都相同,因此我们可以使用bottom 大小来确定标题栏大小的顶部大小,因此top - bottom 给了我们剩余的标题栏大小。

【讨论】:

  • 谢谢,所以这似乎是一个普通的 windows 东西,我可以考虑那些额外的 8,8,8,8 插图。
  • 我减少了硬编码假设,只假设topbottom 的特定边框具有相同的大小,这似乎在 Windows API 中已修复,因为我只看到一个值tagWINDOWINFO 结构中的边框高度。所以top - bottom 是标题栏的大小,这是在最大化窗口中减少内容窗格可用大小的唯一剩余的东西。
  • 所以解决这个问题的最佳方法是通过本机代码查询 Windows,以获得魔法 8。
  • 我找到了frame.getToolkit().getDesktopProperty("win.frame.captionHeight"),它给出了标题栏的大小,除了 1px 的边框。但是我不知道那个边框是一直是 1px 还是需要另外一个属性来查询。这让我们回到第一方。
  • 同时在 linux windows hack 上不起作用,但仍然有假的 5 px 边框添加到框架插图:)
【解决方案2】:

显然最大化的框架的尺寸大于实际屏幕,并且框架插图包括 8,8,8,8 像素(在我的 Windows 上),其中一些在实际框架之外。获得用于最大化窗口的实际帧大小的唯一方法是使其可见一段时间。最后我用了这个作弊方法:

      setExtendedState(JFrame.MAXIMIZED_BOTH);
      setVisible(true);
      Dimension size = getSize();
      Insets insets = getInsets();
      setVisible(false);
      dimension = new Dimension(size.width - insets.left - insets.right, size.height - insets.top - insets.bottom);

这使我可以找出框架内容窗格的尺寸,其中框架占用了所有桌面空间(屏幕减去任务栏)。而setResizable(false) 只能在最终的setVisible(true) 之后调用。

看看这些数字对你是否有意义:

屏幕边界java.awt.Rectangle[x=0,y=0,width=2560,height=1440]

屏幕插图java.awt.Insets[top=0,left=0,bottom=40,right=0]

帧大小java.awt.Dimension[width=2576,height=1416]

框架插图java.awt.Insets[top=31,left=8,bottom=8,right=8]

计算尺寸java.awt.Dimension[width=2560,height=1377]

帧大小在每个方向上比屏幕空间(屏幕边界减去屏幕插入)大 16。这些额外的像素包含在帧插图中,即使它们不在帧中。

【讨论】:

  • 我需要内容窗格的尺寸,因为它用于缩放和布局所述内容窗格内的元素,这些元素来自使用固定定位的遗留系统。任务非常简单,在显示该框架之前找出最大化框架的内容窗格的尺寸。而已。目前看来这是不可能的,而且各种 bounds、sized 和 insets 方法显示奇怪的值(例如最大化的帧大于屏幕尺寸)。
  • 发布的示例适用于我的答案。但我想要维度而不闪烁窗口。
  • 问题是我不能使用屏幕边界和插图来确定框架大小和内容大小,因为最大化的框架实际上比屏幕大,并且框架插图包括屏幕外框架占用的额外空间。这不奇怪吗?
  • 查看我的要求 - 我需要为内容窗格内的自定义布局管理器提供大小。我的理由是我自己的,但恕我直言,我应该能够在不求助于检查可见窗口的情况下得出正确的数字:screenBounds java.awt.Rectangle[x=0,y=0,width=2560,height=1440] screenInsets java.awt.Insets[top=0,left=0,bottom=40,right=0] expectedFrameSize java.awt.Dimension[width=2560,height=1400] frameSize java.awt.Dimension[width=2576,height=1416] frameInsets java.awt.Insets[top=31,left=8,bottom=8,right=8] contentSize java.awt.Dimension[width=2560,height=1377]
  • 我无法解释那些 8,8,8,8 帧插图,它们将在屏幕外占用帧大小(因此,它们不会从内容大小中减去,而是增加了帧大小)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 2016-06-02
相关资源
最近更新 更多