在JScrollPane的视口上绘制静态图像

4
我正在尝试在JScrollPane上绘制一个红色正方形。我下面的代码做得还不错,但有时当我快速滚动视口时,红色正方形会向上或向下跳动。
这让我感到奇怪,因为JScrollPane本身是静止的,所以我认为Swing不会试图移动其中绘制的组件。我猜想实际发生的是,红色正方形与视口关联,而视口显示的图形确实会移动。
无论如何,我该如何防止红色正方形跳来跳去并成功地在列表上绘制红色正方形?也许我完全采取了错误的方法。
package components;

import java.awt.*;
import java.util.Vector;

import javax.swing.*;
import javax.swing.event.*;

@SuppressWarnings("serial")
public class DialogWithScrollPane extends JFrame {

  public DialogWithScrollPane() {
    super();

    setResizable(false);
    Container pane = getContentPane();

    Vector<Object> listOfStuff = new Vector<Object>();
    for (int i = 0; i < 100; i++) {
      listOfStuff.add(Integer.toString(i));
    }

    final JScrollPane scrollPane = new JScrollPane() {

      public void paint(Graphics g) {
        System.out.println("JScrollPane.paint() called.");
        super.paint(g);

        g.setColor(Color.red);
        g.fillRect(20, 50, 100, 200);
      }
    };
    JList list = new JList(listOfStuff) {
      public void paint(Graphics g) {
        System.out.println("JList.paint() called.");
        super.paint(g);

        // Well, I could do this...
            //
        // scrollPane.repaint();
        //
        // ...and it would solve the problem, but it would also result in an
        // infinite recursion since JScrollPane.paint() would call this
        // function again.
      }
    };

    // Repaint the JScrollPane any time the viewport is moved or an item in the
    // list is selected.
    scrollPane.getViewport().addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        scrollPane.repaint();
      }
    });
    list.addListSelectionListener(new ListSelectionListener() {
      public void valueChanged(ListSelectionEvent e) {
        scrollPane.repaint();
      }
    });

    scrollPane.setViewportView(list);

    pane.add(scrollPane);
    setMinimumSize(new Dimension(300, 300));
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLocation(500, 250);
    setVisible(true);
  }

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

2
这个解决方案可能过于复杂,但你可以尝试在“玻璃面板”上绘制。例如 - https://dev59.com/SFjUa4cB1Zd3GeqPWf-_#6610064 但我可能是错误的,这个解决方案可能不适用。 - mre
@masson:请查看我回答的编辑。 - Hovercraft Full Of Eels
1个回答

6
JScrollPane 应该在 JViewport 后面绘制,而 JViewport 应该在列表后面绘制。我猜这只能通过覆盖 paint 方法并不断调用 JScrollPane 的 repaint 方法来实现。也许您想使用 JLayeredPane 来容纳 JScrollPane 并在其上绘制。编辑:或者像 mre 建议的那样使用 glasspane,但是如果您这样做并将 glasspane 设置为可见,可能会失去与底层 scrollpane 交互的能力。
编辑2:例如,
import java.awt.*;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.Vector;
import javax.swing.*;

@SuppressWarnings("serial")
public class DialogWithScrollPane2 extends JFrame {

   public DialogWithScrollPane2() {
      super();

      //setResizable(false);
      final JPanel pane = (JPanel) getContentPane();

      Vector<Object> listOfStuff = new Vector<Object>();
      for (int i = 0; i < 100; i++) {
         listOfStuff.add(Integer.toString(i));
      }

      final JScrollPane scrollPane = new JScrollPane();
      JList list = new JList(listOfStuff);

      scrollPane.setViewportView(list);

      final JPanel blueRectPanel = new JPanel() {
         @Override
         protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.blue);
            g.fillRect(20, 50, 100, 200);
         }
      };
      blueRectPanel.setOpaque(false);

      final JLayeredPane layeredPane = new JLayeredPane();
      layeredPane.add(scrollPane, JLayeredPane.DEFAULT_LAYER);
      layeredPane.add(blueRectPanel, JLayeredPane.PALETTE_LAYER);

      layeredPane.addComponentListener(new ComponentAdapter() {

         private void resizeLayers() {
            final JViewport viewport = scrollPane.getViewport();
            scrollPane.setBounds(layeredPane.getBounds());
            blueRectPanel.setBounds(viewport.getBounds());
            SwingUtilities.invokeLater(new Runnable() {
               public void run() {
                  blueRectPanel.setBounds(viewport.getBounds());
               }
            });
         }

         @Override
         public void componentShown(ComponentEvent e) {
            resizeLayers();
         }

         @Override
         public void componentResized(ComponentEvent e) {
            resizeLayers();
         }
      });

      pane.add(layeredPane);
      setPreferredSize(new Dimension(300, 300));
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      pack();
      setLocation(500, 250);
      setVisible(true);
   }

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

1
我不确定您的编辑是否真实。在那个例子中,我故意阻止了所有输入事件,而“玻璃面板”确实拦截了它们,但我不确定它是否默认完全阻止它们。因此,与底层滚动面板交互的能力可能仍然存在,但我还没有测试过!:D - mre
1
@masson 请查看有关在JViewPort上绘制任何内容的相关帖子 https://dev59.com/nFrUa4cB1Zd3GeqPghvr#7006684,以及trashgod的链接+1。 - mKorbel
@mKorbel:请解释一下“(你的表单不是Old.Forums.Sun吗?” - Hovercraft Full Of Eels
这个不错的例子我在forums.sun.com下用加密名称放置了一点儿,你是不是? - mKorbel
谢谢你的回答。我之前不知道 JLayeredPane,现在在这种情况下它更加合理。 - peskal
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接