Keeping draw graphics - removing super.paintComponent

前端 未结 2 1132
眼角桃花
眼角桃花 2021-01-22 02:56

I have a class named Foo that extends a class named Bar that extends JPanel and implements ActionListener. When I select Circle and click the draw button, I draw a circle, and w

相关标签:
2条回答
  • 2021-01-22 03:12

    Along side all of Hovercraft's comments

    The Graphics context is shared between components. One of the tasks of the super.paintComponent is to "clean" the graphics context before painting on it.

    This is why you're seeing two version of your buttons...

    I would also do a number of things differently. This should help with extendability and reuse over time, as well also reduce the logic a little.

    I would...

    • Abstract the shapes to a basic "shape" class that has the minimum requirements, such as fill and stroke color, location, size, stroke, etc and know how to paint itself.
    • I would create a model of some kind that would allow you to separate and define the boundaries of responsibility. It's not the responsibility of the component to "manage" the shapes, it only cares about painting them on it's surface. Equally, the component doesn't care about what the "shape" is, it only wants to know about how they are to be painted...
    • I would use Actions to simply the creation of those shapes and adding them to the model...

    enter image description here

    I've only created a triangle shape (and it has no attributes beyond location and size), but I'm sure you'll get the general idea...(ps you'll need to supply your own triangle icon for the action ;))

    public class DrawMe {
    
        public static void main(String[] args) {
            new DrawMe();
        }
    
        public DrawMe() {
    
            EventQueue.invokeLater(new Runnable() {
                @Override
                public void run() {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException ex) {
                    } catch (InstantiationException ex) {
                    } catch (IllegalAccessException ex) {
                    } catch (UnsupportedLookAndFeelException ex) {
                    }
    
                    JFrame frame = new JFrame();
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.setLayout(new BorderLayout());
    
                    DrawModel model = new DefaultDrawModel();
                    model.addElement(new Triangle(new Rectangle(10, 10, 100, 100)));
                    DrawPane drawPane = new DrawPane(model);
    
                    JToolBar toolBar = new JToolBar();
                    toolBar.add(new AddTriangleAction(model));
                    frame.add(toolBar, BorderLayout.NORTH);
    
                    frame.add(drawPane);
                    frame.setSize(400, 400);
                    frame.setVisible(true);
                }
            });
    
        }
    
        /**
         * Simple action used to add triangles to the model...the model acts
         * as a bridge between the action and the UI.
         */
        protected class AddTriangleAction extends AbstractAction {
    
            private DrawModel model;
    
            public AddTriangleAction(DrawModel model) {
                // Supply your own icon
                putValue(SMALL_ICON, new ImageIcon(getClass().getResource("/shape_triangle.png")));
                this.model = model;
            }
    
            public DrawModel getModel() {
                return model;
            }
    
            @Override
            public void actionPerformed(ActionEvent e) {
                // Randomly add the triangles...
                int x = (int)(Math.random() * 400);
                int y = (int)(Math.random() * 400);
                model.addElement(new Triangle(new Rectangle(x, y, 100, 100)));
            }
    
        }
    
        /**
         * This is the background pane, from which the draw pane extends...
         */
        protected class BackgroundPane extends JPanel {
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
    
                int x = getWidth() / 2;
                int y = getHeight() / 2;
    
                Graphics2D g2d = (Graphics2D) g.create();
                RadialGradientPaint rgp = new RadialGradientPaint(
                        new Point(x, y),
                        Math.max(getWidth(), getHeight()),
                        new float[]{0f, 1f},
                        new Color[]{Color.GRAY, Color.WHITE}
                        );
    
                g2d.setPaint(rgp);
                g2d.fill(new Rectangle(0, 0, getWidth(), getHeight()));
    
                g2d.setBackground(Color.BLACK);
                g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
    
                g2d.dispose();
            }
        }
    
        /**
         * This is a simple model, I stole the list model because it was quicker
         * and easier to demonstrate (don't need to write all the listeners)
         */
        public interface DrawModel extends ListModel<DrawMeShape> {
            public void addElement(DrawMeShape shape);
            public void removeElement(DrawMeShape shape);
        }
    
        /**
         * A default implementation of the DrawModel...
         */
        public class DefaultDrawModel extends DefaultListModel<DrawMeShape> implements DrawModel {
            @Override
            public void removeElement(DrawMeShape shape) {
                removeElement((Object)shape);
            }
        }
    
        /**
         * The actually "canvas" that shapes are rendered to
         */
        protected class DrawPane extends BackgroundPane {
    
            // Should provide ability to setModel...
            private DrawModel model;
    
            public DrawPane(DrawModel model) {
                this.model = model;
                model.addListDataListener(new ListDataListener() {
    
                    @Override
                    public void intervalAdded(ListDataEvent e) {
                        repaint();
                    }
    
                    @Override
                    public void intervalRemoved(ListDataEvent e) {
                        repaint();
                    }
    
                    @Override
                    public void contentsChanged(ListDataEvent e) {
                        repaint();
                    }
                });
            }
    
            public DrawModel getModel() {
                return model;
            }
    
            @Override
            protected void paintComponent(Graphics g) {
    
                super.paintComponent(g);
    
                // Draw the shapes from the model...
                Graphics2D g2d = (Graphics2D) g.create();
                DrawModel model = getModel();
                for (int index = 0; index < model.getSize(); index++) {
                    DrawMeShape shape = model.getElementAt(index);
                    shape.paint(g2d, this);
                }
    
                g2d.dispose();
    
            }
    
        }
    
        /**
         * A abstract concept of a shape.  Personally, if I was doing it, I would
         * generate an interface first, but this is just a proof of concept...
         */
        public abstract class DrawMeShape {
    
            private Rectangle bounds;
    
            public void setBounds(Rectangle bounds) {
                this.bounds = bounds;
            }
    
            public Rectangle getBounds() {
                return bounds;
            }
    
            protected abstract Shape getShape();
    
            /**
             * The shape knows how to paint, but it needs to know what to paint...
             * @param g2d
             * @param parent 
             */
            public void paint(Graphics2D g2d, JComponent parent) {
                g2d = (Graphics2D) g2d.create();
                Rectangle bounds = getBounds();
                Shape shape = getShape();
                g2d.translate(bounds.x, bounds.y);
                g2d.setColor(Color.DARK_GRAY);
                g2d.fill(shape);
                g2d.setColor(Color.BLACK);
                g2d.draw(shape);
                g2d.dispose();
            }
    
        }
    
        /**
         * An implementation of a Triangle shape...
         */
        public class Triangle extends DrawMeShape {
    
            public Triangle(Rectangle bounds) {
                setBounds(bounds);
            }
    
            @Override
            protected Shape getShape() {
                // This should be cached ;)
                Path2D path = new Path2D.Float();
                Rectangle bounds = getBounds();
    
                path.moveTo(bounds.width / 2, 0);
                path.lineTo(bounds.width, bounds.height);
                path.lineTo(0, bounds.height);
                path.lineTo(bounds.width / 2, 0);
                path.closePath();
    
                return path;
            }
        }
    }
    

    Happy drawing...

    0 讨论(0)
  • 2021-01-22 03:20

    Suggestions:

    • Don't remove super.paintComponent(g) as it has a necessary important role to play.
    • Instead why not draw to a BufferedImage and then draw that BufferedImage in the paintComponent(...) method override.
    • Then if you want to erase drawn images, simply create a new BufferedImage, or draw over it.

    As an aside, don't compare Strings using ==. Use the equals(...) or the equalsIgnoreCase(...) method instead. Understand that == checks if the two objects are the same which is not what you're interested in. The methods on the other hand check if the two Strings have the same characters in the same order, and that's what matters here. So instead of

    if (fu == "bar") {
      // do something
    }
    

    do,

    if ("bar".equals(fu)) {
      // do something
    }
    

    or,

    if ("bar".equalsIgnoreCase(fu)) {
      // do something
    }
    
    0 讨论(0)
提交回复
热议问题