How to smoothen scrolling of JFrame in Java
Asked Answered
L

3

5

I have a JFrame in my Java application that contains a JPanel where I have some drawing objects created at run-time. The problem is while scrolling the JFrame for large figures the scrolling slows up and scroll bar does not move smoothly. Please note I am using Graphics 2D object and doing repaint on scroll action.

Is there any way of smoothing the scrolling action of JFrame.

Here is some part of the code

public class DiagramPanel implements MouseListener{

    int click=0;
    Point p1;
    Point p2;
    private Dimension panelDimension;
    .... // variables

    public void go() {
        p1 = new Point();
        p2 = new Point();

        JFrame f = new JFrame();
        f.setVisible(true);
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setSize(1200,500);
        panelx = new DiaPanel();
        panelx.setOpaque(true);
        panelx.setBackground(Color.white);
        panelx.setAutoscrolls(true);
        panelx.addMouseListener(this);


        JScrollPane scrollPane = new JScrollPane();

        // scrollPane.add(panelx);

        ClassRectangle tempRect = null;
        for (ClassRectangle rect : this.classRectangles) {
            tempRect = rect;
        }


        Rectangle rect = new Rectangle();
        rect.setBounds(tempRect.getW() - 100, 0, 1000,
                tempLife.getEndpointY() * 500);

        panelDimension = new Dimension(0,0);
        for (ClassRectangle rectx : classRectangles){
            panelDimension.width=rectx.getW()+300;
        }
        for (LifeLine life : lifeLines) {
            panelDimension.height=life.getEndpointY()+300;
        }

        scrollPane.setViewportView(panelx);
        panelx.computeVisibleRect(rect);
        JScrollPane scrollPane1 = new JScrollPane(panelx);

        panelx.setPreferredSize(panelDimension);
        panelx.repaint();
        panelx.revalidate();
        p1.x=0;
        p1.y=0;
        p2.y=panelDimension.height;
        p2.x=panelDimension.width;
        f.add( scrollPane1);
        scrollPane.revalidate();
        f.setBackground(Color.white);
    }

    public DiagramPanel(ArrayList<Rectangle> classRectangles,
            ArrayList<Pair> pairs, ArrayList<Line> lines,
            ArrayList<Life> meth) {

            // constructing obj of DrawingPanel Here
    }

    public class SeqDiaPanel extends JPanel {
        /**
         * 
         */

        private static final long serialVersionUID = 1L;

        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d2 = (Graphics2D) g;
            g2d2.setColor(Color.orange);

            //grid
            for (int i = 0; i < panelDimension.height; i++) {
                g2d2.drawLine(0, 0 + i * 5, panelDimension.width+1000, 0 + i * 5);

            }

            for (int i = 0; i < panelDimension.width; i++) {
                g2d2.drawLine(0 + i * 5, 0, 0 + i *5,panelDimension.height+300);
            }

            g2d2.setColor(Color.black);

