Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: How to draw non-scrolling overlay over ScrollPane Viewport?

I'd like to use a ScrollPane to display an image in its Viewport, and also have a grid (or box, or any other type of registration/location marker) overlay on the image. I need the overlay to remain fixed when scrolling (meaning the image seems to move "under" the overlay). I will be scrolling the View in the Viewport at a fixed rate to provide a smooth motion, and the overlay is to provide a reference to a certain location within the Viewport. Conceptually, think of a large map scrolling in a Viewport, and the Viewport having a rectangle that does not move (relative to the Viewport itself) that marks a region that can be zoomed into based on some user action.

I presume (but have not yet confirmed) that the ScrollPane implementation handles rendering of the View efficiently (from backing store, not having to repaint the entire View (or even ViewPort) for each new partial exposure) and so would prefer not to have to Override the paint() method.

I've looked at LayerPane, and while not having mastered it, it seems that this is a brute force approach. The ScrollPane is one Component of a SplitPane and will be moved/resized. I expect that I'd have to manually maintain the correct relationship between the ViewPort and the LayerPane using absolute positioning. I'm far from an expert in GUI design & implementation and I'm sure I'm missing possibilities ranging from obvious to obscure and elegant that an experienced person would know.

I'm open to any suggestions, not just the path I've started down. Should I (can I) add a JPanel as one component in my SplitPane, then add a LayerPane to that containing my ScrollPane and the overlay (another JPanel) on different layers on the LayerPane? Is there functionality in ScrollPane to support this directly? I've looked at the Java Swing Tutorials and the API documentation but haven't seen anything yet.

Thanks.

UPDATE:

Thanks for the link, trashgod. This was much easier than I had expected. No need for LayerPane, GlassPane, xor-based compositing... I don't know why it didn't occur to me to try overriding JViewport.paint() to draw my overlay - but with the code below I validated the concept will give me what I'm looking for. Just use the subclass of JViewport and it does what I wanted (in this case overlaying a rectangle in the center of the Viewport while the image scrolls underneath). I'm no GUI expert, and the Java API is incredibly wide; I have no idea how you guys keep this all in your head - but thanks!

class MyViewport extends JViewport {
    @Override
    public void paint(Graphics g) {
        super.paint(g);
        Graphics2D g2 = (Graphics2D)g;
        g2.setPaint(Color.BLACK);
        g2.drawRect(getWidth()/4,getHeight()/4,getWidth()/2,getHeight()/2);
    }
}
like image 993
ags Avatar asked Apr 10 '12 17:04

ags


1 Answers

Ordinarily, "Swing programs should override paintComponent() instead of overriding paint()," as mentioned in Painting in AWT and Swing: The Paint Methods. Based on ScrollPanePaint, which draws below the scrolling content, the following example overrides paint() to draw above the scrolling content.

ScrollPanePaint

import java.awt.*;
import javax.swing.*;

/**
 * @see https://stackoverflow.com/a/10097538/230513
 * @see https://stackoverflow.com/a/2846497/230513
 * @see https://stackoverflow.com/a/3518047/230513
 */
public class ScrollPanePaint extends JFrame {

    private static final int TILE = 64;

    public ScrollPanePaint() {
        JViewport viewport = new MyViewport();
        viewport.setView(new MyPanel());
        JScrollPane scrollPane = new JScrollPane();
        scrollPane.setViewport(viewport);
        this.add(scrollPane);
        this.setDefaultCloseOperation(DISPOSE_ON_CLOSE);
        this.pack();
        this.setLocationRelativeTo(null);
        this.setVisible(true);
    }

    private static class MyViewport extends JViewport {

        public MyViewport() {
            this.setOpaque(false);
            this.setPreferredSize(new Dimension(6 * TILE, 6 * TILE));
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
            g.setColor(Color.blue);
            g.fillRect(TILE, TILE, 3 * TILE, 3 * TILE);
        }
    }

    private static class MyPanel extends JPanel {

        public MyPanel() {
            this.setOpaque(false);
            this.setPreferredSize(new Dimension(9 * TILE, 9 * TILE));
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.lightGray);
            int w = this.getWidth() / TILE + 1;
            int h = this.getHeight() / TILE + 1;
            for (int row = 0; row < h; row++) {
                for (int col = 0; col < w; col++) {
                    if ((row + col) % 2 == 0) {
                        g.fillRect(col * TILE, row * TILE, TILE, TILE);
                    }
                }
            }
        }
    }

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

            @Override
            public void run() {
                new ScrollPanePaint();
            }
        });
    }
}

Note: Setting an opaque component (for instance JTable) as a view for the scroll pane will give strange visual bugs, for instance moving the fixed blue box when scrolling. Use setOpaque(false) on the view component to fix that.

like image 63
trashgod Avatar answered Nov 15 '22 16:11

trashgod