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

Java 在JScrollPane的视口上绘制静态图像,java,swing,jscrollpane,Java,Swing,Jscrollpane,我正试图在一个JScrollPane上画一个红方块。下面的代码在这方面做得不错,但有时当我滚动视口太快时,红方块会上下跳跃 这让我感到奇怪,因为JScrollPane本身是静止的,所以我假设Swing不会试图在它内部绘制的组件周围移动。我猜实际发生的是,红方块与视口关联,视口显示的图形确实在移动 不管怎样,我如何防止红方块跳来跳去,并成功地在列表上画出一个红方块?也许我采取了完全错误的方法 package components; import java.awt.*; import java.

我正试图在一个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();
      }
    });
  }
}
封装组件;
导入java.awt.*;
导入java.util.Vector;
导入javax.swing.*;
导入javax.swing.event.*;
@抑制警告(“串行”)
公共类DialogWithScrollPane扩展了JFrame{
公共对话框WithScrollPane(){
超级();
可设置大小(假);
容器窗格=getContentPane();
Vector listOfStuff=新向量();
对于(int i=0;i<100;i++){
add(Integer.toString(i));
}
final JScrollPane scrollPane=新JScrollPane(){
公共空间涂料(图g){
System.out.println(“调用了JScrollPane.paint()”);
超级油漆(g);
g、 setColor(Color.red);
g、 fillRect(20,50,100,200);
}
};
JList list=新JList(listofsuff){
公共空间涂料(图g){
System.out.println(“调用了JList.paint()”);
超级油漆(g);
//好吧,我可以这么做。。。
//
//scrollPane.repaint();
//
//…这会解决问题,但也会导致
//无限递归,因为JScrollPane.paint()会调用
//再次发挥作用。
}
};
//在移动视口或视图中的项目时重新绘制JScrollPane
//列表已选中。
scrollPane.getViewport().addChangeListener(新的ChangeListener()){
公共无效状态已更改(更改事件e){
scrollPane.repaint();
}
});
addListSelectionListener(新的ListSelectionListener(){
public void值已更改(ListSelectionEvent e){
scrollPane.repaint();
}
});
scrollPane.setViewportView(列表);
添加(滚动窗格);
设置最小尺寸(新尺寸(300300));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
设置位置(500250);
setVisible(真);
}
公共静态void main(字符串[]args){
javax.swing.SwingUtilities.invokeLater(新的Runnable(){
公开募捐{
新建对话框WithScrollPane();
}
});
}
}

JScrollPane应该在JViewport后面绘制,JViewport应该在列表后面绘制。我猜这只是因为您正在重写paint而不是paintComponent,并一直在JScrollPane上调用repaint,以便它在绘制组件后再次绘制自己

也许您希望使用JLayeredPane,让它握住JScrollPane并在其上绘制

编辑:或者我现在看到的mre建议的glasspane,但我担心如果您这样做,并将glasspane设置为可见,您将失去与底层滚动窗格交互的能力

编辑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();
         }
      });
   }
}
import java.awt.*;
导入java.awt.event.ComponentAdapter;
导入java.awt.event.ComponentEvent;
导入java.util.Vector;
导入javax.swing.*;
@抑制警告(“串行”)
公共类对话框WithScrollPane2扩展了JFrame{
公共对话框WithScrollPane2(){
超级();
//可设置大小(假);
最终JPanel窗格=(JPanel)getContentPane();
Vector listOfStuff=新向量();
对于(int i=0;i<100;i++){
add(Integer.toString(i));
}
final JScrollPane scrollPane=新JScrollPane();
JList list=新的JList(listOfStuff);
scrollPane.setViewportView(列表);
最终JPanel blueRectPanel=新JPanel(){
@凌驾
受保护组件(图形g){
超级组件(g);
g、 setColor(Color.blue);
g、 圆角(20、50、100、200);
}
};
blueRectPanel.set不透明(false);
最终JLayeredPane layeredPane=新JLayeredPane();
layeredPane.add(滚动窗格,JLayeredPane.DEFAULT_层);
layeredPane.add(blueRectPanel、JLayeredPane.PALETTE_层);
layeredPane.addComponentListener(新的ComponentAdapter(){
私有void resizeLayers(){
final JViewport viewport=scrollPane.getViewport();
scrollPane.setBounds(layeredPane.getBounds());
blueRectPanel.setBounds(viewport.getBounds());
SwingUtilities.invokeLater(新的Runnable(){
公开募捐{
blueRectPanel.setBounds(viewport.getBounds());
}
});
}
@凌驾
显示的公共无效组件(组件事件e){
resizeLayers();
}
@凌驾
公共无效组件已恢复(组件事件e){
resizeLayers();
}
});
窗格。添加(分层窗格);
setPreferredSize(新尺寸(300300));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
包装();
设置位置(500250);
setVisible(真);
}
公共静态void main(字符串[]args){
javax.swing.SwingUtilities.invokeLater(新的Runnable(){
公开募捐{
新对话框WithCrollPane2();
}
});
}
}

此解决方案对此可能过于强大,但您可以尝试在玻璃窗格上绘制。例如-但我可能错了,这个解决方案可能不适用。@masson:请参阅“编辑我的答案”,我不确定您的编辑是否正确。在该示例中,我确实有意阻止所有输入事件,而
玻璃窗格确实拦截了它们,但我不确定它是否会阻塞