2010-05-25 57 views
5

我有一个运行在32位Windows 2008 Server上的Java(Swing)应用程序,它需要将其输出呈现为离屏图像(然后由另一个C++申请在其他地方渲染)。大多数组件都能正确渲染,除非奇怪的情况下,刚刚失去焦点的组件被另一个组件遮挡,例如,如果两个JComboBox接近,则用户与较低组件互动,然后单击上面的那个,所以它的下拉与另一个盒子重叠。将Swing组件渲染到离线缓冲区

在这种情况下,失去焦点的组件会在其遮挡后呈现,因此会出现在输出顶部。它在正常的Java显示中正常呈现(在主显示器上全屏运行),并尝试更改有问题的组件的层不起作用。

我正在使用自定义RepaintManager将组件绘制到屏幕外图像上,并且我认为问题在于为每个有问题的组件调用addDirtyRegion()的顺序,但我想不出一种确定这种特定状态何时发生以预防它的好方法。对其进行黑客攻击,使刚刚失去焦点的对象不会重新粉刷,从而可以解决问题,但显然会导致更大的问题,即在所有其他正常情况下都不会重新绘制该问题。

有没有任何方式来编程识别这种状态,或重新排序的事情,以便它不会发生?

非常感谢,

尼克

[编辑] 添加了一些代码作为一个例子:

重绘管理器和相关的类:

class NativeObject { 
    private long nativeAddress = -1; 

    protected void setNativeAddress(long address) { 
     if (nativeAddress != -1) { 
      throw new IllegalStateException("native address already set for " + this); 
     } 
     this.nativeAddress = address; 
     NativeObjectManager.getInstance().registerNativeObject(this, nativeAddress); 
    } 
} 

public class MemoryMappedFile extends NativeObject { 
    private ByteBuffer buffer; 

    public MemoryMappedFile(String name, int size) 
    { 
     setNativeAddress(create(name, size)); 
     buffer = getNativeBuffer(); 
    } 

    private native long create(String name, int size); 

    private native ByteBuffer getNativeBuffer(); 

    public native void lock(); 

    public native void unlock(); 

    public ByteBuffer getBuffer() { 
     return buffer; 
    } 
} 

private static class CustomRepaintManager extends RepaintManager{  
    class PaintLog { 
     Rectangle bounds; 
     Component component; 
     Window window; 

     PaintLog(int x, int y, int w, int h, Component c) { 
      bounds = new Rectangle(x, y, w, h); 
      this.component = c; 
     } 

     PaintLog(int x, int y, int w, int h, Window win) { 
      bounds = new Rectangle(x, y, w, h); 
      this.window= win; 
     } 
    } 

    private MemoryMappedFile memoryMappedFile; 
    private BufferedImage offscreenImage; 
    private List<PaintLog> regions = new LinkedList<PaintLog>(); 
    private final Component contentPane; 
    private Component lastFocusOwner; 
    private Runnable sharedMemoryUpdater; 
    private final IMetadataSource metadataSource; 
    private Graphics2D offscreenGraphics; 
    private Rectangle offscreenBounds = new Rectangle(); 
    private Rectangle repaintBounds = new Rectangle(); 

    public CustomRepaintManager(Component contentPane, IMetadataSource metadataSource) { 
     this.contentPane = contentPane; 
     this.metadataSource = metadataSource; 
     offscreenBounds = new Rectangle(0, 0, 1920, 1080); 
     memoryMappedFile = new MemoryMappedFile("SystemConfigImage", offscreenBounds.width * offscreenBounds.height * 3 + 1024); 
     offscreenImage = new BufferedImage(offscreenBounds.width, offscreenBounds.height, BufferedImage.TYPE_3BYTE_BGR); 
     offscreenGraphics = offscreenImage.createGraphics(); 

     sharedMemoryUpdater = new Runnable(){ 
      @Override 
      public void run() 
      { 
       updateSharedMemory(); 
      } 
     }; 
    } 

    private boolean getLocationRelativeToContentPane(Component c, Point screen) { 
     if(!c.isVisible()) { 
      return false; 
     } 

     if(c == contentPane) { 
      return true; 
     } 

     Container parent = c.getParent(); 
     if(parent == null) { 
      System.out.println("can't get parent!"); 
      return true; 
     } 

     if(!parent.isVisible()) { 
      return false; 
     } 

     while (!parent.equals(contentPane)) { 
      screen.x += parent.getX(); 
      screen.y += parent.getY(); 
      parent = parent.getParent(); 

      if(parent == null) { 
       System.out.println("can't get parent!"); 
       return true; 
      } 
      if(!parent.isVisible()) { 
       return false; 
      } 
     } 
     return true; 
    } 

