2010-05-25 22 views
5

Tengo una aplicación Java (Swing), que se ejecuta en un servidor Windows 2008 de 32 bits, que necesita mostrar su salida a una imagen fuera de pantalla (que luego es captada por otro C++ aplicación para renderizar en otro lugar). La mayoría de los componentes se procesan correctamente, excepto en el caso extraño donde un componente que acaba de perder el enfoque está ocluido por otro componente, por ejemplo, donde hay dos JComboBoxes cerca uno del otro, si el usuario interactúa con el inferior y luego hace clic el superior, así que es desplegable se superpone a la otra caja.Representación de componentes Swing a un buffer sin pantalla

En esta situación, el componente que ha perdido el foco se representa después del que lo ocluyó, y así aparece en la parte superior de la salida. Se procesa correctamente en la pantalla Java normal (se ejecuta en pantalla completa en la pantalla principal), y el intento de cambiar las capas de los componentes en cuestión no ayuda.

Estoy usando un RepaintManager personalizado para pintar los componentes a la imagen fuera de pantalla, y supongo que el problema radica en el orden en el que addDirtyRegion() se llama para cada uno de los componentes en cuestión, pero no puedo pensar en una buena forma de identificar cuándo ocurre este estado particular para prevenirlo. Cortarlo para que el objeto que acaba de perder el foco no se vuelva a pintar detiene el problema, pero obviamente causa el problema mayor de que no se repinta en todas las demás circunstancias normales.

¿Hay alguna forma de identificar mediante programación este estado, o de reordenar cosas para que no ocurra?

Muchas gracias,

Nick

[editar] añadido algo de código como ejemplo:

gestor de repintado y clases asociadas:

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

El Grupo Especial de que está teniendo el problemas:

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

Usando este ejemplo, si el usuario interactúa con comboBox2, entonces con comboBox1, el menú desplegable de comboBox1 se superpone comboBox2, pero comboBox2 se vuelve a dibujar encima de él.

+2

¿Qué sucede cuando usa el RepaintManager predeterminado? Además, no entiendo cómo se inicia la representación de los componentes. Por ejemplo, si tengo un cuadro combinado abierto y hago clic en un botón para iniciar alguna acción, el cuadro combinado se cierra, por lo que no sé cómo reproducir la situación que describes. Sugiero ayuda para publicar su SSCCE (http://sscce.org) para demostrar el problema. – camickr

+0

Desafortunadamente no puedo usar el administrador de repintado predeterminado ya que también tiene que escribir algunos metadatos para que la aplicación C++ los use, p. Ej. identificar regiones sucias en la imagen fuera de pantalla para que la aplicación C++ vuelva a dibujar. La representación se inicia con los medios estándar, todo lo que he reemplazado en el JFrame principal es el RepaintManager, por lo que yo entiendo, ¿cada componente debería causar un repintado cuando se vuelve inválido? No estoy seguro de poder publicar un ejemplo completo debido a la naturaleza del código nativo involucrado, pero lo pondré al lado de Java. –

+0

En el ciclo for en updateSharedMemory, la gran cadena if-else no tiene otra cosa. Intenta agregar eso y ver si hay regiones que no estás manejando. –

Respuesta

0

Encontré un par de cosas que podrían contribuir a lo que está viendo.

En el código updateSharedMemory para manejar el repintado de una ventana, el código llama al contentPane.paint. Este es el culpable más probable ya que la ventana puede no ser su panel de contenido. El código para JPopupMenu (que es utilizado por JComboBox) puede elegir presentar la ventana emergente como un componente de peso pesado. Entonces, la Ventana podría ser la ventana emergente de uno de los JComboBoxes.

Además, sharedMemoryUpdater está programado en el EDT donde se ejecutará una vez que la cola de eventos esté vacía. Por lo tanto, puede haber un desfase entre cuando se llama al addDirtyRegion y cuando se llama al updateSharedMemory. En updateSharedMemory hay una llamada al region.component.paint. Si alguno de los eventos ya en cola cambia component, los resultados reales de la llamada de pintura podrían variar.

Algunas sugerencias como resultado de las pruebas:

Crear sharedMemoryUpdater así:

private Runnable scheduled = null; 

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

Luego, en addDirtyRegion

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

que reducirá el número de invocaciones de sharedMemoryUpdater (por 99% en mis pruebas). Como todas las llamadas al addDirtyRegion deberían estar sucediendo en el EDT, no debería necesitar una sincronización en scheduled, pero agregar no le hará mucho daño.

Como hay un desfase, la cantidad de regiones a procesar puede llegar a ser bastante grande. En mis pruebas, vi que superaba los 400 en un punto.

Estos cambios reducirán el tiempo dedicado a manipular la lista de regiones, ya que es más rápido crear una nueva lista que crear todas las entradas necesarias para copiar la lista existente.

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

Gracias por eso. Parece que lo hace funcionar de manera más eficiente, pero no resuelve el problema. De hecho, cuando lo miro de cerca, parece que el componente ocluido se vuelve a dibujar por separado sobre el menú, es decir,el menú completo está pintado, y luego el otro componente está pintado sobre él después –

0

Estoy haciendo una suposición: que su aplicación se ejecuta en un entorno gráfico real (es decir, no sin cabeza).

Creo que es posible que desee aprovechar el java.awt.Robot que fue diseñado para imitar a un usuario que usa una aplicación AWT/Swing. Puede hacer cosas como simular pulsaciones de teclas, clics del mouse, y puede tomar capturas de pantalla! El método es createScreenCapture(Rectangle), y devuelve un BufferedImage que debería ser perfecto para la mayoría de los casos de uso.

Aquí hay un ejemplo en el que he incluido una cantidad de JComboBox es verticales que se superponen entre sí. Abrir uno de estos y presionar F1 tomará una captura de pantalla y lo mostrará en el panel de vista previa a continuación.

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

} 

Usted debe ser capaz de ver toda la ventana que incluye las decoraciones de las ventanas, y el menú de cuadro combinado debe aparecer en la parte superior de los otros cuadros de combinación exactamente como se ve en la pantalla.

Cuestiones relacionadas