Scrolled composite with slow to repaint content looks ugly
Asked Answered
T

3

7

I am implementing a Gantt component for SWT and this takes a bit to repaint (like, 200 ms for the whole visible part of the diagram).

Now, when I scroll, I only repaint what is needed regarding the clipping rectangle. This makes the application look very bad when I scroll fast, because then the still visible part after scrolling seems to be moved by the OS first, and when I finished painting the remaining part (the part which has become visible during scrolling), immediatly a new scrolling step begins, moving half of my diagram to the right and lets me repaint the other half. This effectively looks like my diagram flickers in the middle during scrolling.

This doesn't look really nice. Is there a way to get around this? Is this question understandable?

EDIT: Here is a "small" test program that shows exactly the behaviour described. You only need SWT in the classpath to run it.

package de.ikoffice.gui;

import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class SlowRepaintProblem {

    public Color[] colors = new Color[501];

    public SlowRepaintProblem() {
        Display display = Display.getDefault();
        for( int i=0; i<=500; i++ ) {
            int r = ( i * 10 ) % 256;
            int g = ( i * 20 ) % 256;
            int b = ( i * 30 ) % 256;
            colors[i] = new Color(display,r,g,b);
        }

        Shell shell = new Shell(display);
        shell.setText("SlowRepaintTest");
        ScrolledComposite comp = new ScrolledComposite(shell,
                SWT.H_SCROLL | SWT.V_SCROLL | SWT.DOUBLE_BUFFERED | SWT.NO_BACKGROUND);
        SlowRepaintingCanvas canvas = new SlowRepaintingCanvas(comp,SWT.NONE| SWT.NO_BACKGROUND);
        comp.setContent(canvas);
        canvas.setSize(5000,5000);

        // Layouting
        shell.setLayout(new GridLayout());        
        comp.setLayoutData(new GridData(GridData.FILL_BOTH));
        shell.setBounds(50, 50, 800, 600);

        // Showing the control
        shell.open();
        while (!shell.isDisposed()) {
            try {
                if (!shell.getDisplay().readAndDispatch()) {
                    shell.getDisplay().sleep();
                }
            } catch (Throwable e) {
                String message = e.getMessage();
                if( message == null || !e.getMessage().equals("Widget is diposed") ) {
                    e.printStackTrace();
                }
                break;
            }
        }
    }

    public static void main(String[] args) {
        new SlowRepaintProblem(); // Evil constructor call to start main program flow.
    }

    class SlowRepaintingCanvas extends Canvas {

        public SlowRepaintingCanvas(Composite parent, int style) {
            super(parent, style);

            addPaintListener(new PaintListener() {
                @Override
                public void paintControl(PaintEvent e) {
                    GC gc = e.gc;
                    Rectangle r = gc.getClipping();
                    gc.setAlpha(255);
//                    gc.setBackground(ColorUtils.WHITE);
//                    gc.fillRectangle(r);

                    int x = r.x - (r.x % 10);
                    int width = (r.width + r.x - x) - (r.width + r.x - x) % 10 + 10;
                    int y = r.y - (r.y % 10);
                    int height = (r.height + r.y - y) - (r.height + r.y - y) % 10 + 10;

                    gc.setAlpha(128);
                    for( int i = x; i < x+width; i+= 10 ) {
                        gc.setBackground(colors[i/10]);
                        gc.fillRectangle(i, r.y, 10, r.height);  
                    }
                    for( int j = y; j < y+height; j+= 10 ) {
                        gc.setBackground(colors[j/10]);
                        gc.fillRectangle(r.x, j, r.width, 10);  
                    }
                }
            });
        }

    }

}
Tynan answered 9/8, 2011 at 6:23 Comment(3)
After reading the question and comments in first answer, there is really no way how to help you without a SSCCE. You don't have to expose your Gantt diagram code, just make SSCCE with any dummy cg and use same scrolling technique to show us the problem exactly..Rhnegative
OK, I will try to create a minimal working example showing the problem.Tynan
Have you tried double-buffering and page-flipping techniques, and are you saying they aren't working for you?Throttle
E
7

