【发布时间】:2011-08-26 09:37:09
【问题描述】:
我有一个扩展 JPanel 的类(如下),这个面板位于 JScrollPane 内。它侦听(自己)鼠标事件并尝试重新定位自己(在拖动时)并重新调整自己(在滚轮上)以模拟鼠标移动和缩放。该面板还负责我的应用程序的主要视觉输出。它存储在 JScrollPane 的可视区域(但在面板的图形上)呈现的 BufferedImage。图像的大小和形状保持与可视区域相匹配。
我的问题就是这样的;
1) 在鼠标事件中,我得到大量的闪烁和性能下降 2)如果我用自己的绘画方法覆盖paint或paintComponent方法,这对于摆脱闪烁和其他绘画问题是可取的,我仍然会得到相同的闪烁效果和从加载的图像中绘制的图形,这些图像具有透明区域然后着色黑色区域。当我手动调用我的paint方法而不覆盖paint和paintComponent方法时,我仍然会闪烁,但透明区域可以正常显示。
我是 Swing 绘画的新手,显然做错了什么,谁能指出我正确的方向来解决这个问题?
谢谢
import jSim.simulation.Simulation;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JPanel;
import javax.swing.JViewport;
import javax.swing.event.MouseInputListener;
public class SimPanel extends JPanel implements MouseWheelListener, MouseInputListener {
//Simulation
Simulation sim;
//Viewer
JViewport viewport;
Dimension viewSize;
BufferStrategy strat;
//Drawing
Image renderImage;
Graphics2D g2d;
boolean draw = true;
double scale = 1.0;
Object drawLock = new Object();
//Mouse events
int m_XDifference, m_YDifference;
public SimPanel(JViewport viewport) {
this.viewport = viewport;
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.addMouseWheelListener(this);
//this.setup();
}
public SimPanel(Simulation sim, JViewport viewport) {
this.sim = sim;
this.viewport = viewport;
this.addMouseListener(this);
this.addMouseMotionListener(this);
this.addMouseWheelListener(this);
//this.setup();
}
//Used to initialise the buffered image once drawing begins
private void setup() {
synchronized (drawLock) {
viewSize = viewport.getExtentSize();
renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
g2d = (Graphics2D) renderImage.getGraphics();
}
}
// @Override
// public void paint(Graphics g)
// {
// synchronized(drawLock) {
// //super.paintComponent(g);
// paintSimulation();
// }
// }
//Paint the screen for a specific simulation
public void paintSimulation(Simulation sim) {
synchronized (drawLock) {
setSimulation(sim);
paintSimulation();
}
}
//Paint the screen with the panels simulation
public void paintSimulation() {
synchronized (drawLock) {
//if no image, then init
if (renderImage == null) {
setup();
}
//clear the screen
resetScreen();
//draw the simulation if not null, to the image
if (sim != null) {
sim.draw(this);
}
//paint the screen with the image
paintScreen();
}
}
private void resetScreen() {
Dimension newSize = viewport.getExtentSize();
if (viewSize.height != newSize.height || viewSize.width != newSize.width || renderImage == null) {
//System.out.println("Screen Size Changed: " + viewSize + " " + newSize);
viewSize = newSize;
renderImage = new BufferedImage(viewSize.width, viewSize.height, BufferedImage.TYPE_INT_RGB);
g2d = (Graphics2D) renderImage.getGraphics();
} else {
g2d.setBackground(Color.DARK_GRAY);
g2d.clearRect(0, 0, (int) (viewSize.width), (int) (viewSize.height));
}
}
private void paintScreen() {
Graphics g;
Graphics2D g2;
try {
//g = viewport.getGraphics();
g = this.getGraphics();
g2 = (Graphics2D) g;
if ((g != null) && (renderImage != null)) {
g2.drawImage(renderImage, (int) viewport.getViewPosition().getX(), (int) viewport.getViewPosition().getY(), null);
}
Toolkit.getDefaultToolkit().sync(); // sync the display on some systems
g.dispose();
g2.dispose();
this.revalidate();
} catch (Exception e) {
System.out.println("Graphics context error: " + e);
}
}
//Simulation makes calls to this method to draw items on the image
public void draw(BufferedImage image, int x, int y, Color colour) {
synchronized (drawLock) {
Rectangle r = viewport.getViewRect();
if (g2d != null && draw) {
Point p = new Point((int) (x * scale), (int) (y * scale));
if (r.contains(p)) {
if (scale < 1) {
Graphics2D g2 = (Graphics2D) image.getGraphics();
Image test = image.getScaledInstance((int) (image.getWidth(null) * scale), (int) (image.getHeight(null) * scale), Image.SCALE_FAST);
g2d.drawImage(test, (int) ((x * scale - r.x)), (int) ((y * scale - r.y)), null);
} else {
g2d.drawImage(image, x - r.x, y - r.y, null);
}
}
}
}
}
public void setDraw(boolean draw) {
this.draw = draw;
}
public void setSimulation(Simulation sim) {
synchronized (drawLock) {
if (!(this.sim == sim)) {
this.sim = sim;
}
}
}
public void mouseWheelMoved(MouseWheelEvent e) {
synchronized (drawLock) {
updatePreferredSize(e.getWheelRotation(), e.getPoint());
}
}
private void updatePreferredSize(int wheelRotation, Point stablePoint) {
double scaleFactor = findScaleFactor(wheelRotation);
if (scale * scaleFactor < 1 && scale * scaleFactor > 0.05) {
scaleBy(scaleFactor);
Point offset = findOffset(stablePoint, scaleFactor);
offsetBy(offset);
this.getParent().doLayout();
}
}
private double findScaleFactor(int wheelRotation) {
double d = wheelRotation * 1.08;
return (d > 0) ? 1 / d : -d;
}
private void scaleBy(double scaleFactor) {
int w = (int) (this.getWidth() * scaleFactor);
int h = (int) (this.getHeight() * scaleFactor);
this.setPreferredSize(new Dimension(w, h));
this.scale = this.scale * scaleFactor;
}
private Point findOffset(Point stablePoint, double scaleFactor) {
int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
return new Point(x, y);
}
private void offsetBy(Point offset) {
Point location = viewport.getViewPosition();
//this.setLocation(location.x - offset.x, location.y - offset.y);
viewport.setViewPosition(new Point(location.x + offset.x, location.y + offset.y));
}
public void mouseDragged(MouseEvent e) {
synchronized (drawLock) {
//Point p = this.getLocation();
Point p = viewport.getViewPosition();
int newX = p.x - (e.getX() - m_XDifference);
int newY = p.y - (e.getY() - m_YDifference);
//this.setLocation(newX, newY);
viewport.setViewPosition(new Point(newX, newY));
//this.getParent().doLayout();
}
}
public void mousePressed(MouseEvent e) {
synchronized (drawLock) {
setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
m_XDifference = e.getX();
m_YDifference = e.getY();
}
}
public void mouseReleased(MouseEvent e) {
synchronized (drawLock) {
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
}
public void mouseClicked(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet.");
}
public void mouseEntered(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet.");
}
public void mouseExited(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet.");
}
public void mouseMoved(MouseEvent e) {
//throw new UnsupportedOperationException("Not supported yet.");
}
}
【问题讨论】:
-
为什么要同步所有方法?我已经使用 Swing 多年,从来不需要这样做。
-
对不起,我添加了这些只是为了看看在尝试了很多其他的东西之后是否有任何不同而忘了把它们拿出来。我只是想检查一下没有什么愚蠢的事情发生,比如调整面板中画或类似的大小。
-
Swing 绘制发生在 EDT - 事件调度线程上。在绘制完成之前不会处理“调整面板大小”事件,因为它们都在同一个线程上处理。这就是为什么如果一个事件导致一个较长的进程,你应该在另一个线程中运行那个进程,这样你的 Swing UI 仍然可以响应并重新绘制自己。
-
您认为在这种情况下将鼠标侦听器移到此类之外会有所帮助吗?我不认为这将是一个漫长的过程。我认为运行时间最长的过程是调用 sim.draw() 因为它负责将所有图形实际绘制到缓冲区图像。但是,当没有注册鼠标事件(即面板没有调整大小或移动)时,显示运行平稳,也许这是有道理的。