Question

I have a JPanel in a JScrollpane. I draw on a BufferedImage, which I display on the JPanel. In the top left corner of the JScrollpane, I want a picture, that always stays in that corner when I scroll down to see the rest of my JPanel. Here the paintComponent method of the Jpanel:

@Override
public void paintComponent(Graphics g){
    super.paintComponent(g);
    if (bufferedImage != null){
        g.drawImage(bufferedImage, 0, 0, this);
        Point p = parent.getViewPosition();
        System.out.println("paintComponent(): "+ p.x + "," + p.y);
        g.setColor(Color.RED);
        g.fillRect(p.x + 20, p.y + 20, 200, 200);
    }
}

Where parent.getViewPosition() give me the scrollPane.getViewport().getViewPosition(). When I start up, I can see the buffered image with the red rectangle in the top left corner. When I scroll down, I can see the rest of the buffered image, but the red rectangle moves up and then disappaeres and don't come again when I scroll up. In the console I can see that point p changes when I scroll:

paintComponent(): 0,0
paintComponent(): 0,10
paintComponent(): 0,20
paintComponent(): 0,30
paintComponent(): 0,40
paintComponent(): 0,50

Can anyone help me with this problem?

Was it helpful?

Solution

You could use a glass pane for this and tell it to draw its image at a location that depends on the location of the viewport. For example:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;

import javax.swing.*;

@SuppressWarnings("serial")
public class ScrollImgGlass extends JPanel {
   private static final int BI_W = 40;
   private static final int BI_H = BI_W;
   private static final String[] DATA = { "One", "Two", "Three", "Four",
         "Five", "Six", "Seven", "Eight", "Nine", "Zero", "One", "Two",
         "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Zero",
         "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight",
         "Nine", "Zero", "One", "Two", "Three", "Four", "Five", "Six", "Seven",
         "Eight", "Nine", "Zero" };
   private BufferedImage img = null;
   private JViewport viewport;

   public ScrollImgGlass(JViewport viewport) {
      setOpaque(false);
      this.viewport = viewport;
      img = new BufferedImage(BI_W, BI_H, BufferedImage.TYPE_INT_ARGB);
      Graphics2D g2 = img.createGraphics();
      g2.setColor(Color.red);
      g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
      g2.fillOval(0, 0, BI_W, BI_H);
      g2.dispose();
   }

   @Override
   protected void paintComponent(Graphics g) {
      Point vpLocation = viewport.getLocationOnScreen();
      Point gpLocation = getLocationOnScreen();

      int x = vpLocation.x - gpLocation.x;
      int y = vpLocation.y - gpLocation.y;

      super.paintComponent(g);
      if (img != null) {
         g.drawImage(img, x, y, this);
      }
   }

   private static void createAndShowGui() {
      JList<String> jList = new JList<String>(DATA);
      jList.setOpaque(false);

      JViewport viewport = new JViewport();
      JScrollPane scrollpane = new JScrollPane();
      scrollpane.setViewport(viewport);
      viewport.setView(jList);

      ScrollImgGlass glass = new ScrollImgGlass(viewport);

      JFrame frame = new JFrame("ScrollImg");
      frame.setGlassPane(glass);
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(scrollpane, BorderLayout.CENTER);

      // just to show that this works if the viewport is shifted over
      frame.getContentPane().add(Box.createRigidArea(new Dimension(20, 20)), BorderLayout.NORTH);
      frame.getContentPane().add(Box.createRigidArea(new Dimension(20, 20)), BorderLayout.WEST);

      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);

      glass.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}

Which would display like:

enter image description here

OTHER TIPS

As suggested by MadProgrammer, a JLayer does work:

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

public class FixedImageLayerUI extends LayerUI<JComponent>
{
    @Override
    public void paint(Graphics g, JComponent c)
    {
        super.paint(g, c);

        Graphics2D g2 = (Graphics2D) g.create();

        g2.setColor( Color.RED );
        g2.fillOval(0, 0, 10, 10);

        g2.dispose();
    }

