2011-11-24 152 views
3

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

程序结构是这样的:的JFrame - >的JPanel - > JScrollPane中 - >的JPanel - > [许多其他JPanels,它们是类似的层]

作为测试,我添加绘制代码的JFrame,它能正确显示我的Line2D实例在其他任何位置上。但是,当我在需要该子进行重绘的子组件中执行任何操作时,JFrame中绘制的线将被删除。

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

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

UPDATE
工作的例子的代码如下。请注意,在我的真实应用程序中,我有几十个可能触发重绘(repaint)的不同组件,它们都没有引用网格线绘制的组件(当然,我可以将它传递给每个人)是最新的选项)

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

为什么不能在的paintComponent方法,而不是开始的结束画网格线?另外,要获得更具体的帮助,请考虑创建并发布显示问题的[SSCCE](http://sscce.org)。 –

+0

我这样做了,但问题是,当一个子组件重新绘制自己时,父组件(位于层次结构中最远端的组件,如果没有未知的getParent()调用)不能访问它。我会尝试获得一个工作代码在这里展示。 –

+0

@Rafael Steil请参阅我的编辑 – mKorbel

回答

5

,如果你想画在JComponents放置到JScrollPane,那么你可以画到JViewPort,例如here

编辑:

1)怎么一回事,因为你的代码画到错误的容器,到JFrame,一定可以画到JFrame,但你必须提取RootPane or GlassPane

2),你必须学会​​如何LayoutManagers的作品,我让跟原来的大小调整,不是好和非常坏的代码

3 )油漆到GlassPaneJViewPort

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,如果你希望单行线,在固定的界限

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

import java.awt.*; 
import java.awt.event.*; 
import java.awt.geom.Line2D; 
import javax.swing.*; 
import javax.swing.border.LineBorder; 

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

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

    private void run() { 
     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(); 
      } 
     }; 
     JPanel p1 = new JPanel(); 
     p1.setBorder(new LineBorder(Color.black,1)); 
     JPanel p2 = new JPanel(); 
     p2.setBorder(new LineBorder(Color.black,1)); 
     JPanel p3 = new JPanel(); 
     p3.setBorder(new LineBorder(Color.black,1)); 
     p.setLayout(new GridLayout(3,0)); 
     p.add(p1); 
     p.add(p2); 
     p.add(p3); 
     p.setBounds(20, 20, 100, 100); 
     p.setBackground(Color.white); 
     add(p, BorderLayout.CENTER); 

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

      @Override 
      public void actionPerformed(ActionEvent e) { 
       p.repaint(); 
      } 
     }); 
     add(b, BorderLayout.SOUTH); 

     pack(); 
     setVisible(true); 
    } 
} 
+0

我试过了,但是(至少在我的测试中),当滚动窗格的子组件重新绘制自己时,viewPort的stageChanged(ChangeListener)不会被调用(只有当我执行其他操作时才使用滚动条)。如果我的孩子的组件被固定在他们的位置,它会工作,但问题是,用户可能拖曳他们在绘制区 –

+0

@Rafael Steil那非常理论的问题,请张贴代码在http://sscce.org/形式明确你的问题 – mKorbel

+0

好的,我创建了一个非常小而简单的例子来说明问题。 –

1

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

gridlines = getParentsGridLines() 
gridlines.offsetBasedOnRelativePosition() 
drawStuff() 
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(); 
     } 
     }); 
    } 
} 
1

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

This code使用放置在的JLayeredPane内定位(和自动重画)任意装饰品主内容的任何部件的上方,从而不需要覆盖任何给定组分的涂料()方法的组件。

0

我知道这是旧的文章,但我已经最近同样的问题...
你应该重写paintChildren而不是paintpaintComponent。 从JComponent.paint documentation

援引摆动,以绘制组件。应用程序不应该直接调用paint,而应该使用repaint方法来调度组件以进行重绘。
此方法实际上将绘画作品委托给三个受保护的方法:paintComponent,paintBorder和paintChildren。 以列出的顺序调用它们以确保儿童出现在组件本身之上。一般而言,组件及其子女不应在分配给边界的插入区域上绘画。一如以往,子类可以重写此方法。只想要专门化UI(外观)委托的绘画方法的子类应该重写paintComponent。

所以,如果你

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

电网将在你的子组件的顶部^^