    protected void updateSharedMemory() { 
     if (regions.isEmpty()) return; 

     List<PaintLog> regionsCopy = new LinkedList<PaintLog>(); 

     synchronized (regions) { 
      regionsCopy.addAll(regions); 
      regions.clear(); 
     } 

     memoryMappedFile.lock(); 
     ByteBuffer mappedBuffer = memoryMappedFile.getBuffer(); 
     int imageDataSize = offscreenImage.getWidth() * offscreenImage.getHeight() * 3; 
     mappedBuffer.position(imageDataSize); 

     if (mappedBuffer.getInt() == 0) { 
      repaintBounds.setBounds(0, 0, 0, 0); 
     } else { 
      repaintBounds.x = mappedBuffer.getInt(); 
      repaintBounds.y = mappedBuffer.getInt(); 
      repaintBounds.width = mappedBuffer.getInt(); 
      repaintBounds.height = mappedBuffer.getInt(); 
     } 

     for (PaintLog region : regionsCopy) { 
      if (region.component != null && region.bounds.width > 0 && region.bounds.height > 0) { 
       Point regionLocation = new Point(region.bounds.x, region.bounds.y); 
       Point screenLocation = region.component.getLocation(); 
       boolean isVisible = getLocationRelativeToContentPane(region.component, screenLocation); 

       if(!isVisible) { 
        continue; 
       } 

       if(region.bounds.x != 0 && screenLocation.x == 0 || region.bounds.y != 0 && screenLocation.y == 0){ 
        region.bounds.width += region.bounds.x; 
        region.bounds.height += region.bounds.y; 
       } 

       Rectangle2D.intersect(region.bounds, offscreenBounds, region.bounds); 

       if (repaintBounds.isEmpty()){ 
        repaintBounds.setBounds(screenLocation.x, screenLocation.y, region.bounds.width, region.bounds.height); 
       } else { 
        Rectangle2D.union(repaintBounds, new Rectangle(screenLocation.x, screenLocation.y, region.bounds.width, region.bounds.height), repaintBounds); 
       } 

       offscreenGraphics.translate(screenLocation.x, screenLocation.y); 

       region.component.paint(offscreenGraphics); 

       DataBufferByte byteBuffer = (DataBufferByte) offscreenImage.getData().getDataBuffer(); 
       int srcIndex = (screenLocation.x + screenLocation.y * offscreenImage.getWidth()) * 3; 
       byte[] srcData = byteBuffer.getData(); 

       int maxY = Math.min(screenLocation.y + region.bounds.height, offscreenImage.getHeight()); 
       int regionLineSize = region.bounds.width * 3; 

       for (int y = screenLocation.y; y < maxY; ++y){ 
        mappedBuffer.position(srcIndex); 

        if (srcIndex + regionLineSize > srcData.length) { 
         break; 
        } 
        if (srcIndex + regionLineSize > mappedBuffer.capacity()) { 
         break; 
        } 
        try { 
         mappedBuffer.put(srcData, srcIndex, regionLineSize); 
        } 
        catch (IndexOutOfBoundsException e) { 
         break; 
        } 
        srcIndex += 3 * offscreenImage.getWidth(); 
       } 

       offscreenGraphics.translate(-screenLocation.x, -screenLocation.y); 
       offscreenGraphics.setClip(null); 

      } else if (region.window != null){  
       repaintBounds.setBounds(0, 0, offscreenImage.getWidth(), offscreenImage.getHeight()); 

       offscreenGraphics.setClip(repaintBounds); 

       contentPane.paint(offscreenGraphics); 

       DataBufferByte byteBuffer = (DataBufferByte) offscreenImage.getData().getDataBuffer(); 
       mappedBuffer.position(0); 
       mappedBuffer.put(byteBuffer.getData()); 
      } 
     } 

     mappedBuffer.position(imageDataSize); 
     mappedBuffer.putInt(repaintBounds.isEmpty() ? 0 : 1); 
     mappedBuffer.putInt(repaintBounds.x); 
     mappedBuffer.putInt(repaintBounds.y); 
     mappedBuffer.putInt(repaintBounds.width); 
     mappedBuffer.putInt(repaintBounds.height); 
     metadataSource.writeMetadata(mappedBuffer); 

     memoryMappedFile.unlock(); 
    } 