    private static void createAndShowUI()
    {
        String[] data =
        {
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
            "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
            "u", "v", "w", "x", "y", "z"
        };

        JList<String> list = new JList<String>( data );
        JScrollPane scrollPane = new JScrollPane( list );

        LayerUI<JComponent> layerUI = new FixedImageLayerUI();
        JLayer<JComponent> layer = new JLayer<JComponent>(scrollPane, layerUI);

        JFrame frame = new JFrame("FixedImage");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( layer );
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

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

Also, as noted by MadProgrammer, overriding the paint method of JScrollPane doesn't work. However if you make the JList non-opaque it does work:

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

public class FixedImageScrollPane
{

    private static void createAndShowUI()
    {
        String[] data =
        {
            "a", "b", "c", "d", "e", "f", "g", "h", "i", "j",
            "k", "l", "m", "n", "o", "p", "q", "r", "s", "t",
            "u", "v", "w", "x", "y", "z"
        };

        JList<String> list = new JList<String>( data );
        list.setOpaque( false );

        JScrollPane scrollPane = new JScrollPane( list )
        {
            @Override
            public void paint(Graphics g)
            {
                super.paint(g);
                g.setColor( Color.RED );
                g.fillOval(0, 0, 10, 10);
            }
        };

        JFrame frame = new JFrame("FixedImage");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( scrollPane );
        frame.pack();
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

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

Don't put the picture in the panel that scrolls. Put it in a different panel, and arrange the two panels with a layout manager.

I suggest looking at BorderLayout; it has areas at n, s, e, and w, and one in center, but you don't have to use all of them (very seldom are all of them used). You can create a JPanel, set its layout manager to BorderLayout, put the panel containing your image in the NORTH part of that, and put your scrolling panel in the CENTER. As a no-cost bonus, your JPanel in the center will stretch in both dimensions when/if the window is resized, because that's the way BorderLayout works.

Somewhere in your code you create a JScrollPane.

Change

    final JScrollPane scrollpane = new JScrollPane();

To:

    final JScrollPane scrollpane = new JScrollPane() {
        @Override
        public void paint(final Graphics g) {
            super.paint(g);
            // Put you drawing here...example, draw a geen dot...
            g.setColor(Color.GREEN);
            g.fillOval(0, 0, 30, 30);
        }
    };

EDIT: Per comments, need do a setOpaque(false) on the object placed in the JScrollPane.

Example:

    list.setOpaque(false);
    scrollpane.setViewportView(list);

OK, following code works with a JTabbedPane. I had to add a componentListener to the panel in the tabbed pane, because I get an exception on the method 'getLocationOnScreen()' when the pane is not on the screen.

public class GlassFrame {
    private JPanel panel;
    private JScrollPane scrollPane;
    private BufferesImage img;

    public GlassFrame() {
        panel = new JPanel(){
            @Override
            public void paintComponent(Graphics g){
                super.paintComponent(g);
                img = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g2 = img.createGraphics();
                g2.setPaint(Color.WHITE);
                   Rectangle2D rect = new Rectangle2D.Float(0, 0, 420, 420);
                   g2.fill(rect);
                   g2.setPaint(Color.BLACK);
                   for (int i = 0; i <= 10; i++) {
                       g2.draw(new Line2D.Float(10 + i * 40,10,10 + i * 40,410));
                       g2.draw(new Line2D.Float(10,10 + i * 40,410,10 + i * 40));
                   }
                g2.dispose();
                g.drawImage(img, 0, 0, this);
            }
        };
        panel.setPreferredSize(new Dimension(420, 420));
        panel.setOpaque(false);

        scrollPane = new JScrollPane(panel, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS);
        scrollPane.getVerticalScrollBar().setUnitIncrement(10);

        JFrame frame = new JFrame("ScrollPane and GlassPane");
        final GlassPane glass = new GlassPane(scrollPane.getViewport());
        frame.setGlassPane(glass);

        JTabbedPane tab = new JTabbedPane();
        JPanel panelInTab = new JPanel();
        panelInTab.setLayout(new BorderLayout());
        tab.add("first tab", panelInTab);
        tab.add("second tab", new JPanel());
        panelInTab.add(scrollPane, BorderLayout.CENTER);
        panelInTab.add(new JButton("testbutton"), BorderLayout.NORTH);
        panelInTab.addComponentListener(new ComponentAdapter() {
            @Override
            public void componentShown(ComponentEvent arg0) {
                glass.setVisible(true);
            }
            @Override
            public void componentHidden(ComponentEvent arg0) {
                glass.setVisible(false);
            }
        });

        frame.getContentPane().add(tab);    
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setPreferredSize(new Dimension(300, 400));
        frame.pack();
        frame.setLocation(200, 200);
        frame.setVisible(true);
        glass.setVisible(true);
    }

    class GlassPane extends JPanel{
        private JViewport viewport;
        private BufferedImage image = null;

        public GlassPane(JViewport viewport){
            setOpaque(false);
            this.viewport = viewport;
            image = new BufferedImage(58, 58, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2 = image.createGraphics();
            g2.setColor(Color.red);
            g2.setStroke(new BasicStroke(3.0f));
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2.drawOval(5, 5, 50, 50);
            g2.dispose();
        }

        @Override
        protected void paintComponent(Graphics g) {
            Point vpLocation = viewport.getLocationOnScreen();
            Point gpLocation = getLocationOnScreen();

            int x = vpLocation.x - gpLocation.x;
            int y = vpLocation.y - gpLocation.y;

            super.paintComponent(g);
            if (image != null) {
                g.drawImage(image, x, y, this);
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                GlassFrame frame = new GlassFrame();
            }
        });
    }
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top