我有一个在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);
}
}
遇到问题的小组:
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将重新绘制在其上。
答案 0 :(得分:0)
我发现了一些可能有助于你所看到的事情。
在用于处理窗口重绘的updateSharedMemory
代码中,代码调用contentPane.paint
。这是最可能的罪魁祸首,因为Window可能不是您的contentPane。 JPopupMenu(由JComboBox使用)的代码可以选择将弹出窗口呈现为重量级组件。因此,Window可以是其中一个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;
}
答案 1 :(得分:0)
我做了一个假设:您的应用程序在真实的图形环境中运行(即不是无头)。
我认为您可能希望利用旨在模仿使用AWT / Swing应用程序的用户的java.awt.Robot
。它可以执行诸如模拟按键,鼠标点击之类的操作,并且可以截取屏幕截图!方法为createScreenCapture(Rectangle)
,它返回BufferedImage
,对于大多数用例来说应该是完美的。
这是一个例子,其中我包含了许多相互重叠的垂直JComboBox
es。打开其中一个并按下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);
}
});
}
}
你应该可以看到整个窗口,包括窗口装饰,组合框菜单应该出现在其他组合框的顶部,就像你在屏幕上看到的一样。