    @Override 
    public void addDirtyRegion(JComponent c, int x, int y, int w, int h) { 
     super.addDirtyRegion(c, x, y, w, h); 
     synchronized (regions) { 
      regions.add(new PaintLog(x, y, w, h, c)); 
     } 
     SwingUtilities.invokeLater(sharedMemoryUpdater); 
    } 

    @Override 
    public void addDirtyRegion(Window window, int x, int y, int w, int h) { 
     super.addDirtyRegion(window, x, y, w, h); 
     synchronized (regions) {  
      regions.add(new PaintLog(x, y, w, h, window)); 
     } 
     SwingUtilities.invokeLater(sharedMemoryUpdater); 
    } 
} 

小组是具有问题:

private static class EncodingParametersPanel extends JPanel implements ActionListener 
{ 
    private JLabel label1 = new JLabel(); 
    private JComboBox comboBox1 = new JComboBox(); 

    private JLabel label2 = new JLabel(); 
    private JComboBox comboBox2 = new JComboBox(); 

    private JLabel label3 = new JLabel(); 
    private JComboBox comboBox3 = new JComboBox(); 

    private JButton setButton = new JButton(); 

    public EncodingParametersPanel() 
    { 
     super(new BorderLayout()); 

     JPanel contentPanel = new JPanel(new VerticalFlowLayout()); 
     JPanel formatPanel = new JPanel(new VerticalFlowLayout()); 

     sdiFormatPanel.setBorder(BorderFactory.createTitledBorder(BorderFactory.createLoweredBevelBorder(), "Format")); 

     label1.setText("First Option:"); 
     label2.setText("Second Option:"); 
     label3.setText("Third OPtion:"); 

     setButton.addActionListener(this); 

     formatPanel.add(label1); 
     formatPanel.add(comboBox1); 
     formatPanel.add(label2); 
     formatPanel.add(comboBox2); 
     formatPanel.add(label3); 
     formatPanel.add(comboBox3); 

     contentPanel.add(formatPanel); 

     contentPanel.add(setButton); 

     add(contentPanel); 
    } 
} 

使用此示例,如果用户与comboBox2交互,则使用comboBox1,comboBox1的下拉与comboBox2重叠,但comboBox2在其上重新绘制。

+2

