Binayaka Chakraborty
Binayaka Chakraborty

Reputation: 1335

Refresh widget properly?

I am currently trying to make the UI of our application lightweight, and as such am trying to move the heavyweight server calls to their own threads. I then update the widget using Display.getDefault.asyncExec(), which works perfectly.

I want to display a animation to the user while the server call returns. To that point, I am using another thread, which shows the animation in another Display.getDefault.asyncExec(). That also works... but in a weird way. When I run the app, the widget flickers very heavily, and sometime the screen stays unupdated. The thing is, when I run a prototype version,i.e without the main app, just a bare bones SWT app, it works perfectly. Any ideas why this happens?

Basic workflow ::

showAnimation = new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    synchronized (this) {
                        try {                       
                            Thread.sleep(1000);
                            drawCircles(); // basically the animation, also called in a Display.getDefault().asyncExec()
                        } 
                        catch (InterruptedException e) 
                        {
                            // do nothing, because this thread is meant to be interrupted!
                            // and then, break out of the infinite loop                                 
                            System.out.println("Interrupted");
                            break;
                        }
                    }                   
                }                               
            }
        });

invokeLater = new Thread(new Runnable(){
    @Override
    public void run(){
        // do the data base call here
        System.out.println("Got data!");
        if(showAnimation.isAlive())
        {
            showAnimation.interrupt();
        }
    }
});

and in my view -->

showAnimation.start();
invokeLater.start();

I briefly wondered if I should increase the priority of the animation thread, just to see if that works.It didn't (also doesn't make sense to increase the priority of the thread which is not bottle-necking the application).

Any ideas?

P.S: The drawCircles() code -->

public void drawCircles(){
        Display.getDefault().asyncExec(new Runnable() {                         
            @Override
            public void run(){
                int radius = 100;
                int maxX = radius ;
                int maxY = radius ;
                int direction=0;                        
                int centerX = table.getBounds().width/2;
                int centerY = table.getBounds().height/2;       
                System.out.println("Drawing");
                Image image = new Image(Display.getDefault(),table.getBounds().width,table.getBounds().height);
                GC gc = new GC(image);                              
                gc.setAntialias(SWT.ON);
                gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                //gc.drawLine(0, centerY, shell.getBounds().width, centerY);
                //gc.drawLine(centerX, 0, centerX, shell.getBounds().height);
                // 1st circle
                gc.drawOval(centerX-radius/2, centerY-radius - radius/2, maxX, maxY);
                // 2nd circle
                gc.drawOval(centerX - 2*radius + radius/2, centerY - radius/2, maxX, maxY);
                // 3rd circle
                gc.drawOval(centerX+radius/2, centerY-radius/2, maxX, maxY);
                // 4th circle
                gc.drawOval(centerX-radius/2, centerY + radius/2, maxX, maxY);
                direction++;
                direction %= 4;
                switch(direction){                              
                case 0:                                 
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX - 2*radius + radius/2, centerY - radius/2, maxX, maxY);
                    break;
                case 1:                                 
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX-radius/2, centerY-radius - radius/2, maxX, maxY);
                    break;                              
                case 2:                                 
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX+radius/2, centerY-radius/2, maxX, maxY);
                    break;      
                case 3 :                                    
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX-radius/2, centerY + radius/2, maxX, maxY);
                    break;
                }
                table.setBackgroundImage(image);
                table.redraw();
                table.update();         
                image.dispose();
                gc.dispose();   
            }
        });
    }

Upvotes: 0

Views: 147

Answers (1)

Binayaka Chakraborty
Binayaka Chakraborty

Reputation: 1335

To solve the issue, I ended up using double buffering, by drawing the image in a GC and then updating the paintEvent gc later. This greatly reduced the flickering , even with anti-aliasing turned on.

Basically, create a PaintListener::

final PaintListener paint = new PaintListener() {

            @Override
            public void paintControl(PaintEvent e) {
                int radius = 100;
                int maxX = radius ;
                int maxY = radius ;                             
                int centerX = e.width/2;
                int centerY = e.height/2;
                Image image = new Image(Display.getDefault(),e.width,e.height);
                GC gc = new GC(image);                              
                gc.setAntialias(SWT.ON);
                gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                //gc.drawLine(0, centerY, shell.getBounds().width, centerY);
                //gc.drawLine(centerX, 0, centerX, shell.getBounds().height);
                // 1st circle
                gc.drawOval(centerX-radius/2, centerY-radius - radius/2, maxX, maxY);
                // 2nd circle
                gc.drawOval(centerX - 2*radius + radius/2, centerY - radius/2, maxX, maxY);
                // 3rd circle
                gc.drawOval(centerX+radius/2, centerY-radius/2, maxX, maxY);
                // 4th circle
                gc.drawOval(centerX-radius/2, centerY + radius/2, maxX, maxY);
                direction++;
                direction %= 4;
                switch(direction){                              
                case 0:                 
                    gc.setAlpha(255);
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX - 2*radius + radius/2, centerY - radius/2, maxX, maxY);
                    break;
                case 1:
                    gc.setAlpha(170);
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX-radius/2, centerY-radius - radius/2, maxX, maxY);
                    break;                              
                case 2:             
                    gc.setAlpha(80);
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX+radius/2, centerY-radius/2, maxX, maxY);
                    break;      
                case 3 :            
                    gc.setAlpha(20);
                    gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
                    gc.fillOval(centerX-radius/2, centerY + radius/2, maxX, maxY);
                    break;
                }
                e.gc.drawImage(image, 0, 0);
                image.dispose();
                gc.dispose();
            }
        };

The animation thread can then be written as ::

showAnimation = new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    synchronized (this) {
                        try {                       
                            Thread.sleep(100);
                            Display.getDefault().asyncExec(new Runnable() {                             
                                @Override
                                public void run() {
                                    table.addPaintListener(paint);
                                    table.redraw();
                                    table.update();
                                    table.removePaintListener(paint);
                                }
                            });

                        } 
                        catch (InterruptedException e) 
                        {
                            // do nothing, because this thread is meant to be interrupted!
                            // and then, break out of the infinite loop
                            break;
                        }
                    }                   
                }                               
            }
        });

I added and removed the listeners because notify was giving some issues (drawing more times than required!). Then, the database call can be made in another thread like this::

invokeLater = new Thread(new Runnable() {

            @Override
            public void run() {        
              //Fetching records from Service
                try 
                {   
                    // database call here
                    if(showAnimation.isAlive())
                    {
                        showAnimation.interrupt();
                    }

                } 
                        }
             });

This ended up working for me If anyone can improve upon this, please tell me :)

Upvotes: 1

Related Questions