问题描述
不久前,我编写并制作了一个 Zoom to Mouse Swing 面板,可以处理突出显示、平移、鼠标缩放、选择等。非常棒。
我今天又去玩了一些,但它不起作用。我一头雾水。我知道我有一个工作示例——某处。但是在我的驱动器上搜索,我的实验都没有奏效。我开始尝试让它再次工作。
最终我发现了问题。它是 JDK 11 和我的新 iMac 的某种组合。上次我在这方面工作是在我的旧 Mac 上(我可能使用过也可能没有使用过 JDK 11,我不记得了)。
如果您使用代码,您会发现在 JDK 11 下显然存在某种(2X?)扩展,但在 JDK 8 下没有。我不知道它是否试图弥补我机器上的大显示屏,或者发生了什么。
但是您尝试在 JDK 11 下进行缩放或平移,发现它没有以鼠标为中心,以及跟踪高亮显示错误等。
如何在 JDK 11 下使其正常工作?它是唯一的Mac吗?还是带有“驱动影院尺寸显示器”的 Mac?
代码如下:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class TestPanel {
public static void main(String[] args) throws Exception {
SwingUtilities.invokelater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame("Test Zoom");
f.setDefaultCloSEOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
TestPanelZoom p = new TestPanelZoom();
f.add(p,BorderLayout.CENTER);
f.setPreferredSize(new Dimension(400,500));
f.pack();
f.setVisible(true);
}
private static class TestPanelZoom extends JPanel {
boolean hilighted = false;
private int hiliteX = -1;
private int hiliteY = -1;
boolean selected = false;
private int selectX = -1;
private int selectY = -1;
private AffineTransform at = new AffineTransform();
public TestPanelZoom() {
setBackground(Color.WHITE);
setForeground(Color.BLACK);
addMouseAdapter();
at.setToIdentity();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400,500);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setTransform(at);
paintPanel(g2d);
g2d.dispose();
}
private void paintPanel(Graphics2D g2) {
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Color c = g2.getColor();
g2.setColor(Color.WHITE);
g2.fill(getBounds());
g2.setColor(c);
for (int i = 0; i < 400; i += 50) {
Line2D line = new Line2D.Double(i,i,350);
g2.draw(line);
line = new Line2D.Double(0,350,i);
g2.draw(line);
}
if (hilighted) {
Rectangle2D rect = new Rectangle2D.Double(hiliteX * 50 + 5,hiliteY * 50 + 5,40,40);
g2.setColor(Color.GREEN);
g2.fill(rect);
}
if (selected) {
Rectangle2D rect = new Rectangle2D.Double(selectX * 50 + 10,selectY * 50 + 10,30,30);
g2.setColor(Color.RED);
g2.fill(rect);
}
}
private void addMouseAdapter() {
MouseAdapter ma = new MouseAdapter() {
int lx,ly;
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
AffineTransform t = new AffineTransform();
double delta = 1 + 0.05f * e.getPreciseWheelRotation();
int x = e.getX();
int y = e.getY();
t.translate(x,y);
t.scale(delta,delta);
t.translate(-x,-y);
t.concatenate(at);
at = t;
revalidate();
repaint();
}
@Override
public void mousepressed(MouseEvent e) {
lx = e.getX();
ly = e.getY();
}
@Override
public void mouseDragged(MouseEvent e) {
update(e);
}
@Override
public void mouseReleased(MouseEvent e) {
update(e);
}
@Override
public void mouseClicked(MouseEvent e) {
Point2D srcPt = getSrcPoint(e);
selected = true;
selectX = (int) (srcPt.getX() / 50);
selectY = (int) (srcPt.getY() / 50);
revalidate();
repaint();
}
@Override
public void mouseMoved(MouseEvent e) {
Point2D srcPt = getSrcPoint(e);
hilighted = true;
hiliteX = (int) (srcPt.getX() / 50);
hiliteY = (int) (srcPt.getY() / 50);
revalidate();
repaint();
}
public void update(MouseEvent e) {
Point2D srcPt = new Point2D.Double(e.getX(),e.getY());
Point2D lastPt = new Point2D.Double(lx,ly);
try {
at.inverseTransform(srcPt,srcPt);
at.inverseTransform(lastPt,lastPt);
} catch (NoninvertibleTransformException noninvertibleTransformException) {
throw new RuntimeException(noninvertibleTransformException);
}
double dx = srcPt.getX() - lastPt.getX();
double dy = srcPt.getY() - lastPt.getY();
at.translate(dx,dy);
lx = e.getX();
ly = e.getY();
revalidate();
repaint();
}
public Point2D getSrcPoint(MouseEvent e) {
Point2D srcPt = new Point2D.Double(e.getX(),e.getY());
try {
at.inverseTransform(srcPt,srcPt);
} catch (NoninvertibleTransformException ex) {
throw new RuntimeException(ex);
}
return srcPt;
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
addMouseWheelListener(ma);
}
}
}
编辑:
经过更多的探索,无论出于何种原因,在 JDK 11 下,Graphics2D 的默认变换缩放为 2。
在这段代码中,如果你打印出默认的转换,在 JDK 11(在我的环境中)你会得到:
AffineTransform[[2.0,0.0,0.0],[0.0,2.0,0.0]]
在 JDK 8 上,您可以获得身份。
AffineTransform[[1.0,1.0,0.0]]
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
System.out.println(g2d.getTransform());
g2d.setTransform(at);
paintPanel(g2d);
g2d.dispose();
}
我最初通过使用我自己的原始 AffineTransform
来强制它识别。在 JDK 8 上,这是一个 nop,JDK 11,不是那么多。
我试过这个,我第一次将默认变换 (at
) 设置为图形上下文的“默认”(使用它的基本世界观与强制我自己的世界观)。这使网格具有适当的大小,但会干扰光标位置。
// I remove setting `at` up above,and setting it to identity also.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (at == null) {
at = g2d.getTransform();
}
g2d.setTransform(at);
paintPanel(g2d);
g2d.dispose();
}
所以这不是解决方案。
也许有一种方法可以通过利用上下文的默认转换来使其工作,但我还没有弄清楚。我必须假设默认转换是流畅的,不仅仅是 JDK 到 JDK,而是机器到机器。它甚至可能在多显示器情况下发生变化。
编辑:
继续。
因此,在屏幕上创建和显示的窗口是我在 Java 中要求的大小的 2 倍。我通过创建窗口的屏幕截图来检查这一点。
在 JDK 8 中,让 Java 中的 100 像素线在显示器上变成 200 像素线的机制在我的代码中并不明显。
在 JDK 11 中,可以看到他们试图通过直接使用 AffineTransform 将其向上移动。
类似地,在 JDK 8 中,鼠标坐标被缩放回 Java 坐标系。因此,如果您在屏幕上放置一个 400x400 的框并将鼠标从一个角移动到另一个角,即使该框的实际像素为 800x800,鼠标的范围也只是 0-400。
JDK 8 中的问题是由于 Transform,屏幕坐标不再与模型坐标匹配。当您绘制一个 400x400 的框时,在屏幕上绘制的是 400x400 乘以 2(由于变换)。但是鼠标坐标不在屏幕坐标中。原始鼠标在 0-400 范围内,而不是 0-800 范围内。因此,您不能再使用图形变换将鼠标坐标转换回模型坐标。幕后隐藏着恶作剧和阴谋诡计。简单地说,转换不是规范的。这就是为什么我的鼠标变得一团糟。
此外,我在另一台分辨率较低的 Mac 上运行了该示例,完全没有问题。 JDK 11 下的默认转换是身份。
解决方法
我没有看到 JDK 8 和 11 之间的任何区别,但问题是 Graphics2D
上下文已经转换为匹配屏幕配置,所以你需要做的地方 g2d.setTransform(at)
g2d.transform(at)
。然后一切都会如你所愿。 (我在 OS X 11.4 上使用 JDK 17)
所以 paintComponent
应该是(我还添加了一些日志记录):
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform t = g2d.getTransform();
System.out.println("Base G2D:" + t);
System.out.println("Ours:" + at);
g2d.transform(at);
System.out.println("Final:" + g2d.getTransform());
paintPanel(g2d);
g2d.dispose();
}
我已经在 OpenJDK Runtime Environment 18.9 (build 11.0.2+9)
和 Java(TM) SE Runtime Environment (build 1.8.0_201-b09)
上对此进行了测试。
我现在也用 11.0.11 进行了测试:
$ rm *.class
$ java -version
openjdk version "11.0.11" 2021-04-20
OpenJDK Runtime Environment AdoptOpenJDK-11.0.11+9 (build 11.0.11+9)
OpenJDK 64-Bit Server VM AdoptOpenJDK-11.0.11+9 (build 11.0.11+9,mixed mode)
$ javac TestPanel.java
$ java TestPanel
Base G2D:AffineTransform[[2.0,0.0,0.0],[0.0,2.0,0.0]]
Ours:AffineTransform[[1.0,1.0,0.0]]
Final:AffineTransform[[2.0,0.0]]
,
为了后代,我想发布最终代码,以防其他人想要一个工作示例,而不是试图将原始代码与讨论并列。
package pkg;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
public class TestPanel {
public static void main(String[] args) throws Exception {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame f = new JFrame("Test Zoom");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new BorderLayout());
TestPanelZoom p = new TestPanelZoom();
f.add(p,BorderLayout.CENTER);
f.setPreferredSize(new Dimension(400,500));
f.pack();
f.setVisible(true);
}
private static class TestPanelZoom extends JPanel {
boolean hilighted = false;
private int hiliteX = -1;
private int hiliteY = -1;
boolean selected = false;
private int selectX = -1;
private int selectY = -1;
private AffineTransform at = new AffineTransform();
public TestPanelZoom() {
setBackground(Color.WHITE);
setForeground(Color.BLACK);
addMouseAdapter();
}
@Override
public Dimension getPreferredSize() {
return new Dimension(400,500);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.transform(at);
paintPanel(g2d);
g2d.dispose();
}
private void paintPanel(Graphics2D g2) {
g2.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Color c = g2.getColor();
g2.setColor(Color.WHITE);
g2.fill(getBounds());
g2.setColor(c);
for (int i = 0; i < 400; i += 50) {
Line2D line = new Line2D.Double(i,i,350);
g2.draw(line);
line = new Line2D.Double(0,350,i);
g2.draw(line);
}
if (hilighted) {
Rectangle2D rect = new Rectangle2D.Double(hiliteX * 50 + 5,hiliteY * 50 + 5,40,40);
g2.setColor(Color.GREEN);
g2.fill(rect);
}
if (selected) {
Rectangle2D rect = new Rectangle2D.Double(selectX * 50 + 10,selectY * 50 + 10,30,30);
g2.setColor(Color.RED);
g2.fill(rect);
}
}
private void addMouseAdapter() {
MouseAdapter ma = new MouseAdapter() {
int lx,ly;
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
AffineTransform t = new AffineTransform();
double delta = 1 + 0.05f * e.getPreciseWheelRotation();
int x = e.getX();
int y = e.getY();
t.translate(x,y);
t.scale(delta,delta);
t.translate(-x,-y);
t.concatenate(at);
at = t;
revalidate();
repaint();
}
@Override
public void mousePressed(MouseEvent e) {
lx = e.getX();
ly = e.getY();
}
@Override
public void mouseDragged(MouseEvent e) {
update(e);
}
@Override
public void mouseReleased(MouseEvent e) {
update(e);
}
@Override
public void mouseClicked(MouseEvent e) {
Point2D srcPt = getSrcPoint(e);
selected = true;
selectX = (int) (srcPt.getX() / 50);
selectY = (int) (srcPt.getY() / 50);
revalidate();
repaint();
}
@Override
public void mouseMoved(MouseEvent e) {
Point2D srcPt = getSrcPoint(e);
hilighted = true;
hiliteX = (int) (srcPt.getX() / 50);
hiliteY = (int) (srcPt.getY() / 50);
revalidate();
repaint();
}
public void update(MouseEvent e) {
Point2D srcPt = new Point2D.Double(e.getX(),e.getY());
Point2D lastPt = new Point2D.Double(lx,ly);
try {
at.inverseTransform(srcPt,srcPt);
at.inverseTransform(lastPt,lastPt);
} catch (NoninvertibleTransformException noninvertibleTransformException) {
throw new RuntimeException(noninvertibleTransformException);
}
double dx = srcPt.getX() - lastPt.getX();
double dy = srcPt.getY() - lastPt.getY();
at.translate(dx,dy);
lx = e.getX();
ly = e.getY();
revalidate();
repaint();
}
public Point2D getSrcPoint(MouseEvent e) {
Point2D srcPt = new Point2D.Double(e.getX(),e.getY());
try {
at.inverseTransform(srcPt,srcPt);
} catch (NoninvertibleTransformException ex) {
throw new RuntimeException(ex);
}
return srcPt;
}
};
addMouseListener(ma);
addMouseMotionListener(ma);
addMouseWheelListener(ma);
}
}
}