当您使用默认的RepaintManager时会发生什么?另外,我不明白你是如何开始渲染组件的。例如,如果我有一个打开的组合框并单击一个按钮来启动某个动作,组合框将关闭,所以我不知道如何重现您描述的情况。我建议帮助张贴您的SSCCE(http://sscce.org)来证明问题。 – camickr 2010-05-25 20:54:15

+0

不幸的是,我不能使用默认的重绘管理器,因为它还必须为C++应用程序编写一些元数据以供使用,例如,识别屏幕图像中的脏区域,以便重绘C++应用程序。 渲染是通过标准方式启动的,我在主JFrame中替换的所有内容都是RepaintManager,因此就我所知,每个组件在变为无效时都应该导致重绘。 我不确定我可以发布一个完整的例子,因为涉及的本地代码的性质,但我会把它的Java一面。 – 2010-05-26 09:06:41

+0

在updateSharedMemory的for循环中,大的if-else链没有最后的else。尝试添加,看看是否有你不处理的区域。 – 2010-05-27 20:52:45

回答

0

我发现了一些可能有助于您看到的东西。

updateSharedMemory处理窗口重绘的代码中,代码调用contentPane.paint。由于Window可能不是您的contentPane,因此这是最有可能的罪魁祸首。 JPopupMenu(由JComboBox使用)的代码可以选择将弹出框显示为重量级组件。所以,该窗口可以是其中一个JComboBoxes的弹出窗口。

此外,sharedMemoryUpdater计划在EDT将在事件队列为空时运行。因此,在调用addDirtyRegion和调用updateSharedMemory时可能会有一段时间滞后。在updateSharedMemory有一个电话region.component.paint。如果任何已排队的事件更改为component,则绘图调用的实际结果可能会有所不同。

一些建议,测试的结果:

创建sharedMemoryUpdater这样的:

private Runnable scheduled = null; 

    sharedMemoryUpdater = Runnable { 
     public void run() { 
      scheduled = null; 
      updateSharedMemory(); 
     } 
    } 

然后,在addDirtyRegion

if (scheduled == null) { 
     scheduled = sharedMemoryUpdater; 
     SwingUtilities.invokeLater(sharedMemoryUpdater); 
    } 

这将减少sharedMemoryUpdater调用次数(由99%在我的测试中)。由于所有对addDirtyRegion的调用都应该在EDT上进行,因此您不需要在scheduled上进行同步,但添加不会造成太大的影响。

由于存在延迟,因此要处理的区域数量可能会变得很大。在我的测试中,我看到它在一个点上超过了400。

这些更改将减少操作区域列表的时间,因为创建1个新列表比创建复制现有列表所需的所有条目要快。

private final Object regionLock = new Opject; 
private List<PaintLog> regions = new LinkedList<PaintLog>(); 

// In addDirtyRegions() 
synchronized(regionLock) { 
    regions.add(...); 
} 

// In updateSharedMemory() 
List<PaintLog> regionsCopy; 
List<PaintLog> tmp = new LinkedList<PaintLog>() 
synchronized(regionLock) { 
    regionsCopy = regions; 
    regions = tmp; 
} 
+0

谢谢你。它确实似乎使它更有效地运行,但并不能解决问题。事实上,当我仔细观察它时,看起来被遮挡的组件正在被单独重新绘制在菜单上,即完整的菜单被绘制,然后其他组件被绘制在其上 – 2010-06-04 10:26:39

0

我做了一个假设:你的应用程序运行在真实的图形环境中(即不是无头的)。

我想你可能想利用java.awt.Robot,它被设计用来模仿使用AWT/Swing应用程序的用户。它可以做一些事情,如模拟按键,鼠标点击,它可以截屏!该方法是createScreenCapture(Rectangle),它返回一个BufferedImage,这对大多数用例来说应该是完美的。

下面是一个示例,其中包含了一些相互重叠的垂直JComboBoxES。打开其中一个并按下F1会截屏并在下面的预览面板中显示它。

import java.awt.AWTException; 
import java.awt.BorderLayout; 
import java.awt.GridLayout; 
import java.awt.Rectangle; 
import java.awt.Robot; 
import java.awt.event.ActionEvent; 
import java.awt.event.KeyEvent; 
import java.awt.image.BufferedImage; 

import javax.swing.AbstractAction; 
import javax.swing.Action; 
import javax.swing.ImageIcon; 
import javax.swing.JComboBox; 
import javax.swing.JComponent; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.JOptionPane; 
import javax.swing.JPanel; 
import javax.swing.KeyStroke; 
import javax.swing.SwingUtilities; 
import javax.swing.border.TitledBorder; 

public class ScreenshotTester { 
    public static void main(String[] args) { 
     SwingUtilities.invokeLater(new Runnable() { 
      @Override 
      public void run() { 
       final JFrame f = new JFrame("Screenshot Tester"); 
       f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

       f.setLayout(new BorderLayout(10, 10)); 

       final JPanel preview = new JPanel(); 
       preview.setBorder(new TitledBorder("Screenshot")); 
       f.add(preview, BorderLayout.CENTER); 

       final JPanel testPanel = new JPanel(new GridLayout(3, 1)); 
       testPanel.add(new JComboBox(new String[] { "a", "b" })); 
       testPanel.add(new JComboBox(new String[] { "c", "d" })); 
       testPanel.add(new JComboBox(new String[] { "e", "f" })); 
       f.add(testPanel, BorderLayout.NORTH); 

       Action screenshotAction = new AbstractAction("Screenshot") { 
        @Override 
        public void actionPerformed(ActionEvent ev) { 
         try { 
          Rectangle region = f.getBounds(); 
          BufferedImage img = new Robot().createScreenCapture(region); 
          preview.removeAll(); 
          preview.add(new JLabel(new ImageIcon(img))); 
          f.pack(); 
         } catch (AWTException e) { 
          JOptionPane.showMessageDialog(f, e); 
         } 
        } 
       }; 

       f.getRootPane().getActionMap().put(screenshotAction, screenshotAction); 
       f.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
         KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0), screenshotAction); 
       f.pack(); 
       f.setLocationRelativeTo(null); 
       f.setVisible(true); 
      } 
     }); 
    } 

} 

你应该能够看到整个窗口,包括窗口装饰,和你看到它在屏幕上的组合框的菜单上应该正好出现在另一组合框顶部。

相关问题