覆盖所有其他组件(Swing、Java)

覆盖所有其他组件(Swing、Java),java,swing,drawing,java-2d,Java,Swing,Drawing,Java 2d,在我的应用程序中,我需要像Photoshop那样绘制网格线——例如,用户可以在文档上拖动线条来帮助他对齐图层。现在的问题是,我能够画出这样的线(这只是使用Line2D的简单Java2D绘画),但我不能将这些线放在其他一切之上,因为当子组件自己绘制时,我的网格线被擦除 程序结构是这样的:JFrame->JPanel->JScrollPane->JPanel->[许多其他类似于层的JPanel] 作为测试,我向JFrame添加了绘图代码,它正确地显示了我的Line2D实例。但是,当我在子组件中执行任

在我的应用程序中,我需要像Photoshop那样绘制网格线——例如,用户可以在文档上拖动线条来帮助他对齐图层。现在的问题是,我能够画出这样的线(这只是使用Line2D的简单Java2D绘画),但我不能将这些线放在其他一切之上,因为当子组件自己绘制时,我的网格线被擦除

程序结构是这样的:JFrame->JPanel->JScrollPane->JPanel->[许多其他类似于层的JPanel]

作为测试,我向JFrame添加了绘图代码,它正确地显示了我的Line2D实例。但是,当我在子组件中执行任何需要该子组件重新绘制自身的操作时,JFrame中绘制的线将被擦除

我知道这是预期的Swing行为——也就是说,它只会重新绘制那些已更改的区域。然而,我正在寻找一种方法,这种方法可以在所有其他方法的基础上连续绘制线网格线

我能够让它工作的唯一方法是使用一个Swing计时器,它每隔10毫秒对我的根组件调用repaint(),但它会消耗大量CPU

更新
下面是一个示例的工作代码。请注意,在我的实际应用程序中,我有几十个不同的组件可以触发重绘(),但没有一个组件引用绘制网格线的组件(当然我可以将其传递给所有人,但这似乎是最新的选项)


如果要在放置到
JScrollPane
JComponents
上进行绘制,则可以在
JViewPort
上进行绘制,例如

编辑:

1) 由于您的代码绘制到了错误的容器,绘制到了
JFrame
,因此确实可以绘制到
JFrame
,但您必须提取

2) 你必须学会如何工作,我让你的代码与原来的大小,不好,非常糟糕

3) 在
玻璃窗格
JViewPort

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Line2D;
import javax.swing.*;

public class GridTest extends JFrame {
    private static final long serialVersionUID = 1L;

    public static void main(String[] args) {
        new GridTest().run();
    }

    private void run() {
        setLayout(null);
        setPreferredSize(new Dimension(200, 200));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        final JPanel p = new JPanel() {
            private static final long serialVersionUID = 1L;

            @Override
            public void paint(Graphics g) {

                super.paint(g);
                Graphics2D gg = (Graphics2D) g.create();
                Line2D line = new Line2D.Double(0, 50, getWidth(), 50);
                gg.setStroke(new BasicStroke(3));
                gg.setColor(Color.red);
                gg.draw(line);
                //gg.dispose();

            }
        };
        p.setBounds(20, 20, 100, 100);
        p.setBackground(Color.white);
        add(p);

        JButton b = new JButton("Refresh");
        b.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                p.repaint();
            }
        });
        b.setBounds(0, 150, 100, 30);
        add(b);

        pack();
        setVisible(true);
    }
}
编辑:2,如果需要单行,则在固定边界上


假设父帧已经有了它必须绘制的所有网格线的列表,那么您可以让每个子帧绘制它自己的网格线位。在伪代码中:

gridlines = getParentsGridLines()
gridlines.offsetBasedOnRelativePosition()
drawStuff()

一种可能的解决方案是重写JPanel的
repaint
方法,以便它调用contentPane的repaint方法。另一点是,您可能不应该直接在JFrame中绘制网格线,而应该在其contentPane中绘制网格线。与我通常建议的相反,我认为最好重写contentPane的paint方法(或其他一些包含JPanel的方法),而不是它的paintComponent方法,以便它可以在绘制子对象后调用。例如:

import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.event.ActionEvent;

import javax.swing.*;

@SuppressWarnings("serial")
public class GridTest2 extends JPanel {
   private static final Stroke LINE_STROKE = new BasicStroke(3f);
   private boolean drawInPaintComponent = false;