SWT painting is very fast and lacking UI can be usually tracked down to slow paint methods. Hence, try to optimize the algorithm that draws your diagram! One approach could be caching - draw the diagram contents into an Image:

Image cache = new Image(Display.getCurrent(), width, height);
GC gc = new GC(cache);

and repaint only the necessary image parts when scrolling:

gc.drawImage(cache, srcX, srcY, srcWidth, srcHeight, destX, destY, destWidth, destHeight);

Once the diagram changes - and only then - repaint the cache image using your complex paint method.

HTH

Exurbanite answered 9/8, 2011 at 7:51 Comment(6)
Does not work also. Still looks as ugly as before, since doing the copy from the image seems to be as slow as rendering it manually.Tynan
Did you sync the clipping region with the attributes in drawImage(), namely did you copy only the necessary subregion of the cached image instead of painting the whole one?Exurbanite
I don't know if I get it right what you just did, when you state "doing copy from the image". It's important to create the cached image only after your diagram has changed and use the cached diagram presentation over several paint requests instead of needing to recreate it for every single paint. Painting a single image is a single GC call compared to several of them when drawing your diagram, which should be much faster, thus. If it's still not faster, I can't help w/o further details/code snippets, sorry.Exurbanite
I only copy the region specified by the GC's clipping region, and i don't repaint the image further. Still not fast enough, sadly. I still have the flickering. IMHO the problem could only be solved, if the first copy done by windows itself would write into a buffer, then I render my changes into the same buffer, then the full buffer is copied onto the screen, but I don't know how to achive this with SWT/Java/WindowsTynan
+1 because the answer would be correct if my painting was much slower.Tynan
Your answer helped most, even if not completely. Here you go :).Tynan
H
0

I took Henrik's suggestion to use an Image to buffer the painting and implemented it in your SSCCE. I see much less flickering now on my system.

        addPaintListener(new PaintListener() {
            @Override
            public void paintControl(PaintEvent e) {
                GC gc = e.gc;
                Rectangle r = gc.getClipping();

                int x = r.x - (r.x % 10);
                int width = (r.width + r.x - x) - (r.width + r.x - x) % 10 + 10;
                int y = r.y - (r.y % 10);
                int height = (r.height + r.y - y) - (r.height + r.y - y) % 10 + 10;

                Image image = new Image(gc.getDevice(),width,height);
                GC igc = new GC(image);

                // Without buffering, this code was necessary to prevent "ghost"
                // scrollbars on window resize, but with buffering it is no longer
                // required...it does affect the visual results however.
                //igc.setAlpha(255);
                //igc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_BLACK));
                //igc.fillRectangle(image.getBounds());

                igc.setAlpha(128);
                for( int i = x; i < x+width; i+= 10 ) {
                    igc.setBackground(colors[i/10]);
                    igc.fillRectangle(i-x, 0, 10, height);  
                }
                for( int j = y; j < y+height; j+= 10 ) {
                    igc.setBackground(colors[j/10]);
                    igc.fillRectangle(0, j-y, width, 10);  
                }

                gc.drawImage(image, x, y);
                igc.dispose();
                image.dispose();
            }
        });
Hexa answered 17/8, 2011 at 18:7 Comment(2)
Oops, just read the comment history under Henrik's answer more closely, seems you already tried an image buffer and it didn't help enough? I'll leave my answer here anyways in case someone else finds it useful...Hexa
But you are right. Much less flickering here. Strangly, if I apply the same technique to my gantt, it gets 3 times slower. I will have to see if I have another error in it.Tynan
A
0

This issue is due to SWT.NO_BACKGROUND is used in your graphical SWT components.

Aphorize answered 4/6, 2020 at 11:57 Comment(2)
It's a long time but I think I just skipped scrolled conposite and implemented the handling of the scroll bars on my own. Somehow solved the problem.Tynan
yeah, the issue is caused by SWT.NO_BACKGROUND that you have passed to the scrolled composites. we got the same issue and it was fixed after replacing SWT.TRANSPARENTAphorize

© 2022 - 2024 — McMap. All rights reserved.