java - 如何在不闪烁的情况下调整 Swing JWindow 的大小?

标签 java swing resize flicker jwindow

我正在尝试基于 JWindow 制作自定义 UI,以便选择要共享的屏幕区域。我已经扩展了 JWindow 并添加了代码以使其可调整大小并使用 AWTUtilities.setWindowShape()“切掉”窗口的中心。

运行代码时,当窗口在负 x 和 y 方向(即向上和向左)调整大小时,我遇到了闪烁。似乎正在发生的事情是在更新组件之前调整窗口大小并绘制。下面是代码的简化版本。运行时,顶部面板可用于向上和向左调整窗口大小。窗口的背景设置为绿色,以明确我不想显示的像素在哪里。

编辑:改进了代码以使用 ComponentListener 正确塑造窗口,并在底部添加了一个虚拟组件以进一步说明闪烁(也更新了屏幕截图)。

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Area;

import javax.swing.JPanel;
import javax.swing.JWindow;
import javax.swing.border.CompoundBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.border.EtchedBorder;
import javax.swing.border.LineBorder;

import com.sun.awt.AWTUtilities;

public class FlickerWindow extends JWindow implements MouseListener, MouseMotionListener{

    JPanel controlPanel;
    JPanel outlinePanel;
    int mouseX, mouseY;
    Rectangle windowRect;
    Rectangle cutoutRect;
    Area windowArea;

    public static void main(String[] args) {
        FlickerWindow fw = new FlickerWindow();
    }

    public FlickerWindow() {
        super();
        setLayout(new BorderLayout());
        setBounds(500, 500, 200, 200);
        setBackground(Color.GREEN);

        controlPanel = new JPanel();
        controlPanel.setBackground(Color.GRAY);
        controlPanel.setBorder(new EtchedBorder(EtchedBorder.LOWERED));
        controlPanel.addMouseListener(this);
        controlPanel.addMouseMotionListener(this);

        outlinePanel = new JPanel();
        outlinePanel.setBackground(Color.BLUE);
        outlinePanel.setBorder(new CompoundBorder(new EmptyBorder(2,2,2,2), new LineBorder(Color.RED, 1)));

        add(outlinePanel, BorderLayout.CENTER);
        add(controlPanel, BorderLayout.NORTH);
        add(new JButton("Dummy button"), BorderLayout.SOUTH);
        setVisible(true);
        setShape();

        addComponentListener(new ComponentAdapter() {           
            @Override
            public void componentResized(ComponentEvent e) {
                setShape();
            }});
    }


    public void paint(Graphics g) {
        // un-comment or breakpoint here to see window updates more clearly
        //try {Thread.sleep(10);} catch (Exception e) {}
        super.paint(g);
    }

    public void setShape() {
        Rectangle bounds = getBounds();
        Rectangle outlineBounds = outlinePanel.getBounds();
        Area newShape = new Area (new Rectangle(0, 0, bounds.width, bounds.height));
        newShape.subtract(new Area(new Rectangle(3, outlineBounds.y + 3, outlineBounds.width - 6, outlineBounds.height - 6)));
        setSize(bounds.width, bounds.height);
        AWTUtilities.setWindowShape(this, newShape);
    }

    public void mouseDragged(MouseEvent e) {
        int dx = e.getXOnScreen() - mouseX;
        int dy = e.getYOnScreen() - mouseY;

        Rectangle newBounds = getBounds();
        newBounds.translate(dx, dy);
        newBounds.width -= dx;
        newBounds.height -= dy;

        mouseX = e.getXOnScreen();
        mouseY = e.getYOnScreen();

        setBounds(newBounds);
    }

    public void mousePressed(MouseEvent e) {
        mouseX = e.getXOnScreen();
        mouseY = e.getYOnScreen();
    }

    public void mouseMoved(MouseEvent e) {}
    public void mouseClicked(MouseEvent e) {}
    public void mouseReleased(MouseEvent e) {}
    public void mouseEntered(MouseEvent e) {}
    public void mouseExited(MouseEvent e) {}
}

