如何调整 Swing JWindow 的大小而不闪烁?

发布于 2024-10-05 05:10:55 字数 4924 浏览 3 评论 0原文

我正在尝试创建一个基于 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() ,以便在更新发生时提供更清晰的视图。

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


调整大小之前的窗口,应如下所示:

alt text


调整大小期间的窗口较大(向上和向左),如在覆盖的断点处所示paint() 方法):

alt text


窗口在调整大小时更小(向下和向右),如图所示在重写 paint() 方法的断点处):

alt text


当然,这些屏幕截图是在攻击期间拍摄的鼠标拖动移动,但即使对于更温和的鼠标拖动,闪烁也会变得相当烦人。

调整大小到更大屏幕截图上的绿色区域显示了在完成任何绘画/布局之前绘制的新背景,它似乎发生在底层 ComponentPeer 或本机窗口管理器中。 “调整大小”屏幕截图上的蓝色区域显示 JPanel 的背景已推入视图,但现已过时。这种情况发生在 Linux(Ubuntu) 和 Windows XP 下。

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


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

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

I am trying to make a custom UI based on a JWindow for the purpose of selecting an area of the screen to be shared. I have extended JWindow and added code to make it resizable and to 'cut out' the centre of the window using AWTUtilities.setWindowShape().

When running the code I am experiencing a flicker as the window is resized in negative x and y directions, i.e. up and left. What appears to be happening is that the window is resized and drawn before the components are updated. Below is a simplified version of the code. When run the top panel can be used to resize the window up and to the left. The background of the window is set to green to make it clear where the pixels I do not want showing are.

Edit: Improved the code to shape the window correctly using a ComponentListener and added a dummy component at bottom to further illustrate flicker (also updated screenshots).

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) {}
}

The overridden paint() method can be used as a breakpoint or the Thread.sleep() can be uncommented there to provide a clearer view of the update as it happens.

My problem seems to stem from the setBounds() method causing the window to be painted to the screen before being laid out.


Window before resizing, as it should look:

alt text


Window during resizing larger (up and left) as seen at breakpoint at overridden paint() method):

alt text


Window during resizing smaller (down and right) as seen at breakpoint at overridden paint() method):

alt text


Granted these screenshots are taken during aggressive mouse drag movements but the flicker becomes quite annoying even for more moderate mouse drags.

The green area on the resize to larger screenshot shows the new background that gets drawn before any painting/layout is done, it seems to happen in the underlying ComponentPeer or native window manager. The blue area on the 'resize to smaller' screenshot shows the JPanel's background being pushed into view but is now out of date. This happens under Linux(Ubuntu) and Windows XP.

Has anyone found a way to cause a Window or JWindow to resize to a back buffer before any changes are made to the screen and thus avoid this flickering effect? Maybe there is a java.awt.... system property that can be set to avoid this, I could not find one though.


Edit #2: Comment out the call to AWTUtilities.setWindowShape() (and optionally uncomment the Thread.sleep(10) line in paint()) then drag the top panel around aggressively in order to clearly see the nature of the flicker.

Edit #3: Is anyone able to test this behaviour under Sun Java on Windows 7 or Mac OSX ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

走野 2024-10-12 05:10:55

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

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

还记得 DirectDraw 吗?如今一切都有了,窗口操作也非常顺利。但是,如果您曾经使用过一台由于某种原因而没有安装该功能的计算机(例如,安装了没有驱动程序的 XP),您就会注意到正是这种类型的速度减慢。

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

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

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

编辑:在阅读了您的更多评论后,很明显您绝对了解窗口是如何发生的 - 我不想显得居高临下。不仅如此,您还对调整大小更感兴趣,而我的评论与此没有太大关系。我仍然推荐 SWT,因为它是本机代码并且速度更快,但这与上面的答案不同。

I admit this is not a particularly helpful answer, but understanding what exactly Swing is may help.

See, Swing does all its own work, short of actually getting a bit of space to draw on from the OS. All the drawing, widgets, etc are Java code. It's not so much that this is running to slowly, as that it's running without the benefit of the 2D graphics card acceleration and the OS rendering tricks.

Remember DirectDraw? Everything has it nowadays, and window ops are butter-smooth. But if you ever grab a computer that doesn't have it for some reason (say, a XP install with no drivers) you notice exactly this type of slowdown.

With Swing, because it manages all its own space, the OS can't do any of these rendering tricks to help you out.

Somebody may come along with some optimization that fixes your problem on your computer, but I'm concerned that it's just not really going to fix the base issue - Swing is slow and can't get faster.

You should look into native toolkits. AWT is OK, but missing a lot of widgets/etc. It's native, and built-in, so it should be plenty fast if that's all you need. I'm partial to SWT, which is what Eclipse, Vuze (among others) use. It combines the native-ness of AWT with the ease and features of Swing, and of course runs everywhere.

EDIT: It's pretty clear after reading some more of your comments that you absolutely understand how the windowing happens - I don't want to come off as condescending. Not only that, but you're more interested in resizing, which my comment doesn't have that much to do with. I'd still recommend SWT because it's native code and faster, but that's a different answer than the one above.

喜爱皱眉﹌ 2024-10-12 05:10:55

我刚刚尝试过你的例子。调整窗口大小时我确实看到了一些小的闪烁。我尝试替换从 setBounds() 开始到 AWTUtilities.setWindowShape(this, newShape); 结束的代码片段

setSize(newBounds.width, newBounds.height);

,发现闪烁消失了。因此,我建议您使用此解决方案,除非您有任何特殊原因使用 setBounds

I have just tried your example. I really saw some small flicking when resizing the window. I tried to replace the code fragment starting from setBounds() and finishing at AWTUtilities.setWindowShape(this, newShape); by

setSize(newBounds.width, newBounds.height);

and saw that the flicking disappeared. So, I'd suggest you this solution unless you have any special reasons to use setBounds.

眼前雾蒙蒙 2024-10-12 05:10:55

另一种方法可能是等待用户在绘画之前完成拖动/调整大小。也许使用边界框向用户显示窗口的大小,直到调整大小事件完成为止。

我注意到很多窗口应用程序要么采用这种方法,要么处理闪烁。几乎所有我尝试过的激进调整大小的应用程序都有同样的问题(非 Java),因此问题可能只是图形子系统而不是 Swing 本身。

Another approach might be to wait for the user to complete their dragging/resizing before painting. Maybe use a bounding box to show the user the size of the window until after their resize event is complete.

I've noticed a lot of windowed applications will either take that approach or deal with the flickering. Just about all the apps I've tried an aggressive resize like that with has the same problem (non-Java) so the problem may just lie with the graphic subsystem rather than Swing itself.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文