绘制所有其他组件(Swing,Java)

时间:2011-11-24 17:11:33

标签: java swing drawing java-2d

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

程序结构是这样的:JFrame - > JPanel - > JScrollPane - > JPanel - > [许多其他JPanels,就像层]

作为测试,我将绘制代码添加到JFrame,它正确显示我的Line2D实例在其他所有内容之上。但是,当我在子组件中执行任何需要该子项重新绘制的内容时,JFrame中绘制的线将被删除。

我知道这是预期的Swing行为 - 也就是说,它只会重绘那些已经改变的区域。但是,我正在寻找一些方法,不断在其他所有方面绘制线网格线。

我能够让它工作的唯一方法是使用Swing Timer每隔10ms在我的根组件上调用repaint(),但它消耗了大量的CPU。

更新
下面是一个例子的工作代码。请注意,在我的实际应用程序中,我有许多可以触发重绘()的不同组件,并且没有一个组件引用了执行网格线绘制的组件(当然我可以将它传递给每个人,但是是最新的选择)

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Line2D;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class GridTest extends JFrame {
    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();
        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) {
                // When I call repaint() here, the paint() method of
                // JFrame it's not called, thus resulting in part of the
                // red line to be erased / overridden.

                // In my real application application, I don't have
                // easy access to the component that draws the lines
                p.repaint();
            }
        });
        b.setBounds(0, 150, 100, 30);
        add(b);

        pack();
        setVisible(true);
    }

    @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();
    }
}

5 个答案:

答案 0 :(得分:5)

如果您要在放置到JComponents的{​​{1}}上绘画,那么您可以绘制到JScrollPane,例如here

编辑:

1)将您的代码绘制到错误的容器,JViewPort,确保可以绘制到JFrame,但您必须提取RootPane or GlassPane

2)你必须学习LayoutManagers如何工作,我让你的代码用原始的大小调整,不是很好而且非常糟糕

3)画到JFrameGlassPane

JViewPort

编辑:2,如果您期望单行,则在固定边界

enter image description here enter image description here enter image description here

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);
    }
}

答案 1 :(得分:4)

一种可能的解决方案是覆盖JPanel的repaint方法,以便它调用contentPane的重绘方法。另一点是你可能不应该直接在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();
         }
      });
   }
}

答案 2 :(得分:1)

假设父框架已经有一个它必须绘制的所有网格线的列表,你可以做的是让每个子框架绘制它自己的线条的个人位。在伪代码中:

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

答案 3 :(得分:1)

Swing在JFrames(和类似组件)中使用JLayeredPane。使用分层窗格,您可以在主要内容上定位仅绘画组件。

This code使用放置在JLayeredPane中的组件来定位(并自动重绘)任何组件主要内容之上的任意装饰,从而避免需要覆盖paint()方法任何给定的组件。

答案 4 :(得分:1)

我知道这是一个老帖子,但我最近也遇到了同样的问题...... 您应该覆盖green gpworv1.html 5 1 0 0 1.1kb 575b green rgspaa1.html 5 1 0 0 1.1kb 575b green raeeno1.html 5 1 0 0 1.1kb 575b green nnwrsa1.html 5 1 0 0 1.1kb 575b green nrrvwo1.html 5 1 0 0 1.1kb 575b 而不是paintChildrenpaint。 来自JComponent.paint documentation

  

由Swing调用以绘制组件。应用程序不应直接调用绘制,而应使用重绘方法来调度组件以进行重绘   此方法实际上将绘制工作委托给三个受保护的方法:paintComponent,paintBorder和paintChildren。 按照列出的顺序调用它们以确保子项显示在组件本身之上。一般来说,组件及其子组件不应在分配给边框的insets区域中绘制。子类可以像往常一样覆盖此方法。一个只想专门化UI(外观)委托的paint方法的子类应该只覆盖paintComponent。

所以,如果你

paintComponent

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