我有一个运行在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在其上重新绘制。
当您使用默认的RepaintManager时会发生什么?另外,我不明白你是如何开始渲染组件的。例如,如果我有一个打开的组合框并单击一个按钮来启动某个动作,组合框将关闭,所以我不知道如何重现您描述的情况。我建议帮助张贴您的SSCCE(http://sscce.org)来证明问题。 – camickr 2010-05-25 20:54:15
不幸的是,我不能使用默认的重绘管理器,因为它还必须为C++应用程序编写一些元数据以供使用,例如,识别屏幕图像中的脏区域,以便重绘C++应用程序。 渲染是通过标准方式启动的,我在主JFrame中替换的所有内容都是RepaintManager,因此就我所知,每个组件在变为无效时都应该导致重绘。 我不确定我可以发布一个完整的例子,因为涉及的本地代码的性质,但我会把它的Java一面。 – 2010-05-26 09:06:41
在updateSharedMemory的for循环中,大的if-else链没有最后的else。尝试添加,看看是否有你不处理的区域。 – 2010-05-27 20:52:45