            // objects 
            .......... some objects here


            }
            }
            // draw Lines

            Stroke drawingStroke = new BasicStroke(2, BasicStroke.CAP_BUTT,
                    BasicStroke.JOIN_BEVEL, 0, new float[] { 5 }, 0);
            // Stroke drawingStroke = new BasicStroke();
            Graphics2D g2d = (Graphics2D) g;
            g2d.setStroke(drawingStroke);
            for (Line life : lines) {
                g2d.drawLine(life.getStartpointX(), life.getStartpointY(),
                        life.getEndpointX(), life.getEndpointY());
                panelDimension.height=life.getEndpointY()+300;
            }

            // draw methodLfe
            for (Object2 ml1 : Obj2) {
                g2d2.fill3DRect(ml1.StartX(), ml1.getMethodStartY(),
                        ml1.getBreadth(), ml1.getEndX(),true);
            }

        }


    }

    // tobeused

    public int calculateWidth(String name){
        Font font = new Font("Serif", Font.BOLD, 12);
         FontMetrics metrics = new FontMetrics(font){

            /**
             * 
             */
            private static final long serialVersionUID = 1L;};
        int tempInt2=SwingUtilities.computeStringWidth( metrics, name);
        tempInt2=tempInt2+10;
        return tempInt2;
    }




    /*public class MouseClick implements MouseListener{
        Point p =  new Point(0,0);
        @Override
        public void mouseClicked(MouseEvent evnt) {

            p.x=evnt.getX();
            p.y=evnt.getY();
            System.out.println("MouseClicked @"+p.x+":"+p.y);

        }

        @Override
        public void mouseEntered(MouseEvent arg0) {
            // TODO Auto-generated method stub

        }

        @Override
        public void mouseExited(MouseEvent arg0) {
            // TODO Auto-generated method stub

        }

        @Override
        public void mousePressed(MouseEvent arg0) {
            // TODO Auto-generated method stub

        }

        @Override
        public void mouseReleased(MouseEvent arg0) {
            // TODO Auto-generated method stub

        }

    }*/

    @Override
    public void mouseClicked(MouseEvent evnt) {
        click++;
        if(click==1){
        //Point p= new Point();
        p1.x=evnt.getX();
        p1.y=evnt.getY();
    //  System.out.println("MouseClicked1 @"+p1.x+":"+p1.y);
        }

        if(click==2){
            p2.x=evnt.getX();
            p2.y=evnt.getY();
            //System.out.println("MouseClicked2 @"+p2.x+":"+p2.y);
            click=0;
            if(p1.x<p2.x&&p1.y<p2.y){
            panelx.repaint();
            }
            else{

            }

        }/*else{
            p1.x=0;
            p1.y=0;
            p2.x=panelDimension.width+500;
            p2.y=panelDimension.height+700;
        }*/
    }

    @Override
    public void mouseEntered(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseExited(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mousePressed(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }

    @Override
    public void mouseReleased(MouseEvent arg0) {
        // TODO Auto-generated method stub

    }
}
Luggage answered 4/7, 2012 at 13:40 Comment(7)
You mean that when you are scrolling, you are calling repaint? Post an SSCCE for better helpAntiparallel
I guess you need to use "double buffering". Search this term or post some codes for further help.Gurule
Thanks for the code but it does not compile and it is not executable. Anyway, follow @mKorbel's advice and take into account the clip bounds of the Graphics object. In your paint method you repaint the whole component while you should try to paint only the clipped area. Also, I don't see where you call repaint during scroll.Antiparallel
Both mKorbel and myself have now posted an SSCCE as an answer. As you can see, the code runs as copy/pasted, and they are (very) short. Please read the document linked by @GuillaumePoletMathildemathis
@GuillaumePolet yes It has actually 5 classes that are part of this code so I tried to convey the code in a little short mannerLuggage
"I tried to convey the code in a little short manner" What does the MouseListener have to do with it? Can you reproduce the problem without it? If so ..take it out. That is the process of creating an SSCCE of broken code. Your words suggest you have not read the SSCCE document. Please do so.Mathildemathis
@AndrewThompson yes sir I will improve the code after reading SSCCEE mouselister in original code helps in selecting points on the JPanel at runtimeLuggage
M
7

Why not put the Graphics2D drawing in a (large) BufferedImage and display it in a label in a scroll-pane? Something like this (animated, 5000x5000px):

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;

public class BigScrollImage {

    BigScrollImage() {
        final int x = 5000;
        final int y = 5000;
        final BufferedImage bi = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB);
        Graphics2D g1 = bi.createGraphics();

        g1.setColor(Color.BLACK);
        g1.fillRect(0, 0, x, y);

        g1.dispose();

        final JLabel label = new JLabel(new ImageIcon(bi));

        ActionListener listener = new ActionListener() {
            Random rand = new Random();
            @Override
            public void actionPerformed(ActionEvent ae) {
                Graphics2D g2 = bi.createGraphics();
                int x1 = rand.nextInt(x);
                int x2 = rand.nextInt(x);
                int y1 = rand.nextInt(y);
                int y2 = rand.nextInt(y);
                int r = rand.nextInt(255);
                int g = rand.nextInt(255);
                int b = rand.nextInt(255);
                g2.setColor(new Color(r,g,b));
                g2.drawLine(x1,y1,x2,y2);

                g2.dispose();
                label.repaint();
            }
        };

        Timer t = new Timer(5,listener);

        JScrollPane scroll = new JScrollPane(label);
        JFrame f = new JFrame("Big Scroll");
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        f.add(scroll);
        f.pack();
        f.setSize(800, 600);

        f.setLocationByPlatform(true);
        f.setVisible(true);
        t.start();
    }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                new BigScrollImage();
            }
        });
    }
}

