2010-05-25 27 views
5

Tôi có ứng dụng Java (Swing), chạy trên Máy chủ Windows 2008 32 bit, cần kết xuất đầu ra thành hình ảnh ngoài màn hình (sau đó được chọn bởi C++ khác ứng dụng để hiển thị ở nơi khác). Hầu hết các thành phần hiển thị chính xác, ngoại trừ trong trường hợp kỳ lạ khi một thành phần vừa bị mất tiêu điểm bị bao quanh bởi một thành phần khác, ví dụ như có hai JComboBox gần nhau, nếu người dùng tương tác với một thành phần thấp hơn, sau đó nhấp vào phía trên để nó kéo xuống chồng chéo lên hộp khác.Hiển thị thành phần Swing vào bộ đệm ngoài màn hình

Trong trường hợp này, thành phần bị mất tiêu điểm được hiển thị sau khi một thành phần bị che khuất và xuất hiện trên đầu trang ở đầu ra. Nó hiển thị chính xác trong màn hình Java bình thường (chạy toàn màn hình trên màn hình chính), và cố gắng thay đổi các lớp của các thành phần được đề cập sẽ không giúp ích gì.

Tôi đang sử dụng Trình quản lý lại tùy chỉnh để vẽ các thành phần vào hình ảnh ngoài màn hình và tôi giả định vấn đề nằm trong thứ tự mà addDirtyRegion() được gọi cho từng thành phần được đề cập, nhưng tôi không thể nghĩ một cách tốt để xác định khi nào trạng thái cụ thể này xảy ra để ngăn chặn nó. Hacking nó để các đối tượng mà chỉ mất tập trung không được sơn lại dừng vấn đề, nhưng rõ ràng là nguyên nhân vấn đề lớn hơn mà nó không được sơn lại trong tất cả các khác, bình thường, hoàn cảnh.

Có cách nào để lập trình xác định trạng thái này hoặc sắp xếp lại các thứ để nó không xảy ra không?

Rất cám ơn,

Nick

[sửa] gia tăng một số mã như một ví dụ:

quản lý tô màu lại

và các lớp học liên quan:

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); 
    } 
} 

Các Ban Hội thẩm rằng đang có sự cố:

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); 
    } 
} 

Sử dụng ví dụ này, nếu người dùng tương tác với comboBox2, sau đó với comboBox1, kéo xuống từ comboBox1 chồng chéo comboBox2, nhưng comboBox2 được vẽ lại trên đầu trang của nó.

+2

Điều gì xảy ra khi bạn sử dụng Trình quản lý lại mặc định? Ngoài ra, tôi không hiểu cách bạn bắt đầu hiển thị các thành phần. Ví dụ nếu tôi có một hộp combo mở và bấm vào một nút để bắt đầu một số hành động, hộp kết hợp đóng lại, vì vậy tôi không biết làm thế nào để tái tạo tình hình mà bạn mô tả. Tôi đề nghị giúp đỡ đăng SSCCE của bạn (http://sscce.org) để chứng minh vấn đề. – camickr

+0

Rất tiếc, tôi không thể sử dụng trình quản lý đăng ký mặc định vì nó cũng phải viết một số siêu dữ liệu cho ứng dụng C++ để sử dụng, ví dụ: xác định các vùng bẩn trong hình ảnh ngoài màn hình cho ứng dụng C++ để vẽ lại. Hiển thị được khởi tạo bằng các phương tiện chuẩn, tất cả những gì tôi đã thay thế trong JFrame chính là RepaintManager, vì vậy theo như tôi hiểu, mỗi thành phần sẽ gây ra một sự tái xuất hiện khi nó không hợp lệ? Tôi không chắc chắn tôi có thể đăng một ví dụ đầy đủ do bản chất của mã gốc có liên quan, nhưng tôi sẽ đưa lên phía java của nó. –

+0

Trong vòng lặp for trong bản cập nhậtSharedMemory, chuỗi if-else lớn không có final. Hãy thử thêm điều đó và xem liệu có khu vực nào bạn không xử lý hay không. –

Trả lời

0

Tôi đã tìm thấy một vài thứ có thể đóng góp vào những gì bạn đang thấy.

Trong mã updateSharedMemory để xử lý bản sao của Cửa sổ, mã gọi contentPane.paint. Đây là thủ phạm có khả năng nhất vì Cửa sổ có thể không phải là contentPane của bạn. Mã cho JPopupMenu (được JComboBox sử dụng) có thể chọn hiển thị cửa sổ bật lên dưới dạng thành phần hạng nặng. Vì vậy, cửa sổ có thể là cửa sổ bật lên của một trong những JComboBoxes.

Ngoài ra, sharedMemoryUpdater được lên lịch trên EDT nơi nó sẽ chạy khi hàng đợi sự kiện trống. Vì vậy, có thể có độ trễ giữa khi số addDirtyRegion được gọi và khi gọi updateSharedMemory. Trong số updateSharedMemory, hãy gọi tới số region.component.paint. Nếu bất kỳ sự kiện đã xếp hàng nào thay đổi component, kết quả thực tế của cuộc gọi sơn có thể thay đổi.

Một số gợi ý như là kết quả của thử nghiệm:

Tạo sharedMemoryUpdater như thế này:

private Runnable scheduled = null; 

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

Sau đó, trong addDirtyRegion

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

Điều đó sẽ giảm số lượng các lời gọi của sharedMemoryUpdater (bởi 99% trong thử nghiệm của tôi). Vì tất cả các cuộc gọi đến addDirtyRegion sẽ diễn ra trên EDT, bạn không cần đồng bộ hóa trên scheduled, nhưng việc thêm sẽ không gây tổn hại nhiều.

Vì có độ trễ, số vùng cần xử lý có thể trở nên khá lớn. Trong các thử nghiệm của tôi, tôi thấy nó vượt quá 400 tại một điểm.

Những thay đổi này sẽ cắt giảm thời gian thao tác trong danh sách khu vực vì tạo danh sách mới nhanh hơn tạo tất cả các mục cần thiết để sao chép danh sách hiện có.

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

Cảm ơn vì điều đó. Nó xuất hiện để làm cho nó chạy hiệu quả hơn, nhưng không giải quyết được vấn đề. Trong thực tế, khi tôi xem nó chặt chẽ, có vẻ như thành phần bị tách biệt đang được vẽ lại riêng biệt trên menu, tức là –

0

Tôi đang tạo một giả định: ứng dụng của bạn đang chạy trong môi trường đồ họa thực (tức là không đầu).

Tôi nghĩ bạn có thể muốn tận dụng lợi thế của java.awt.Robot được thiết kế để bắt chước người dùng bằng ứng dụng AWT/Swing. Nó có thể làm những việc như mô phỏng nhấn phím, nhấp chuột và có thể chụp ảnh màn hình! Phương pháp này là createScreenCapture(Rectangle) và phương thức trả về là BufferedImage cần hoàn hảo cho hầu hết các trường hợp sử dụng.

Đây là một ví dụ, trong đó tôi đã bao gồm một số dọc JComboBox es chồng lên nhau. Mở một trong số đó và nhấn F1 sẽ chụp ảnh màn hình và hiển thị nó trong bảng xem trước bên dưới.

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); 
      } 
     }); 
    } 

} 

Bạn có thể xem toàn bộ cửa sổ bao gồm trang trí cửa sổ và menu hộp kết hợp sẽ xuất hiện trên đầu các hộp kết hợp khác chính xác như bạn thấy trên màn hình.

Các vấn đề liên quan