How can a Swing JWindow be resized without flickering?
Asked Answered
O

3

24

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 ?

Overwinter answered 1/12, 2010 at 12:12 Comment(4)
@willjcroz: +1, very nicely formulated question. And to all: please do NOT upvote any answer without ALSO upvoting the question.Coping
I'd like to know the answer to the same question as well - I've hated it for years, but never found even a hacky workaround. Tested on Windows 7 and although I don't get quite the same effects as you I get something very similar. Resize artefacts are seen for every Java application, not just your example. Curiously, moving a window works alright, only resizing sucks (probably because Windows 7 handles moving windows itself and doesn't trigger resize in Java - unlike Windows XP if I remember correctly).Herrle
@Domchi: thanks for the confirmation of it happening on Windows 7 :-). Moving the window on XP seems smooth with no repaints. Maybe you are referring to window 'repair' on XP being poor. Compositing WMs (as seen in Vista/Win7, OSX and Compiz etc on Linux) handle the 'repair' of previously obscured parts of windows without requesting repaints, this is where I think XP may fall down.Overwinter
@willjcroz: Yes, you're correct - I was referring to the fact that Win7 caches current window picture, so if EDT is stuck for whatever reason, you can still move your window around and have insides visible, instead of having gray rectangle as soon as you move the window as on XP.Herrle
L
3

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.

Lustrate answered 4/12, 2010 at 18:58 Comment(2)
thanks for your thoughts, don't worry I didn't feel patronised ;). Yes SWT does look like a way to get around this for some future project. But since this project has an imminent deadline and the fact that this UI aspect is within an applet that requires fast loading times SWT does not offer not offer me a solution right now.Overwinter
also the problem seems to actually exist within the AWT layer underlying Swing's JWindow. Specifically the native peer, ComponentPeer, and its corresponding native code offers no way to ask for a the child component to paint itself into a provided buffer before any updates to the screen device. This therefore seems to be an issue for all top level AWT and (heavyweight) Swing components.Overwinter
T
1

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.

Trudey answered 1/12, 2010 at 12:46 Comment(2)
Thanks for your input. To get the correct resize behaviour (this example represents the use case of resizing up and to the left) I need to both move the window and resize it (the JWindow's top-left origin needs to change) hence the use of setBounds().Overwinter
@AlexR: ok but many other platforms AWTUtilities.setWindowShape(this, newShape); is not working just tried and failed. Fedora linux/Ubuntu and OpenJDK.Osteoplastic
I
1

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.

Incertitude answered 2/12, 2010 at 16:53 Comment(3)
yes I am currently considering the bounding box approach, the only problem with it being that the user needs precision (selecting an exact area of the screen) and so having the interface change between two states is undesirable. As for other apps have you tried resizing in Eclipse? Despite its complex layout it has zero flicker for me, jerky yes but flicker free! This is leading me to think that I may have better luck learning and using SWT.Overwinter
Yeah, I suppose jerky/flicker are one in the to me. It's undesirable either way to see any repainting. I was resizing my IDE while I was writing my comment -- so I suppose we were on the same page. I work with some SWT/RCP apps at work, as much as it provides a pretty good framework but leaves much to be desired in terms of extensibility. The event model it uses too isn't very friendly to work with either. Have you tried checking out the NetBeans platform? I think that's a Swing based frame that might be worth looking into.Incertitude
PS: I was using an old machine to test yours out and try for a fix (2.0 single core with Windows XP) -- I could still see flicker/jerkiness when I did any resizes.Incertitude

© 2022 - 2024 — McMap. All rights reserved.