It tries to draw 200 hundred lines per second, and seems to scroll smoothly here.

Mathildemathis answered 4/7, 2012 at 14:8 Comment(0)
I
12

this idea maybe can to help you

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.*;

public class TilePainter extends JPanel implements Scrollable {

    private static final long serialVersionUID = 1L;

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame("Tiles");
                frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
                frame.getContentPane().add(new JScrollPane(new TilePainter()));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
    private final int TILE_SIZE = 50;
    private final int TILE_COUNT = 100;
    private final int visibleTiles = 10;
    private final boolean[][] loaded;
    private final boolean[][] loading;
    private final Random random;

    public TilePainter() {
        setPreferredSize(new Dimension(TILE_SIZE * TILE_COUNT, TILE_SIZE * TILE_COUNT));
        loaded = new boolean[TILE_COUNT][TILE_COUNT];
        loading = new boolean[TILE_COUNT][TILE_COUNT];
        random = new Random();
    }

    public boolean getTile(final int x, final int y) {
        boolean canPaint = loaded[x][y];
        if (!canPaint && !loading[x][y]) {
            loading[x][y] = true;
            Timer timer = new Timer(random.nextInt(500),
                    new ActionListener() {

                        @Override
                        public void actionPerformed(ActionEvent e) {
                            loaded[x][y] = true;
                            repaint(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
                        }
                    });
            timer.setRepeats(false);
            timer.start();
        }
        return canPaint;
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Rectangle clip = g.getClipBounds();
        int startX = clip.x - (clip.x % TILE_SIZE);
        int startY = clip.y - (clip.y % TILE_SIZE);
        for (int x = startX; x < clip.x + clip.width; x += TILE_SIZE) {
            for (int y = startY; y < clip.y + clip.height; y += TILE_SIZE) {
                if (getTile(x / TILE_SIZE, y / TILE_SIZE)) {
                    g.setColor(Color.GREEN);
                } else {
                    g.setColor(Color.RED);
                }
                g.fillRect(x, y, TILE_SIZE - 1, TILE_SIZE - 1);
            }
        }
    }

    @Override
    public Dimension getPreferredScrollableViewportSize() {
        return new Dimension(visibleTiles * TILE_SIZE, visibleTiles * TILE_SIZE);
    }

    @Override
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE * Math.max(1, visibleTiles - 1);
    }

