Java Windows 下的多显示器处理 - 缩放显示器的错误?
Java Multi-Display Handling under Windows - Bug with scaled displays?
tl;dr
在 Windows 10 下,如果我将副显示器放在主显示器的右侧,并对副显示器应用缩放比例(例如 150%),则显示器坐标(如 return 由 Java API) 重叠而不是让显示边界并排放置。换句话说,如果我慢慢地将我的鼠标从初级的左边缘移动到次级的右边缘,Java 的 API MouseInfo.getPointerInfo().getLocation()
return 会增加X 位置从 0 到 1920,一旦光标进入第二个屏幕,该值将跳回 1280,然后再次增加到 2560。因此 1280-1920 范围被 returned 两次,用于不同的区域。
在 post 的末尾,我包含了一个(更新的)演示,它使问题变得显而易见。不要犹豫,尝试一下并反馈。
长版:
本文提供了(太多)上下文,但也旨在分享我在搜索该主题时学到的东西。
首先,何必呢?因为我正在 Java 中构建一个屏幕捕获应用程序,它需要正确处理多显示器配置,包括应用了 Windows' 缩放功能的显示器。
使用Java API (GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()
),只要缩放比例为100%,就可以观察到主显示器的左上角在原点( 0,0),其他显示器的坐标“紧邻”主显示器。
以下图片是使用post末尾的代码制作的。
例如如果我们有 2 个全高清显示器,主显示器的左上角位于 (0,0),而...
- 如果次级在它的右边,在同一层,它的左上角是(1920,0):
- 如果次级在它的左边,在同一层,它的左上角是(-1920,0):
- 如果次级位于下方,水平对齐,其左上角为 (0,1080):
- 如果次级位于上方,水平对齐,其左上角为(0,-1080):
- 如果显示未对齐,依此类推:
- 或不同的分辨率:
但是,如果缩放辅助显示器,事情就会出错:似乎缩放因子不仅应用于它的尺寸,而且还应用于它的 origin,它越来越接近(0,0).
如果次要在左边,这是有道理的。例如,当次级 1920x1080 缩放为 150% 时,它使逻辑 1280x720 位于 (-1280,0):
但是如果次级在右边,原点也会缩放到(1280,0),越来越接近原点并导致它“重叠”主要一:
换句话说,如果鼠标位于 (1800,0) - 见上面的红点 - 我看不出它是否真的位于第一个显示器的右侧(距离右边缘 120 像素) ) 或在次要的左侧(在左边缘的 520 像素处)。在这种情况下,当将鼠标从主显示器移动到副显示器时,鼠标的 X 位置在到达主显示器的边界时“跳回”。
在屏幕上定位 window 也是如此。如果我将对话框的 X 位置设置为 1800,我将无法知道它将在何处打开。
经过多次浏览,一些答案 表明查询 Windows 缩放的唯一方法是使用本机调用。实际上,使用 JNA,可以获得显示的物理尺寸(尽管答案似乎表明调用应该 return 逻辑尺寸)。即 JNA 调用忽略比例因子,当比例为 100% 时,其行为与 Java API 完全相同:
我是不是漏掉了什么?
不知道比例因子是一个小问题,但无法判断鼠标在哪个显示器上,或者无法在我想要的显示器上定位 window 看起来像是一个真正的问题大部头书。是 Java 错误吗?
注意:这是上面使用的应用程序的代码,运行 在 Windows 10 64b 上使用 OpenJDK14。它显示了 Java 感知到的显示设置和鼠标位置的缩小版本。如果您在小矩形内单击并拖动,它还可以在真实屏幕上放置和移动一个小对话框。致谢:UI 的灵感来自 WheresMyMouse 代码 posted here.
原样,代码仅使用 Java API。
如果要与 JNA 进行比较,请搜索标记为“JNA_ONLY”的 4 个块,取消注释它们,然后添加 jna 库。然后,该演示将在 JNA 和 Java API 之间切换,以便在每次右键单击时显示屏幕边界和鼠标光标。对话框定位在这个版本中从不使用JNA。
// JNA_ONLY
//import com.sun.jna.platform.win32.User32;
//import com.sun.jna.platform.win32.WinDef;
//import com.sun.jna.platform.win32.WinUser;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
/**
* Java multi-display detection and analysis.
* UI idea based on WheresMyMouse -
*/
public class ShowDisplays {
private static boolean useJna = false;
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame("Display Configuration");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static class TestPane extends JPanel {
private List<Rectangle> screenBounds;
JDialog dlg;
public TestPane() {
screenBounds = getScreenBounds();
// refresh screen details every second to reflect changes in Windows Preferences in "real time"
new Timer(1000, e -> screenBounds = getScreenBounds()).start();
// Refresh mouse position at 25fps
new Timer(40, e -> repaint()).start();
MouseAdapter mouseAdapter = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
useJna = !useJna;
repaint();
}
}
@Override
public void mousePressed(MouseEvent e) {
System.out.println(e.getButton());
if (e.getButton() == MouseEvent.BUTTON1) {
if (!dlg.isVisible()) {
dlg.setVisible(true);
}
moveDialogTo(e.getPoint());
}
}
@Override
public void mouseDragged(MouseEvent e) {
moveDialogTo(e.getPoint());
}
private void moveDialogTo(Point mouseLocation) {
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
int screenX = surroundingRectangle.x + (int) ((mouseLocation.x - xOffset) / scaleFactor);
int screenY = surroundingRectangle.y + (int) ((mouseLocation.y - yOffset) / scaleFactor);
dlg.setLocation(screenX - dlg.getWidth() / 2, screenY - dlg.getHeight() / 2);
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
// Prepare the test dialog
dlg = new JDialog();
dlg.setTitle("Here");
dlg.setSize(50, 50);
dlg.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// Mouse position
Point mousePoint = getMouseLocation();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
g2d.setColor(Color.BLUE);
g2d.fillRect(xOffset, yOffset, (int) (surroundingRectangle.width * scaleFactor), (int) (surroundingRectangle.height * scaleFactor));
Font defaultFont = g2d.getFont();
for (int screenIndex = 0; screenIndex < screenBounds.size(); screenIndex++) {
Rectangle screen = screenBounds.get(screenIndex);
Rectangle scaledRectangle = new Rectangle(
xOffset + (int) ((screen.x - surroundingRectangle.x) * scaleFactor),
yOffset + (int) ((screen.y - surroundingRectangle.y) * scaleFactor),
(int) (screen.width * scaleFactor),
(int) (screen.height * scaleFactor));
// System.out.println(screen + " x " + scaleFactor + " -> " + scaledRectangle);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(scaledRectangle);
g2d.setColor(Color.GRAY);
g2d.draw(scaledRectangle);
// Screen text details
g2d.setColor(Color.WHITE);
// Display number
final Font largeFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 2);
g2d.setFont(largeFont);
String label = String.valueOf(screenIndex + 1);
FontRenderContext frc = g2d.getFontRenderContext();
TextLayout layout = new TextLayout(label, largeFont, frc);
Rectangle2D bounds = layout.getBounds();
g2d.setColor(Color.WHITE);
g2d.drawString(
label,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + (scaledRectangle.height + bounds.getHeight()) / 2)
);
// Resolution + corner
final Font smallFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 10);
g2d.setFont(smallFont);
// Resolution
String resolution = screen.width + "x" + screen.height;
layout = new TextLayout(resolution, smallFont, frc);
bounds = layout.getBounds();
g2d.drawString(
resolution,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + scaledRectangle.height - bounds.getHeight())
);
// Corner
String corner = "(" + screen.x + "," + screen.y + ")";
g2d.drawString(
corner,
scaledRectangle.x,
(int) (scaledRectangle.y + bounds.getHeight() * 1.5)
);
}
g2d.setFont(defaultFont);
FontMetrics fm = g2d.getFontMetrics();
if (mousePoint != null) {
g2d.fillOval(xOffset + (int) ((mousePoint.x - surroundingRectangle.x) * scaleFactor) - 2,
yOffset + (int) ((mousePoint.y - surroundingRectangle.y) * scaleFactor) - 2,
4,
4
);
g2d.drawString("Mouse pointer is at (" + mousePoint.x + "," + mousePoint.y + ")", 4, fm.getHeight());
}
g2d.drawString("Click and drag in this area to move a dialog on the actual screens", 4, fm.getHeight() * 2);
// JNA_ONLY
// g2d.drawString("Now using " + (useJna ? "JNA" : "Java API") + ". Right-click to toggle", 4, fm.getHeight() * 3);
g2d.dispose();
}
}
public static Rectangle getSurroundingRectangle(List<Rectangle> screenRectangles) {
Rectangle surroundingBounds = null;
for (Rectangle screenBound : screenRectangles) {
if (surroundingBounds == null) {
surroundingBounds = new Rectangle(screenRectangles.get(0));
}
else {
surroundingBounds.add(screenBound);
}
}
return surroundingBounds;
}
private static Point getMouseLocation() {
// JNA_ONLY
// if (useJna) {
// final WinDef.POINT point = new WinDef.POINT();
// if (User32.INSTANCE.GetCursorPos(point)) {
// return new Point(point.x, point.y);
// }
// else {
// return null;
// }
// }
return MouseInfo.getPointerInfo().getLocation();
}
public static List<Rectangle> getScreenBounds() {
List<Rectangle> screenBounds;
// JNA_ONLY
// if (useJna) {
// screenBounds = new ArrayList<>();
// // Enumerate all monitors, and call a code block for each of them
// // See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
// // See http://www.pinvoke.net/default.aspx/user32/EnumDisplayMonitors.html
// User32.INSTANCE.EnumDisplayMonitors(
// null, // => the virtual screen that encompasses all the displays on the desktop.
// null, // => don't clip the region
// (hmonitor, hdc, rect, lparam) -> {
// // For each found monitor, get more information
// // See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
// // See http://www.pinvoke.net/default.aspx/user32/GetMonitorInfo.html
// WinUser.MONITORINFOEX monitorInfoEx = new WinUser.MONITORINFOEX();
// User32.INSTANCE.GetMonitorInfo(hmonitor, monitorInfoEx);
// // Retrieve its coordinates
// final WinDef.RECT rcMonitor = monitorInfoEx.rcMonitor;
// // And convert them to a Java rectangle, to be added to the list of monitors
// screenBounds.add(new Rectangle(rcMonitor.left, rcMonitor.top, rcMonitor.right - rcMonitor.left, rcMonitor.bottom - rcMonitor.top));
// // Then return "true" to continue enumeration
// return 1;
// },
// null // => No additional info to pass as lparam to the callback
// );
// return screenBounds;
// }
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screenDevices = graphicsEnvironment.getScreenDevices();
screenBounds = new ArrayList<>(screenDevices.length);
for (GraphicsDevice screenDevice : screenDevices) {
GraphicsConfiguration configuration = screenDevice.getDefaultConfiguration();
screenBounds.add(configuration.getBounds());
}
return screenBounds;
}
}
这看起来你 运行 出现了错误 JDK-8211999:
In a multi-monitor setting involving one HiDPI screen placed to the right of one regular monitor, on Windows 10, the bounds returned by GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[x].getDefaultConfiguration().getBounds()
are overlapping. This causes various secondary bugs...
评论注意:
The same bug exists on Linux as well, macOS is not affected.
似乎没有简单的纯 Java 解决方法。
A fix has been proposed 适用于 Windows,甚至不尝试在 Java 中进行坐标数学运算,并将解决方案委托给本机代码。
由于使用 JNA(本机)实现似乎可行,这似乎是 JDK 版本 9 到 15 的最佳方法。该错误已在 JDK16 中修复。
根据错误报告,它会影响 JDK 9+,因此恢复到 JDK 8 可能会解决该问题,尽管我看到了关于此的冲突帐户。
tl;dr
在 Windows 10 下,如果我将副显示器放在主显示器的右侧,并对副显示器应用缩放比例(例如 150%),则显示器坐标(如 return 由 Java API) 重叠而不是让显示边界并排放置。换句话说,如果我慢慢地将我的鼠标从初级的左边缘移动到次级的右边缘,Java 的 API MouseInfo.getPointerInfo().getLocation()
return 会增加X 位置从 0 到 1920,一旦光标进入第二个屏幕,该值将跳回 1280,然后再次增加到 2560。因此 1280-1920 范围被 returned 两次,用于不同的区域。
在 post 的末尾,我包含了一个(更新的)演示,它使问题变得显而易见。不要犹豫,尝试一下并反馈。
长版:
本文提供了(太多)上下文,但也旨在分享我在搜索该主题时学到的东西。
首先,何必呢?因为我正在 Java 中构建一个屏幕捕获应用程序,它需要正确处理多显示器配置,包括应用了 Windows' 缩放功能的显示器。
使用Java API (GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()
),只要缩放比例为100%,就可以观察到主显示器的左上角在原点( 0,0),其他显示器的坐标“紧邻”主显示器。
以下图片是使用post末尾的代码制作的。
例如如果我们有 2 个全高清显示器,主显示器的左上角位于 (0,0),而...
- 如果次级在它的右边,在同一层,它的左上角是(1920,0):
- 如果次级在它的左边,在同一层,它的左上角是(-1920,0):
- 如果次级位于下方,水平对齐,其左上角为 (0,1080):
- 如果次级位于上方,水平对齐,其左上角为(0,-1080):
- 如果显示未对齐,依此类推:
- 或不同的分辨率:
但是,如果缩放辅助显示器,事情就会出错:似乎缩放因子不仅应用于它的尺寸,而且还应用于它的 origin,它越来越接近(0,0).
如果次要在左边,这是有道理的。例如,当次级 1920x1080 缩放为 150% 时,它使逻辑 1280x720 位于 (-1280,0):
但是如果次级在右边,原点也会缩放到(1280,0),越来越接近原点并导致它“重叠”主要一:
换句话说,如果鼠标位于 (1800,0) - 见上面的红点 - 我看不出它是否真的位于第一个显示器的右侧(距离右边缘 120 像素) ) 或在次要的左侧(在左边缘的 520 像素处)。在这种情况下,当将鼠标从主显示器移动到副显示器时,鼠标的 X 位置在到达主显示器的边界时“跳回”。
在屏幕上定位 window 也是如此。如果我将对话框的 X 位置设置为 1800,我将无法知道它将在何处打开。
经过多次浏览,一些答案
我是不是漏掉了什么?
不知道比例因子是一个小问题,但无法判断鼠标在哪个显示器上,或者无法在我想要的显示器上定位 window 看起来像是一个真正的问题大部头书。是 Java 错误吗?
注意:这是上面使用的应用程序的代码,运行 在 Windows 10 64b 上使用 OpenJDK14。它显示了 Java 感知到的显示设置和鼠标位置的缩小版本。如果您在小矩形内单击并拖动,它还可以在真实屏幕上放置和移动一个小对话框。致谢:UI 的灵感来自 WheresMyMouse 代码 posted here.
原样,代码仅使用 Java API。 如果要与 JNA 进行比较,请搜索标记为“JNA_ONLY”的 4 个块,取消注释它们,然后添加 jna 库。然后,该演示将在 JNA 和 Java API 之间切换,以便在每次右键单击时显示屏幕边界和鼠标光标。对话框定位在这个版本中从不使用JNA。
// JNA_ONLY
//import com.sun.jna.platform.win32.User32;
//import com.sun.jna.platform.win32.WinDef;
//import com.sun.jna.platform.win32.WinUser;
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.font.TextLayout;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.List;
/**
* Java multi-display detection and analysis.
* UI idea based on WheresMyMouse -
*/
public class ShowDisplays {
private static boolean useJna = false;
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
JFrame frame = new JFrame("Display Configuration");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static class TestPane extends JPanel {
private List<Rectangle> screenBounds;
JDialog dlg;
public TestPane() {
screenBounds = getScreenBounds();
// refresh screen details every second to reflect changes in Windows Preferences in "real time"
new Timer(1000, e -> screenBounds = getScreenBounds()).start();
// Refresh mouse position at 25fps
new Timer(40, e -> repaint()).start();
MouseAdapter mouseAdapter = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
useJna = !useJna;
repaint();
}
}
@Override
public void mousePressed(MouseEvent e) {
System.out.println(e.getButton());
if (e.getButton() == MouseEvent.BUTTON1) {
if (!dlg.isVisible()) {
dlg.setVisible(true);
}
moveDialogTo(e.getPoint());
}
}
@Override
public void mouseDragged(MouseEvent e) {
moveDialogTo(e.getPoint());
}
private void moveDialogTo(Point mouseLocation) {
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
int screenX = surroundingRectangle.x + (int) ((mouseLocation.x - xOffset) / scaleFactor);
int screenY = surroundingRectangle.y + (int) ((mouseLocation.y - yOffset) / scaleFactor);
dlg.setLocation(screenX - dlg.getWidth() / 2, screenY - dlg.getHeight() / 2);
}
};
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
// Prepare the test dialog
dlg = new JDialog();
dlg.setTitle("Here");
dlg.setSize(50, 50);
dlg.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
// Mouse position
Point mousePoint = getMouseLocation();
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
final Rectangle surroundingRectangle = getSurroundingRectangle(screenBounds);
double scaleFactor = Math.min((double) getWidth() / surroundingRectangle.width, (double) getHeight() / surroundingRectangle.height);
int xOffset = (getWidth() - (int) (surroundingRectangle.width * scaleFactor)) / 2;
int yOffset = (getHeight() - (int) (surroundingRectangle.height * scaleFactor)) / 2;
g2d.setColor(Color.BLUE);
g2d.fillRect(xOffset, yOffset, (int) (surroundingRectangle.width * scaleFactor), (int) (surroundingRectangle.height * scaleFactor));
Font defaultFont = g2d.getFont();
for (int screenIndex = 0; screenIndex < screenBounds.size(); screenIndex++) {
Rectangle screen = screenBounds.get(screenIndex);
Rectangle scaledRectangle = new Rectangle(
xOffset + (int) ((screen.x - surroundingRectangle.x) * scaleFactor),
yOffset + (int) ((screen.y - surroundingRectangle.y) * scaleFactor),
(int) (screen.width * scaleFactor),
(int) (screen.height * scaleFactor));
// System.out.println(screen + " x " + scaleFactor + " -> " + scaledRectangle);
g2d.setColor(Color.DARK_GRAY);
g2d.fill(scaledRectangle);
g2d.setColor(Color.GRAY);
g2d.draw(scaledRectangle);
// Screen text details
g2d.setColor(Color.WHITE);
// Display number
final Font largeFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 2);
g2d.setFont(largeFont);
String label = String.valueOf(screenIndex + 1);
FontRenderContext frc = g2d.getFontRenderContext();
TextLayout layout = new TextLayout(label, largeFont, frc);
Rectangle2D bounds = layout.getBounds();
g2d.setColor(Color.WHITE);
g2d.drawString(
label,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + (scaledRectangle.height + bounds.getHeight()) / 2)
);
// Resolution + corner
final Font smallFont = new Font(defaultFont.getName(), defaultFont.getStyle(), (int) (screen.height * scaleFactor) / 10);
g2d.setFont(smallFont);
// Resolution
String resolution = screen.width + "x" + screen.height;
layout = new TextLayout(resolution, smallFont, frc);
bounds = layout.getBounds();
g2d.drawString(
resolution,
(int) (scaledRectangle.x + (scaledRectangle.width - bounds.getWidth()) / 2),
(int) (scaledRectangle.y + scaledRectangle.height - bounds.getHeight())
);
// Corner
String corner = "(" + screen.x + "," + screen.y + ")";
g2d.drawString(
corner,
scaledRectangle.x,
(int) (scaledRectangle.y + bounds.getHeight() * 1.5)
);
}
g2d.setFont(defaultFont);
FontMetrics fm = g2d.getFontMetrics();
if (mousePoint != null) {
g2d.fillOval(xOffset + (int) ((mousePoint.x - surroundingRectangle.x) * scaleFactor) - 2,
yOffset + (int) ((mousePoint.y - surroundingRectangle.y) * scaleFactor) - 2,
4,
4
);
g2d.drawString("Mouse pointer is at (" + mousePoint.x + "," + mousePoint.y + ")", 4, fm.getHeight());
}
g2d.drawString("Click and drag in this area to move a dialog on the actual screens", 4, fm.getHeight() * 2);
// JNA_ONLY
// g2d.drawString("Now using " + (useJna ? "JNA" : "Java API") + ". Right-click to toggle", 4, fm.getHeight() * 3);
g2d.dispose();
}
}
public static Rectangle getSurroundingRectangle(List<Rectangle> screenRectangles) {
Rectangle surroundingBounds = null;
for (Rectangle screenBound : screenRectangles) {
if (surroundingBounds == null) {
surroundingBounds = new Rectangle(screenRectangles.get(0));
}
else {
surroundingBounds.add(screenBound);
}
}
return surroundingBounds;
}
private static Point getMouseLocation() {
// JNA_ONLY
// if (useJna) {
// final WinDef.POINT point = new WinDef.POINT();
// if (User32.INSTANCE.GetCursorPos(point)) {
// return new Point(point.x, point.y);
// }
// else {
// return null;
// }
// }
return MouseInfo.getPointerInfo().getLocation();
}
public static List<Rectangle> getScreenBounds() {
List<Rectangle> screenBounds;
// JNA_ONLY
// if (useJna) {
// screenBounds = new ArrayList<>();
// // Enumerate all monitors, and call a code block for each of them
// // See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-enumdisplaymonitors
// // See http://www.pinvoke.net/default.aspx/user32/EnumDisplayMonitors.html
// User32.INSTANCE.EnumDisplayMonitors(
// null, // => the virtual screen that encompasses all the displays on the desktop.
// null, // => don't clip the region
// (hmonitor, hdc, rect, lparam) -> {
// // For each found monitor, get more information
// // See https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmonitorinfoa
// // See http://www.pinvoke.net/default.aspx/user32/GetMonitorInfo.html
// WinUser.MONITORINFOEX monitorInfoEx = new WinUser.MONITORINFOEX();
// User32.INSTANCE.GetMonitorInfo(hmonitor, monitorInfoEx);
// // Retrieve its coordinates
// final WinDef.RECT rcMonitor = monitorInfoEx.rcMonitor;
// // And convert them to a Java rectangle, to be added to the list of monitors
// screenBounds.add(new Rectangle(rcMonitor.left, rcMonitor.top, rcMonitor.right - rcMonitor.left, rcMonitor.bottom - rcMonitor.top));
// // Then return "true" to continue enumeration
// return 1;
// },
// null // => No additional info to pass as lparam to the callback
// );
// return screenBounds;
// }
GraphicsEnvironment graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] screenDevices = graphicsEnvironment.getScreenDevices();
screenBounds = new ArrayList<>(screenDevices.length);
for (GraphicsDevice screenDevice : screenDevices) {
GraphicsConfiguration configuration = screenDevice.getDefaultConfiguration();
screenBounds.add(configuration.getBounds());
}
return screenBounds;
}
}
这看起来你 运行 出现了错误 JDK-8211999:
In a multi-monitor setting involving one HiDPI screen placed to the right of one regular monitor, on Windows 10, the bounds returned by
GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[x].getDefaultConfiguration().getBounds()
are overlapping. This causes various secondary bugs...
评论注意:
The same bug exists on Linux as well, macOS is not affected.
似乎没有简单的纯 Java 解决方法。
A fix has been proposed 适用于 Windows,甚至不尝试在 Java 中进行坐标数学运算,并将解决方案委托给本机代码。
由于使用 JNA(本机)实现似乎可行,这似乎是 JDK 版本 9 到 15 的最佳方法。该错误已在 JDK16 中修复。
根据错误报告,它会影响 JDK 9+,因此恢复到 JDK 8 可能会解决该问题,尽管我看到了关于此的冲突帐户。