2010-05-25 58 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); 
      } 
     }); 
    } 

} 

你應該能夠看到整個窗口,包括窗口裝飾,和你看到它在屏幕上的組合框的菜單上應該正好出現在另一組合框頂部。

相關問題