    @Override
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        return false;
    }

    @Override
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return TILE_SIZE;
    }
}
Imposture answered 4/7, 2012 at 13:51 Comment(5)
Nice answer. But apparently I cannot +1 for another (checks) 9 hours! :(Mathildemathis
not good experiences with paintComponent(someRectengle) or paintImediatelly(someRectsngle), my bad not bothering with that somehow... +1 for basic stuffImposture
..tick, tick, tick. Oh, can now. +1 :)Mathildemathis
As Gilbert Le Blanc pointed it out to me, this is a nice solution (see : [[#13386832)Alphonsoalphonsus
+1 very awesome especially for some pure java map tiles :) and looks pretty effecientHelen
M
7

Why not put the Graphics2D drawing in a (large) BufferedImage and display it in a label in a scroll-pane? Something like this (animated, 5000x5000px):

import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;

public class BigScrollImage {

    BigScrollImage() {
        final int x = 5000;
        final int y = 5000;
        final BufferedImage bi = new BufferedImage(x,y,BufferedImage.TYPE_INT_RGB);
        Graphics2D g1 = bi.createGraphics();

        g1.setColor(Color.BLACK);
        g1.fillRect(0, 0, x, y);

        g1.dispose();

        final JLabel label = new JLabel(new ImageIcon(bi));

        ActionListener listener = new ActionListener() {
            Random rand = new Random();
            @Override
            public void actionPerformed(ActionEvent ae) {
                Graphics2D g2 = bi.createGraphics();
                int x1 = rand.nextInt(x);
                int x2 = rand.nextInt(x);
                int y1 = rand.nextInt(y);
                int y2 = rand.nextInt(y);
                int r = rand.nextInt(255);
                int g = rand.nextInt(255);
                int b = rand.nextInt(255);
                g2.setColor(new Color(r,g,b));
                g2.drawLine(x1,y1,x2,y2);

                g2.dispose();
                label.repaint();
            }
        };

        Timer t = new Timer(5,listener);

        JScrollPane scroll = new JScrollPane(label);
        JFrame f = new JFrame("Big Scroll");
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);

        f.add(scroll);
        f.pack();
        f.setSize(800, 600);

        f.setLocationByPlatform(true);
        f.setVisible(true);
        t.start();
    }
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable(){
            @Override
            public void run() {
                new BigScrollImage();
            }
        });
    }
}

It tries to draw 200 hundred lines per second, and seems to scroll smoothly here.

Mathildemathis answered 4/7, 2012 at 14:8 Comment(0)
A
2

Part 1

There are my little mods on mKorbel's answer, thanks to him and Gilbert Le Blanc :

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.lang.ref.WeakReference;
import java.util.Random;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.Timer;


/**
 *
 * @author leBenj
 */
public class GJPanelBufferedImageTileAdapter extends GJPanelBufferedImageAdapter implements Scrollable
{
    protected BufferedImage _image = null;

    protected GIPanelListener _parent = null;

    private int TILE_SIZE_W = -1;

    private int TILE_SIZE_H = -1;

    private int TILE_COUNT_W = 32;

    private int TILE_COUNT_H = 32;

    private int visibleTiles = 10;

    private boolean[][] loading;

    private WeakReference<BufferedImage>[][] subs;

    private final Random random;

    public GJPanelBufferedImageTileAdapter( final GIPanelListener parent , LayoutManager layout , boolean isDoubleBuffered )
    {
        super( parent , layout , isDoubleBuffered );
        this._parent = parent;
        resetTiling();
        random = new Random();
    }

    public void resetTiling()
    {
        loading = new boolean[TILE_COUNT_W][TILE_COUNT_H];
        subs = new WeakReference[TILE_COUNT_W][TILE_COUNT_H];
    }

    private BufferedImage getTile( int x , int y )
    {
        BufferedImage retour = null;
        if( x < TILE_COUNT_W )
        {
            if( y < TILE_COUNT_H )
            {
                if( subs[x][y] != null )
                {
                    retour = subs[x][y].get();
                }
            }
        }
        return retour;
    }

    private void setTile( BufferedImage sub , int x , int y )
    {
        subs[x][y] = new WeakReference<BufferedImage>( sub );
    }

    private boolean loadTile( final int x , final int y )
    {
        boolean canPaint = ( getTile( x , y ) != null );
        if( x < TILE_COUNT_W )
        {
            if( y < TILE_COUNT_H )
            {
                if( !canPaint && !loading[x][y] )
                {
                    Timer timer = new Timer( random.nextInt( 500 ) , new ActionListener()
                    {
                        @Override
                        public void actionPerformed( ActionEvent e )
                        {
                            BufferedImage sub = _image.getSubimage( x * TILE_SIZE_W , y * TILE_SIZE_H , TILE_SIZE_W , TILE_SIZE_H );
                            setTile( sub , x , y );
                            repaint( x * TILE_SIZE_W , y * TILE_SIZE_H , TILE_SIZE_W , TILE_SIZE_H );
                        }
                    } );
                    timer.setRepeats( false );
                    timer.start();
                }
            }
        }
        return canPaint;
        }