重写的 paint() 方法可以用作断点,或者 Thread.sleep() 可以在此处取消注释以提供更清晰的更新 View 发生。

我的问题似乎源于 setBounds() 方法导致窗口在布局之前被绘制到屏幕上。


调整大小之前的窗口,应该是这样的:

alt text


在被覆盖的 paint() 方法的断点处看到更大的窗口(向上和向左):

alt text


在被覆盖的 paint() 方法的断点处看到的更小(向下和向右)的窗口):

alt text


当然,这些屏幕截图是在激烈的鼠标拖动过程中拍摄的,但即使是更温和的鼠标拖动,闪烁也会变得非常烦人。

调整为更大屏幕截图的绿色区域显示了在任何绘画/布局完成之前绘制的新背景,它似乎发生在底层 ComponentPeer 或 native 窗口管理器中。 'resize to small' 屏幕截图上的蓝色区域显示 JPanel 的背景被推到 View 中,但现在已经过时了。这发生在 Linux(Ubuntu) 和 Windows XP 下。

有没有人找到一种方法来使 WindowJWindow 在对屏幕进行任何更改之前调整其大小,从而避免这种闪烁效果?也许有一个 java.awt.... 系统属性可以设置来避免这种情况,但我找不到。


编辑 #2: 注释掉对 AWTUtilities.setWindowShape() 的调用(并可选择取消注释 Thread.sleep(10) 行在 paint()) 中,然后用力拖动顶部面板,以便清楚地看到闪烁的性质。

编辑 #3: 是否有人能够在 Windows 7 或 Mac OSX 上的 Sun Java 下测试此行为?

最佳答案

我承认这不是一个特别有用的答案,但了解 Swing 究竟是什么可能会有所帮助。

你看,Swing 做所有自己的工作,实际上没有从操作系统中获得一点空间。所有的绘图、小部件等都是 Java 代码。与其说它运行缓慢,不如说它在没有 2D 显卡加速和操作系统渲染技巧的情况下运行。

还记得 DirectDraw 吗?现在一切都有了,而且 window 操作非常顺利。但是,如果您曾经因为某种原因(例如,没有安装驱动程序的 XP 安装)购买了一台没有它的计算机,您会注意到正是这种类型的减速。

使用 Swing,因为它管理自己的所有空间,操作系统无法使用任何这些渲染技巧来帮助您。

有人可能会提供一些优化来解决您计算机上的问题,但我担心它并不能真正解决基本问题 - Swing 很慢而且无法变得更快。

您应该研究原生工具包。 AWT 还可以,但缺少很多小部件/等。它是原生的,内置的,所以如果你需要的话,它应该足够快。我偏爱 SWT,这是 Eclipse、Vuze(以及其他)使用的。它结合了 AWT 的原生特性与 Swing 的易用性和特性,当然可以在任何地方运行。

编辑:在阅读了您的更多评论后,您非常清楚您完全了解窗口化是如何发生的——我不想表现得居高临下。不仅如此,您对调整大小更感兴趣,我的评论与此没有太大关系。我仍然推荐 SWT,因为它是 native 代码且速度更快,但与上面的答案不同。

关于java - 如何在不闪烁的情况下调整 Swing JWindow 的大小?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4324221/

相关文章:

java - HttpServletResponse 响应 : Ask user to download file instead of auto downloading

java - 防止在Java中向JTable中添加重复数据

java - Java 中的 ModalDialog 阻塞

java - 如何在 java 中使用代理获取 URL 连接?

java - 仅针对内容类型 x-www-form-urlencoded 接受 Spring + Spring Security 请求

jquery - TinyMCE:禁用特定div的 'resizable'功能?

python - 无法使用 Pillow 将图像调整为更大的尺寸?

javascript - 填充屏幕和滚动时图像的纵横比

java - 查找并替换 xml 文件中的 int

java - 截图程序