【问题标题】:Java screenshot capture - JFrame paints old component for a second & disappears even though all components were removed & repaintedJava 屏幕截图 - JFrame 将旧组件绘制一秒钟并消失,即使所有组件都已删除并重新绘制
【发布时间】:2014-09-25 20:17:54
【问题描述】:

在您阅读之前,这将是一个信息:Java JFrame won't show up after using .setVisible(true) after being invisible

您好,我正在开发一个库 API,它可以让您捕获屏幕的某个区域,它会返回一个包含 ByteArrayInputStream 和实用方法(如 createBufferedImagecreateFile 等)的类。

基本上,您创建一个 Bootstrap 实例,并将您想要的捕获器类型作为依赖项(ScreenshotCapturer 或 GifCapturer)传递:

Bootstrap b = new Bootstrap(new ScreenshotCapturer());

beginCapture 方法接收一个实现ScreenCaptureCallback 的对象,这是将捕获的结果传递给的回调事件。

这是一个简短的背景。

现在,当您使用beginCapture 方法时,基本上它所做的就是创建SelectionCamera 的新实例,这基本上是绘制您在拖动鼠标时选择的选择区域并更新侦听器的组件。

一旦创建实例,它就会调用super.setVisible(true);

调用该方法后,框架会显示出来,并且还会显示旧的绘制屏幕大约 600-500 毫秒,我不确定,但它消失得如此之快。

看看这个活生生的例子:

请注意使用视频选项,否则您将看不到我所看到的,因为 gif 太慢而无法显示!

http://gyazo.com/d2f0432ada37842966b42dfd87be4240

你可以看到我再次点击屏幕截图后,它显示了旧的选定区域并迅速消失。 (顺便说一下,您在 gif 中看到的框架不是应用程序的一部分,只是虚拟的 hello world 示例用法)。

图像捕获的过程。

第 1 步

beginCapture 被调用:

public void beginCapture(final ScreenCaptureCallback c) {
    SwingUtilities.invokeLater(new Runnable() {
        @Override
        public void run() {
            capturer.setCallback(c);
            capturer.beginSelection();

        }
    });
}

第 2 步

beginSelection 在 Capturer 类中被调用(ScreenshotCapturer 扩展 Capturer(摘要)

@Override
public void beginSelection() {
    super.init();
    this.setHotkeys();
    super.getCamera().startSelection();
}

第 3 步

CaptureCamera#startSelection() 被调用

public void startSelection() {
    super.getContentPane().removeAll();
    super.getContentPane().repaint();

    super.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));

    this.selector = new SelectionCamera();
    this.selectionMosueAdapter.updateCamera(this.selector);
    this.selectionMouseMotion.updateCamera(this.selector);

    super.add(this.selector);

    super.setVisible(true);
    super.repaint();
    super.getContentPane().repaint();
}

第 4 步

用户选择一个区域,鼠标监听器和鼠标移动都监听它(Take a look at mouse motion):

@Override
public void mouseDragged(MouseEvent e) {
    Point dragPoint = e.getPoint();
    Point startPoint = this.selector.getStartPoint();

    int x = Math.min(startPoint.x, dragPoint.x);
    int y = Math.min(startPoint.y, dragPoint.y);
    int width = Math.max(startPoint.x - dragPoint.x, dragPoint.x - startPoint.x);
    int height = Math.max(startPoint.y - dragPoint.y, dragPoint.y - startPoint.y);

    this.selector.setCameraDimension(width, height);
    this.selector.setCoordinates(x, y);

    this.camera.repaint(); // important
}

顺便说一下,this.selectorSelectorCamera,它是绘制选择区域的组件。

第 5 步

CaptureCamera#endSelection() 被调用,此方法从选择相机中获取 x、y、宽度、高度,并将其传递给捕获器类,该捕获器类使用机器人获取该矩形的屏幕截图,并在此之前删除所有组件从内容窗格中,重新绘制所有内容,然后将可见性设置为 false。

public void endSelection() {
    super.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));

    int x = this.selector.getCameraX();
    int y = this.selector.getCameraY();
    int w = this.selector.getCameraWidth();
    int h = this.selector.getCameraHeight();

    super.getContentPane().removeAll();
    super.getContentPane().repaint();

    //super.repaint();
    super.setVisible(false);

    this.c.startCapturing(x, y, w, h);

}

基本上这是最后一步,其余步骤对于调试来说是不必要的,因为它只发回回调。

我真的尽力解释了我的申请过程,我已经尝试了 5 个半小时,但完全没有运气。如您所见,通过创建新的 SelectionCamera 对象尝试了不同的方法,但不起作用。

为什么要这样做?是不是跟摆动核心有关系?

SelectionCamera 代码:https://github.com/BenBeri/WiseCapturer/blob/master/src/il/ben/wise/SelectionCamera.java

提前致谢。

【问题讨论】:

  • 它“lloks”就像selector 可能需要重置......也许......您可能还想突出显示您的代码和捕获库中的代码.. .
  • 一切都在图书馆里,一切都是我的代码
  • @MadProgrammer 您说选择器需要重置,但是即使我从 JFrame 中删除该组件,如果它这样做,这怎么可能,这意味着它不会再被重新绘制或显示?
  • 使 Frame left 为 -10,000 然后设置可见 true,添加一个计时器 2 秒,在计时器上:left to 0
  • @tgkprog 这行得通,但是你有什么核心解释为什么会发生这种情况吗?