    // using paint(g) instead of paintComponent(g) to start drawing as soon as the panel is ready
    @Override
    protected void paint( Graphics g )
    {
        super.paint( g );
        Rectangle clip = g.getClipBounds();
        int startX = clip.x - ( clip.x % TILE_SIZE_W );
        int startY = clip.y - ( clip.y % TILE_SIZE_H );
        int endX = clip.x + clip.width /*- TILE_SIZE_W*/;
        int endY = clip.y + clip.height /*- TILE_SIZE_H*/;
        for( int x = startX ; x < endX ; x += TILE_SIZE_W )
        {
            for( int y = startY ; y < endY ; y += TILE_SIZE_H )
            {
                if( loadTile( x / TILE_SIZE_W , y / TILE_SIZE_H ) )
                {
                    BufferedImage tile = getTile( x / TILE_SIZE_W , y / TILE_SIZE_H );
                    if( tile != null )
                    {
                        g.drawImage( subs[x / TILE_SIZE_W][y / TILE_SIZE_H].get() , x , y , this );
                    }
                }
                else
                {
                    g.setColor( Color.RED );
                    g.fillRect( x , y , TILE_SIZE_W - 1 , TILE_SIZE_H - 1 );
                }
            }
        }
            g.dispose(); // Without this, the original view area will never be painted
    }

    /**
     * @param image the _image to set
     */
    public void setImage( BufferedImage image )
    {
        this._image = image;
        TILE_SIZE_W = _image.getWidth() / TILE_COUNT_W;
        TILE_SIZE_H = _image.getHeight() / TILE_COUNT_H;
        setPreferredSize( new Dimension( TILE_SIZE_W * TILE_COUNT_W , TILE_SIZE_H * TILE_COUNT_H ) );
    }

    @Override
    public Dimension getPreferredScrollableViewportSize()
    {
        return new Dimension( visibleTiles * TILE_SIZE_W , visibleTiles * TILE_SIZE_H );
    }

    @Override
    public int getScrollableBlockIncrement( Rectangle visibleRect , int orientation , int direction )
    {
        if( orientation == SwingConstants.HORIZONTAL )
        {
            return TILE_SIZE_W * Math.max( 1 , visibleTiles - 1 );
        }
        else
        {
            return TILE_SIZE_H * Math.max( 1 , visibleTiles - 1 );
        }
    }

    @Override
    public boolean getScrollableTracksViewportHeight()
    {
        return false;
    }

    @Override
    public boolean getScrollableTracksViewportWidth()
    {
        return false;
    }

    @Override
    public int getScrollableUnitIncrement( Rectangle visibleRect , int orientation , int direction )
    {
        if( orientation == SwingConstants.HORIZONTAL )
        {
            return TILE_SIZE_W;
        }
        else
        {
            return TILE_SIZE_H;
        }
    }

}

Explanations :

There was a little problem on right and bottom scrolls : to avoid ArrayOutOfBoundsException, I implemented tests on x and y.

I splitted the TILE_SIZE in two parts in order to fit the image proportions.

As I mentioned in the link in my previous comment, coupling the tiles with an array of WeakReference regulates memory usage : I replaced the boolean[][] loaded by WeakReference[][] and implemented the tileGet(x,y) function to get the tile.

The setImage() methods initializes the class fields such as the size of tiles.

The this._image field is inherited from a superclass and is implemented as below :

protected BufferedImage _image = null;

I hope this could help someone.

Alphonsoalphonsus answered 20/11, 2012 at 0:1 Comment(2)
There is the solution I found to lessen "toggling" on tiles : [[#4781600Alphonsoalphonsus
Where can I find GJPanelBufferedImageAdapter ?Adrianople

© 2022 - 2024 — McMap. All rights reserved.