Search code examples
javaswingjscrollpanejava-2dclip

Wrong getClipBounds in combination with JScrollPane


Maybe i have encountered a bug or more probably doing something wrong ;) I try to translate the content of a user drawn JPanel using a JScrollPanel. Inside the panel the drawing i would like to access the visible area through the Graphics class getClipBounds method to improve rendering performance.

Searching on SO brings a lot results referring to JScrollPane but none is mentioning a problem with the clip bounds. Google the same.

user drawn panel

import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;

public class Content extends JPanel {
    @Override
    protected void paintChildren(Graphics g) {
        super.paintChildren(g);
        // intense clip bounds dependent rendering here
        System.out.println(g.getClipBounds());
    }
    @Override
    public Dimension getPreferredSize() {
        return new Dimension(2000,2000);
    }
}

main frame setup

import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import java.awt.BorderLayout;

public class ClipBoundsIssue {
    private JFrame frame;
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    ClipBoundsIssue window = new ClipBoundsIssue();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }
    public ClipBoundsIssue() {
        frame = new JFrame();
        frame.setBounds(100, 100, 450, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JScrollPane scrollPane = new JScrollPane();
        frame.getContentPane().add(scrollPane, BorderLayout.CENTER);
        Content content = new Content();
        scrollPane.setViewportView(content);
    }
}

to reproduce

Just run the code, move one of the scrollbars and inspect the console output of System.out. The following picture depicted scrolling the bar on the x axis.

moved scrollbar on x axis

actual System.out result

Which produced the following results

java.awt.Rectangle[x=0,y=0,width=416,height=244]
java.awt.Rectangle[x=416,y=0,width=16,height=244]
java.awt.Rectangle[x=432,y=0,width=15,height=244]
java.awt.Rectangle[x=447,y=0,width=16,height=244]
java.awt.Rectangle[x=463,y=0,width=15,height=244]

expected result

I would have expected to have the width of the bounds to keep the same. But it changes from 416 to 16.

The question now is

Does anybody know why this happens, or how it can be avoided??

discared WAs

A possible workaround would be to lookup the view port's view bounds. But if possible i would like to avoid the Content class making any such lookup. Another alternative would be to pass the information into the Content class, but this i would like to avoid as well.


Solution

  • I would have expected to have the width of the bounds to keep the same.

    Why? It is so simple that it is hard to explain, but let me try.

    When you scrolling, only small new portion if the JPanel is appearing if you scroll slowly.

    The produced output is absolutely correct:

    java.awt.Rectangle[x=0,y=0,width=416,height=244] Control is shown first time, you need to redraw it completely

    java.awt.Rectangle[x=416,y=0,width=16,height=244] You scrolled to the right by 16 pixels, so only narrow strip of you control must be redrawn.

    You must understand that these coordinates are related to your control which has size set to 2000x2000 pixels.

    Try to scroll the window created with this code and you will see what I am talking about:

    import javax.swing.*;
    import java.awt.*;
    import java.util.Random;
    
    public class ScrollPaneRepaintDemo extends JPanel {
        public ScrollPaneRepaintDemo() {
            setPreferredSize(new Dimension(2000,2000));
        }
    
        public static void main(String[] args) {
            JFrame frame = new JFrame();
            frame.add(new JScrollPane(new ScrollPaneRepaintDemo()));
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setVisible(true);
        }
    
        @Override protected void paintComponent(Graphics g) {
            Rectangle clip = g.getClipBounds();
            g.setColor(new Color(new Random().nextInt()));
            g.fillRect(clip.x, clip.y, clip.width, clip.height);
        }
    }
    

    By the way - it works so because of JPanel's internal implementation. If you extend JComponent instead, the whole viewport will be clipped. I add also that JPanel repaints completely when resizing, its optimizations are only for scrolling.