标签: java swing api user-interface


【解决方案1】:

基于这个例子...

try {
    final Bootstrap b = new Bootstrap(new ScreenshotCapturer());
    b.beginCapture(new ScreenCaptureCallback() {
        @Override
        public void captureEnded(CapturedImage img) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    b.beginCapture(new ScreenCaptureCallback() {
                        @Override
                        public void captureEnded(CapturedImage img) {
                            System.out.println("...");
                            JFrame frame = new JFrame();
                            frame.add(new JLabel(new ImageIcon(img.getBufferedImage())));
                            frame.pack();
                            frame.setVisible(true);
                            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                        }
                    });
                }
            });
        }
    });
    System.out.println("Hello");
} catch (AWTException exp) {
    exp.printStackTrace();
}

我不会关注第一轮的初始化阶段,我会关注第二轮的初始化,因为这就是问题所在......

b.beginCapture 调用this.capturer.beginSelection();,它调用super.getCamera().startSelection();,它调用setVisible(true)CaptureCameraJFrame)。

这将立即显示以前在CaptureCamera 上显示的内容。需要注意的是,在此过程中没有创建新的对象实例...

现在,我对基础测试做了很多更改,但问题似乎在于框架在第二次可见时的恢复。这似乎是 Window 的透明度支持的问题,因为它似乎恢复了最后一个“已知”状态,而不是立即重新绘制窗口......

现在,我尝试在使 CaptureCamera 对任何 eval 不可见之前清除 selector,因为在绘制 selector 之前窗口似乎是不可见的。

我想出的最终解决方案是在 CaptureCamera 上调用 dispose,这会释放它的本地对等点,因此会破坏旧的图形上下文,当框架再次可见时,它会强制重建自身。

“一个”问题可能是当所有窗口都被释放(并且唯一运行的线程是守护线程)时,JVM 将退出......

这是我测试期间的一个问题,因为我使用javax.swing.Timer 在第一个和第二个捕获过程之间设置延迟,以便我可以看到问题发生在哪里,这导致我的 JVM 退出(因为计时器使用一个守护线程,我没有打开其他窗口)。

我通过在 Capturer 类中创建一个小 (1x1) 透明窗口来解决这个问题,如果 JVM 无缘无故地正常存在,请记住这一点;)

旁注...

现在,SelectionCamera(扩展 JPanel)存在一个问题,它是不透明的,但使用的是透明背景,这是非常危险的,因为 Swing 只知道如何处理不透明或完全透明的组件。

public SelectionCamera() {
    super.setBackground(new Color(0, 0, 0, 0));
    super.setVisible(false);
}

应该更新为...

public SelectionCamera() {
    setOpaque(false);
    //super.setBackground(new Color(0, 0, 0, 0));
    super.setVisible(false);
}

我也对super.xxx 的使用感到困惑,你这样做的唯一原因是如果你已经覆盖了这些方法并且此时不想调用它们......在我的测试中,我删除了所有对 super 的调用,其中一个方法在当前类中没有被覆盖(而且我还没有在被覆盖的方法中)

另外,paintComponent 方法应该调用super.paintComponent

@Override
public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(new Color(0, 0, 0, 0.5f));
    g.fillRect(this.x, this.y, this.width, this.height);
}

【讨论】:

【解决方案2】:

将 Frame left 设置为 -10,000 然后设置为 visible true,添加一个计时器 2 秒(尝试降低到 25-100 毫秒,只是为了让它稍微暂停以使内容无效),将计时器 :left 设置为 0 。我认为它的工作原理是缓存和双缓冲。帧显示它在缓冲区中的内容,由于缓存/延迟重绘,缓冲区指向旧图像。


替代方案: 也许在您的节目之前重新粉刷或无效也可以,并且不需要做剩下的 -10,000。我不太使用 ui-swing,只是几年前,还记得一些类似这样的奇怪事情。

【讨论】:

  • 是的,这是一个解决方案,但对于我的图书馆来说,这是一个非常糟糕的解决方案,考虑到用户点击捕获按钮,需要等待 2 秒,但他不知道他需要,他会尝试尝试选择并且会从负责区域选择的框架中失去焦点。为此,我想添加一个 cursor.WAIT,但我仍然不能这样做,因为 X 轴上的帧是 -10,000。我想在 0,0 上制作一个大小相同的临时 jframe 来显示请等待消息或光标,但我认为这不是一个好主意。
  • 您是否尝试过无效并重新绘制?也许让它 100 毫秒而不是 2 秒。
  • 是的,我尝试过使 contentPane 和 JFrame 失效、验证和重绘。
  • 其实100毫秒就完美了,我什至看不出有什么区别!如果我得不到任何技术答案,我会接受这个答案。
  • 表示离开 java 并转到 c++ :-) 不严重。这太难了,发展太慢了。很高兴 100 毫秒运行良好。
猜你喜欢
  • 1970-01-01
  • 2014-02-10
  • 1970-01-01
  • 1970-01-01
  • 2013-01-13
  • 2013-01-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多