将Swing组件渲染到屏幕外缓冲区

Nic*_*k C 5 java swing

我有一个在32位Windows 2008 Server上运行的Java(Swing)应用程序,它需要将其输出呈现为屏幕外图像(然后由另一个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);
    }
}
Run Code Online (Sandbox Code Playgroud)

有问题的小组:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

Dev*_*ler 1

我发现了一些可能有助于您所看到的内容的事情。

updateSharedMemory处理窗口重绘的代码中,代码调用contentPane.paint. 这是最有可能的罪魁祸首,因为窗口可能不是您的 contentPane。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();
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后,在addDirtyRegion

    if (scheduled == null) {
        scheduled = sharedMemoryUpdater;
        SwingUtilities.invokeLater(sharedMemoryUpdater);
    }
Run Code Online (Sandbox Code Playgroud)

这将减少 的调用次数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;
}
Run Code Online (Sandbox Code Playgroud)