绘制一个组件到BufferedImage导致显示损坏
我正在使用这里描述的JScrollNavigator
组件,以便将一个导航窗口提供到一个embeddedJScrollPane
的大型“canvas”CAD组件。
我试图使用JScrollNavigator
绘制canvas的缩略图来为用户提供一些额外的上下文。 但是,这样做的行为会导致我的应用程序主框架的渲染被破坏。 具体来说,它是在视口组件(即我的主canvaspaint(Graphics)
上调用paint(Graphics)
的动作,传递由BufferedImage
创build的Graphics
对象,导致后续的显示损坏; 如果我评论这一行,一切工作正常。
下面是JScrollNavigator
的重写paintComponent
方法:
@Override protected void paintComponent(Graphics g) { Component view = jScrollPane.getViewport().getView(); BufferedImage img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); // Paint JScrollPane view to off-screen image and then scale. // It is this action that causes the display corruption! view.paint(g2d); g2d.drawImage(img, 0, 0, null); Image scaled = img.getScaledInstance(getWidth(), getHeight(), 0); super.paintComponent(g); g.drawImage(scaled, 0, 0, null); }
有没有人对腐败的原因有什么build议? 我以为画到屏幕外的图像应该对现有的绘画操作没有影响。
编辑
提供一些额外的细节: JScrollNavigator
在JSplitPane
的左侧形成一个子面板。 与导航器关联的JScrollPane
位于右侧。 “腐败”导致分配器不再呈现和滚动条不可见(他们显示为白色)。 如果我调整JFrame
大小, JMenu
部分也会变成白色。 如果我尝试使用导航器或与滚动条互动,它们变得可见,但分离器保持白色。 就好像不同组件的不透明设置已经被视口视图渲染为离屏图像所影响。
另外,如果我让JScrollNavigator
出现在一个完全独立的JDialog
,一切正常。
编辑2
我可以通过执行以下操作来重现问题:
添加一个JMenuBar
到mFrame
:
JMenuBar bar = new JMenuBar(); bar.add(new JMenu("File")); mFrame.setJMenuBar(bar);
在JScrollNavigator
的main()
方法中,replace:
jsp.setViewportView(textArea);
…与:
jsp.setViewportView(new JPanel() { { setBackground(Color.GREEN); setBorder(BorderFactory.createLineBorder(Color.BLACK, 5)); } });
确保JScrollNavigator
作为面板embedded到mFrame
,而不是作为单独的JDialog
出现:
mFrame.add(jsp, BorderLayout.CENTER); mFrame.add(nav, BorderLayout.NORTH);
现在,当应用程序运行时, JMenuBar
不再可见 ; 将视图(即带有黑色边框的绿色JPanel
)绘制到由BufferedImage.createGraphics()
返回的Graphics2D
实际上似乎是在屏幕上 (可能是从JFrame的左上angularBufferedImage.createGraphics()
呈现它,从而遮挡了其他组件。 这似乎只发生在使用JPanel
作为视口视图时,而不是另一个组件,如JTextArea
, JTable
等。
编辑3
看起来像这个人有同样的问题(虽然没有解决scheme): http : //www.javaworld.com/community/node/2894/
编辑4
下面是导致编辑2中描述的可重现错误的main
和paintComponent
方法:
public static void main(String[] args) { JScrollPane jsp = new JScrollPane(); jsp.setViewportView(new JPanel() { { setBackground(Color.GREEN); setBorder(BorderFactory.createLineBorder(Color.BLACK, 5)); } }); JScrollNavigator nav = new JScrollNavigator(); nav.setJScrollPane(jsp); JFrame mFrame = new JFrame(); JMenuBar bar = new JMenuBar(); bar.add(new JMenu("File")); mFrame.setJMenuBar(bar); mFrame.setTitle("JScrollNavigator Test"); mFrame.setSize(800, 600); mFrame.setLayout(new GridLayout(1, 2)); mFrame.add(jsp); mFrame.add(nav); Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize(); mFrame.setLocation((screenDim.width - mFrame.getSize().width) / 2, (screenDim.height - mFrame.getSize().height) / 2); mFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); mFrame.setVisible(true); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Component view = jScrollPane.getViewport().getView(); if (img == null) { GraphicsConfiguration gfConf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB); } Graphics2D g2d = img.createGraphics(); view.paint(g2d); Image scaled = img.getScaledInstance(getWidth(), getHeight(), 0); g.drawImage(scaled, 0, 0, null); }
编辑5
看来其他人似乎无法重现确切的问题。 我会让人们运行粘贴在这里的代码。 当我第一次运行这个例子时,我看到以下内容:
JScrollNavigator或JMenuBar都没有画过; 这些框架区域是透明的。
resize后,我看到以下内容:
JMenuBar
仍然没有被绘制,看起来JPanel
是在(0,0)( JMenuBar
应该在的地方)渲染的。 paintComponent
的view.paint
调用是造成这种情况的直接原因。
简介:原始JScrollNavigator
使用Swing opacity
属性在相邻JScrollPane
中的组件的缩放缩略图上呈现方便的绿色NavBox
。 因为它扩展了JPanel
,所以(共享)UI委托使用与可滚动组件不相容的opacity
冲突。 在上面的编辑5中看到的图像代表了相关的渲染工件,也在这里显示。 解决方法是让NavBox
, JScrollNavigator
和可滚动组件扩展JComponent
,如下面第二个附录所build议的那样。 然后每个组件可以单独pipe理它自己的属性。
我发现在我的平台Mac OS X,Java 1.6上发布的代码没有不寻常的渲染效果。 对不起,我没有看到任何明显的可移植性违规。
一些可能无关紧要但可能有用的观察。
-
即使你在这种情况下使用了
setSize()
,你仍然应该pack()
封闭的Window
。f.pack(); f.setSize(300, 200);
-
为了方便起见,
add()
将组件转发到内容窗格。f.add(nav, BorderLayout.WEST);
-
首选
StringBuilder
到StringBuffer
。 -
考虑
ComponentAdapter
来代替ComponentListener
。
附录:正如这里所build议的,我使用RenderingHints
而不是getScaledInstance()
得到了更加灵活的结果,如下所示。 添加一些图标可以更容易地看到图像和文本上的不同效果。
editPane.insertIcon(UIManager.getIcon("OptionPane.errorIcon")); editPane.insertIcon(UIManager.getIcon("OptionPane.warningIcon")); ... @Override protected void paintComponent(Graphics g) { super.paintComponent(g); Component view = jScrollPane.getViewport().getView(); BufferedImage img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D off = img.createGraphics(); off.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); off.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); view.paint(off); Graphics2D on = (Graphics2D)g; on.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); on.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC); on.drawImage(img, 0, 0, getWidth(), getHeight(), null); }
附录secundum:它看起来像JPanel
UI委托不合作。 一种解决方法是扩展JComponent
以便可以控制不透明度 。 pipe理backgroundColor
只需稍微多一些工作。 NavBox
和JScrollNavigator
也是类似处理的候选对象。
jsp.setViewportView(new JComponent() { { setBackground(Color.red); setBorder(BorderFactory.createLineBorder(Color.BLACK, 16)); } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); g.setColor(getBackground()); g.fillRect(0, 0, getWidth(), getHeight()); } @Override public Dimension getPreferredSize() { return new Dimension(300, 300); } });
我也不确定你是什么意思的腐败,但我注意到,如果您指定Image.SCALE_SMOOTH作为重新调整提示重采样的图像更好:
Image scaled = img.getScaledInstance(getWidth(), getHeight(), Image.SCALE_SMOOTH);
也许这就是你要找的…
我能够重现你的问题,并得到你想要的结果。 问题在于,当您再次重新绘画时,图像的绘制并不完整,因此只有部分图像被绘制。 要解决此问题,请将此字段添加到JScrollNavigator
类(作为锁):
/** Lock to prevent trying to repaint too many times */ private boolean blockRepaint = false;
当我们重新绘制组件时,这个锁将被激活。 直到我们能够成功地绘制面板为止,它才会被释放,然后可以执行另一个绘画。
在绘制导航面板时,需要将paintComponent
更改为遵守locking并使用ImageObserver
。
@Override protected void paintComponent(final Graphics g) { super.paintComponent(g); if(!blockRepaint){ final Component view = (Component)jScrollPane.getViewport().getView(); BufferedImage img = new BufferedImage(view.getWidth(), view.getHeight(), BufferedImage.TYPE_INT_ARGB); Graphics2D g2d = img.createGraphics(); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // Paint JScrollPane view to off-screen image and then scale. // It is this action that causes the display corruption! view.paint(g2d); ImageObserver io = new ImageObserver() { @Override public boolean imageUpdate(Image img, int infoflags, int x, int y,int width, int height) { boolean result = true; g.drawImage(img, 0, 0, null); if((infoflags & ImageObserver.FRAMEBITS) == ImageObserver.FRAMEBITS){ blockRepaint = false; result = false; } return result; } }; Image scaled = img.getScaledInstance(getWidth(), getHeight(), 0); blockRepaint = g.drawImage(scaled, 0, 0, io); } }