Рендеринг компонентов Swing во внеэкранный буфер - PullRequest
5 голосов
/ 26 мая 2010

У меня есть приложение Java (Swing), работающее на 32-битном Windows 2008 Server, которое должно визуализировать его вывод за пределы экрана (который затем выбирается другим приложением C ++ для рендеринга в другом месте). Большинство компонентов отображаются правильно, за исключением нечетного случая, когда компонент, который только что потерял фокус, перекрывается другим компонентом, например, когда есть два JComboBox, близких друг к другу, если пользователь взаимодействует с нижним, затем нажимает на верхний, так что раскрывающийся список перекрывает другой блок.

В этой ситуации компонент, потерявший фокус, визуализируется после того, как он был закрыт, и поэтому отображается в выводе сверху. Он корректно отображается на обычном дисплее 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 ]

0 голосов
/ 09 декабря 2010

Я делаю одно предположение: ваше приложение работает в реальной графической среде (то есть не без головы).

Я думаю, что вы, возможно, захотите воспользоваться java.awt.Robot, который был разработан для имитации пользователя с помощью приложения AWT / Swing. Он может делать такие вещи, как имитация нажатий клавиш, щелчков мыши, и он может делать скриншоты ! Метод - createScreenCapture(Rectangle), и он возвращает BufferedImage, что должно быть идеально для большинства случаев использования.

Вот пример, где я включил несколько вертикальных JComboBox, которые перекрывают друг друга. Если открыть один из них и нажать клавишу 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);
            }
        });
    }

}

Вы должны видеть все окно, включая оформление окон, и меню со списком должно отображаться поверх других полей со списком точно так, как вы видите его на экране.

0 голосов
/ 03 июня 2010

Я нашел пару вещей, которые могли бы способствовать тому, что вы видите.

В коде updateSharedMemory для обработки перерисовки окна этот код вызывает contentPane.paint. Это наиболее вероятный виновник, так как окно может не быть вашей панелью содержимого. Код для JPopupMenu (который используется JComboBox) может выбрать для представления всплывающего окна в качестве тяжеловесного компонента. Таким образом, окно может быть всплывающим окном одного из JComboBox.

Кроме того, 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...