   public GridTest2() {
      final JPanel panel = new JPanel() {
         @Override
         public void repaint() {
            JRootPane rootPane = SwingUtilities.getRootPane(this);
            if (rootPane != null) {
               JPanel contentPane = (JPanel) rootPane.getContentPane();
               contentPane.repaint();
            }
         }
      };
      panel.setBackground(Color.white);
      panel.setPreferredSize(new Dimension(100, 100));

      JPanel biggerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
      biggerPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 0, 0));
      biggerPanel.setOpaque(false);
      biggerPanel.add(panel);

      JButton resetButton = new JButton(new AbstractAction("Reset") {
         public void actionPerformed(ActionEvent arg0) {
            panel.repaint();
         }
      });
      JPanel btnPanel = new JPanel();
      btnPanel.add(resetButton);

      setLayout(new BorderLayout());
      add(biggerPanel, BorderLayout.CENTER);
      add(btnPanel, BorderLayout.SOUTH);
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(300, 300);
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (drawInPaintComponent ) {
         drawRedLine(g);
      }
   }

   @Override
   public void paint(Graphics g) {
      super.paint(g);
      if (!drawInPaintComponent ) {
         drawRedLine(g);
      }
   }

   private void drawRedLine(Graphics g) {
      Graphics2D g2 = (Graphics2D) g;
      g2.setStroke(LINE_STROKE);
      g2.setColor(Color.red);
      g2.drawLine(0, 50, getWidth(), 50);
   }

   private static void createAndShowGui() {
      GridTest2 mainPanel = new GridTest2();

      JFrame frame = new JFrame("GridTest2");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

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

Swing在JFrames(和类似组件)中使用JLayeredPane。使用“分层”窗格,可以将仅绘制组件放置在主要内容上


使用放置在JLayeredPane中的组件将任意装饰放置(并自动重新绘制)在任何组件的主要内容上方,从而避免了重写任何给定组件的paint()方法的需要

我知道这是一篇老帖子,但我最近也遇到了同样的问题……
您应该重写
paintChildren
,而不是
paint
paintComponent
。 从:

由Swing调用以绘制组件。应用程序不应直接调用paint,而应使用repaint方法来安排组件重新绘制。
此方法实际上将绘制工作委托给三种受保护的方法:paintComponent、paintBorder和paintChildren按列出的顺序调用它们,以确保子项显示在组件本身的顶部。一般来说,组件及其子组件不应在分配给边界的插图区域中绘制。子类可以像往常一样重写此方法。只想专门化UI(外观)委托的绘制方法的子类应该重写paintComponent

所以如果你

@Override
protected void paintChildren(Graphics g){
    super.paintChildren(g);
    paintGrid(g);
}

网格将位于子组件的顶部^^

为什么不在paintComponent方法的末尾而不是开始处绘制网格线?另外,为了更具体的帮助,考虑创建和发布一个显示您问题的AN。我这样做了,但问题是当子组件重新绘制自己时,父组件(在层次结构中的最远的地方,如果没有对GePARTANCE(未知)的未知调用,就不能到达它)。我将尝试获取一个工作代码来显示在这里。@Rafael Steil请查看我的edit@RafaelSteil,您在下面的评论中一直在谈论滚动窗格,但您的SSCCE没有滚动窗格,因此它如何准确反映您试图解决的问题?我尝试过,但(至少在我的测试中),当滚动窗格的子组件重新绘制自身时,不会调用视口的stageChanged(属于ChangeListener)(仅当我执行其他操作时,如使用滚动条)。如果我的子组件固定在它们的位置上,它会工作,但问题是用户可能会将它们拖到绘图上area@RafaelSteil这个理论性的问题,请以演示您的问题的形式发布代码,我创建了一个非常小且简单的例子来说明这个问题。我理解布局管理器和其他一切是如何工作的,你不能要求SSCCE并期望它遵守所有的最佳实践和模式,而这些实践和模式根本无法解释问题。无论如何,我在这里找到了我的解决方案,在这里我绘制到滚动窗格,并将它的引用传递给触发重绘事件的任何人。虽然没有我想要的那么圆滑,但效果不错。(请不要误会我的意思,我感谢您的帮助。这里的讨论帮助我找到了一个替代解决方案。):-)等一下,我在JPanel上放置了一些带有红线的JComponents
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Stroke;
import java.awt.event.ActionEvent;

import javax.swing.*;

@SuppressWarnings("serial")
public class GridTest2 extends JPanel {
   private static final Stroke LINE_STROKE = new BasicStroke(3f);
   private boolean drawInPaintComponent = false;

   public GridTest2() {
      final JPanel panel = new JPanel() {
         @Override
         public void repaint() {
            JRootPane rootPane = SwingUtilities.getRootPane(this);
            if (rootPane != null) {
               JPanel contentPane = (JPanel) rootPane.getContentPane();
               contentPane.repaint();
            }
         }
      };
      panel.setBackground(Color.white);
      panel.setPreferredSize(new Dimension(100, 100));

      JPanel biggerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
      biggerPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 0, 0));
      biggerPanel.setOpaque(false);
      biggerPanel.add(panel);

      JButton resetButton = new JButton(new AbstractAction("Reset") {
         public void actionPerformed(ActionEvent arg0) {
            panel.repaint();
         }
      });
      JPanel btnPanel = new JPanel();
      btnPanel.add(resetButton);

      setLayout(new BorderLayout());
      add(biggerPanel, BorderLayout.CENTER);
      add(btnPanel, BorderLayout.SOUTH);
   }

   @Override
   public Dimension getPreferredSize() {
      return new Dimension(300, 300);
   }

   @Override
   protected void paintComponent(Graphics g) {
      super.paintComponent(g);
      if (drawInPaintComponent ) {
         drawRedLine(g);
      }
   }

   @Override
   public void paint(Graphics g) {
      super.paint(g);
      if (!drawInPaintComponent ) {
         drawRedLine(g);
      }
   }

   private void drawRedLine(Graphics g) {
      Graphics2D g2 = (Graphics2D) g;
      g2.setStroke(LINE_STROKE);
      g2.setColor(Color.red);
      g2.drawLine(0, 50, getWidth(), 50);
   }

   private static void createAndShowGui() {
      GridTest2 mainPanel = new GridTest2();

      JFrame frame = new JFrame("GridTest2");
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.getContentPane().add(mainPanel);
      frame.pack();
      frame.setLocationByPlatform(true);
      frame.setVisible(true);
   }

   public static void main(String[] args) {
      SwingUtilities.invokeLater(new Runnable() {
         public void run() {
            createAndShowGui();
         }
      });
   }
}
@Override
protected void paintChildren(Graphics g){
    super.paintChildren(g);
    paintGrid(g);
}