javaswingpaintcomponent

Painting method paints things from other components


I am trying to make a simple Java program with GUI using Java Swing.

I have painting panel (gPanel) in the center of the screen, panel with buttons (buttonSet) in the west and panel with labels (labelPanel) in the east. To paint over gPanel I use paintComponent method and since I have two buttons, which are supposed to draw different things (and change label on the right of the screen), I decided to put switch case in paintComponent method for it to choose the correct actual painting method.

When I run the program everything looks fine - program uses the first method to paint and there is a sampletext.png image shown in the middle of the screen with yellow background, as it should be. Button number 1 also uses this method to draw over gPanel, so pressing it draws the same thing.

Now Button number 2 uses the second painting method and this is where things go wrong. It draws sampleimage.png over the gPanel, but also parts of left and right panels (i.e. buttons from left buttonSet panel and orange colour that is background colour of side panels) are drawn, though it shouldn't happen. Also the whole gPanel becomes gray (I think it happens because of label on the right that becomes very long after pressing Button number 2, because when the label was shorter gPanel didn't turn gray and left the previously drawn things instead).

Pressing Button number 1 paints things from first method properly, so pressing it after pressing Button number 2 "reverts" the changes.

What do I have to do to make my second painting method work properly? Also why adding border to buttonSet and labelPanel works but adding it to gPanel doesn't?

    package com.inferjus.drawingproject;
    
    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.image.*;
    import java.io.*;
    import javax.imageio.*;
    import javax.swing.border.*;
    
    /**
     *
     * @author inferjus
     */
    
    public class DrawingProject
    {
        private JFrame frame;
        private graphicPanel gPanel;
        private JPanel buttonSet;
        private JPanel labelPanel;
        private JLabel label;
        private int painter=0;
        
        public static void main(String[] args)
        {
            DrawingProject program=new DrawingProject();
            program.prepareGUI();
        }
        
        public int getPainter()
        {
            return painter;
        }
        
        public void setPainter(int x)
        {
            painter=x;
        }
        
        public void prepareGUI()
        {
            //setting JFrame and icon
            frame=new JFrame("Drawing Project");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
                try { frame.setIconImage(ImageIO.read(getClass().getResource("/resources/sampleicon.png")));} 
                catch (IOException e) { e.printStackTrace(); }
    
            //border for components
            Border bigBlackBorder=new LineBorder(Color.black, 3);
                
            //setting JPanel (graphicPanel) for drawing images
            gPanel=new graphicPanel();
                gPanel.setBorder(bigBlackBorder); // <--- why it does not work?
                
    
            //setting JPanel for buttons on the left of the screen
            buttonSet=new JPanel();
                buttonSet.setLayout(new BoxLayout(buttonSet, BoxLayout.Y_AXIS));
                buttonSet.setBorder(bigBlackBorder);
    
            //setting JButtons
            JButton buttonOne=new JButton("Button number 1");
                buttonOne.addActionListener(new buttonOneListener());
                buttonSet.add(buttonOne);
                buttonSet.setBackground(Color.orange);
            JButton buttonTwo=new JButton("Button number 2");
                buttonTwo.addActionListener(new buttonTwoListener());
                buttonSet.add(buttonTwo);
    
            //setting JLabels on the right of the screen
            label=new JLabel("Default label");
                label.setFont(new Font("Consolas", Font.PLAIN, 20));
            labelPanel=new JPanel();
                labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.Y_AXIS));
                labelPanel.setBackground(Color.orange);
                labelPanel.setBorder(bigBlackBorder);
            JLabel popeLabelTitle=new JLabel("What does the label say?");
                popeLabelTitle.setFont(new Font("Consolas", Font.BOLD, 24));
            
            //adding JLabels to labelPanel
            labelPanel.add(BorderLayout.NORTH, popeLabelTitle);
            labelPanel.add(BorderLayout.CENTER, label);
            
            //adding components to JFrame
            frame.getContentPane().add(BorderLayout.CENTER, gPanel);
            frame.getContentPane().add(BorderLayout.EAST, labelPanel);
            frame.getContentPane().add(BorderLayout.WEST, buttonSet);
            frame.setVisible(true);
        }
        
        class graphicPanel extends JPanel
        {
            private BufferedImage sampletext=null;
            private BufferedImage sampleimage=null;
            
            @Override
            public void paintComponent(Graphics g)
            {
                //for Button One paint sampletext.png, for Button Two paint sampleimage.png
                switch (painter)
                {
                case 0:
                    paintSampletext(g);
                    break;
                    
                case 1:
                    paintSampleimage(g);
                    break;
                }
            }
            
            //paint yellow background and put sampletext.png in the middle
            private void paintSampletext(Graphics g)
            {
                if (sampletext==null)
                {
                    gPanel.setSampletextPNG();
                }
                g.setColor(Color.yellow);
                g.fillRect(0,0, gPanel.getWidth(), gPanel.getHeight());
                g.drawImage(sampletext, gPanel.getWidth()/2-sampletext.getWidth()/2, gPanel.getHeight()/2-sampletext.getHeight()/2, this);          
                g.setColor(Color.black);
                g.drawRect(gPanel.getWidth()/2-sampletext.getWidth()/2, gPanel.getHeight()/2-sampletext.getHeight()/2, sampletext.getWidth(), sampletext.getHeight());
                g.dispose();
            }
            
            //paint sampleimage.png over what is already displayed
            private void paintSampleimage(Graphics g)
            {
                if (sampleimage==null)
                {
                    gPanel.setSampleimagePNG();
                }
                int x=(int)((Math.random()*gPanel.getWidth())-sampleimage.getWidth());
                int y=(int)((Math.random()*gPanel.getHeight())-sampleimage.getHeight());
                g.drawImage(sampleimage, x, y, gPanel);
                g.dispose();
            }
            
            public void setSampletextPNG()
            {
                try { sampletext=ImageIO.read(getClass().getResource("/resources/sampletext.png")); }
                catch (IOException ex) { System.out.println("Image error"); }            
            }
            
            public void setSampleimagePNG()
            {
                try { sampleimage=ImageIO.read(getClass().getResource("/resources/sampleimage.png")); }
                catch (IOException ex) { System.out.println("Image error"); }
            }
        }
        
        class buttonOneListener implements ActionListener
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                label.setText("Reaction to button number 1: change of label.");
                setPainter(0);
                gPanel.repaint();
            }
        }
        
        class buttonTwoListener implements ActionListener 
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                label.setText("Reaction to button number 2: change of label + drawing images over gPanel.");
                setPainter(1);
                gPanel.repaint();
            }
        }
    }

Tree of my project:

what shows after running the program by default or after pressing Button One

what shows after pressing Button Two one time

what shows after pressing Button Two a few times


Solution

  • Introduction

    Oracle has a helpful tutorial, Creating a GUI With Swing. Skip the Learning Swing with the NetBeans IDE section.

    I went ahead and created the following GUI. I created two BufferedImages for the text image and the plain image so I wouldn't have to read any external files.

    enter image description here

    enter image description here

    Explanation

    When I create a Swing GUI, I use the model-view-controller pattern. This pattern allows me to separate my concerns and focus on one part of the application at a time.

    Model

    I created a model class to hold the button flag and the two BufferedImages. This is the class where you would read the resources.

    You can add the JFrame icon back to this class.

    Model classes are plain Java getter/setter classes.

    View

    All Swing applications must start with a call to the SwingUtilities invokeLater method. This method ensures that the Swing components are created and executed on the Event Dispatch Thread.

    Class names are written in camel case and start with an upper case character. Method names are written in camel case and start with a lower case character. Field names follow the same rules as method names.

    I separated the creation of the JFrame from the creation of the JPanels. This helps me to separate my concerns and makes it much easier to visually verify whether or not the code is correct. Aim to write short methods that do one thing and do it well.

    You have to manually draw a border on a graphic JPanel. I added the code to your paintComponent method to paint a partial border.

    Your paintComponent method should paint. Period. Nothing else. It must also start with a call to the super.paintComponent method to maintain the Swing paint chain.

    I changed your JLabel in the right JPanel to a JTextArea. A JTextArea allows for longer messages to word wrap on multiple lines and not make your JFrame change size.

    Controller

    Your JButton controller classes were fine, except for the class names.

    Code

    Here's the complete runnable code. I made all the additional classes inner classes so I could post the code in one block.

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Font;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    
    import javax.swing.BorderFactory;
    import javax.swing.BoxLayout;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JTextArea;
    import javax.swing.SwingUtilities;
    
    public class DrawingProject implements Runnable {
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new DrawingProject());
        }
    
        private final DrawingModel model;
    
        private GraphicPanel graphicPanel;
    
        private JTextArea textArea;
    
        public DrawingProject() {
            this.model = new DrawingModel();
        }
    
        @Override
        public void run() {
            JFrame frame = new JFrame("Drawing Project");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            graphicPanel = new GraphicPanel(model);
            frame.add(createButtonPanel(), BorderLayout.WEST);
            frame.add(graphicPanel, BorderLayout.CENTER);
            frame.add(createTextPanel(), BorderLayout.EAST);
    
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        private JPanel createButtonPanel() {
            JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
            panel.setBackground(Color.orange);
            panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 3));
    
            JButton buttonOne = new JButton("Button number 1");
            buttonOne.addActionListener(new ButtonOneListener());
            panel.add(buttonOne);
    
            JButton buttonTwo = new JButton("Button number 2");
            buttonTwo.addActionListener(new ButtonTwoListener());
            panel.add(buttonTwo);
    
            return panel;
        }
    
        private JPanel createTextPanel() {
            JPanel panel = new JPanel(new BorderLayout());
            panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 3));
    
            JLabel popeLabelTitle = new JLabel("What does the label say?");
            popeLabelTitle.setFont(new Font(Font.MONOSPACED, Font.BOLD, 24));
            panel.add(popeLabelTitle, BorderLayout.NORTH);
    
            textArea = new JTextArea(4, 30);
            textArea.setEditable(false);
            textArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 20));
            textArea.setText("Default label");
            textArea.setLineWrap(true);
            textArea.setWrapStyleWord(true);
            panel.add(textArea, BorderLayout.CENTER);
    
            return panel;
        }
    
        public class GraphicPanel extends JPanel {
    
            private static final long serialVersionUID = 1L;
    
            private final DrawingModel model;
    
            public GraphicPanel(DrawingModel model) {
                this.model = model;
                this.setPreferredSize(new Dimension(640, 480));
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
    
                // Paint border
                int width = getWidth();
                int height = getHeight();
                int lineThickness = 3;
                g.setColor(Color.BLACK);
                g.fillRect(0, 0, width, height);
                g.setColor(Color.YELLOW);
                g.fillRect(0, lineThickness, width, height - 2 * lineThickness);
    
                switch (model.getPainter()) {
                case 0:
                    paintSampleText(g);
                    break;
                case 1:
                    paintSampleImage(g);
                    break;
                }
            }
    
            private void paintSampleText(Graphics g) {
                BufferedImage image = model.getSampleText();
                int x = (getWidth() - image.getWidth()) / 2;
                int y = (getHeight() - image.getHeight()) / 2;
                g.drawImage(image, x, y, this);
            }
    
            private void paintSampleImage(Graphics g) {
                BufferedImage image = model.getSampleImage();
                int x = (int) ((Math.random() * getWidth()) - image.getWidth());
                int y = (int) ((Math.random() * getHeight()) - image.getHeight());
                g.drawImage(image, x, y, this);
            }
    
        }
    
        public class ButtonOneListener implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent e) {
                textArea.setText("Reaction to button number 1: change of label.");
                model.setPainter(0);
                graphicPanel.repaint();
            }
        }
    
        public class ButtonTwoListener implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent e) {
                textArea.setText("Reaction to button number 2: change of label + "
                        + "drawing images over gPanel.");
                model.setPainter(1);
                graphicPanel.repaint();
            }
        }
    
        public class DrawingModel {
    
            private int painter;
    
            private final BufferedImage sampleText;
            private final BufferedImage sampleImage;
    
            public DrawingModel() {
                this.painter = 0;
                this.sampleText = createBufferedImage(Color.BLUE);
                this.sampleImage = createBufferedImage(Color.MAGENTA);
            }
    
            private BufferedImage createBufferedImage(Color color) {
                BufferedImage image = new BufferedImage(64, 64,
                        BufferedImage.TYPE_INT_RGB);
                Graphics g = image.getGraphics();
                g.setColor(color);
                g.fillRect(0, 0, image.getWidth(), image.getHeight());
                g.dispose();
                return image;
            }
    
            public int getPainter() {
                return painter;
            }
    
            public void setPainter(int painter) {
                this.painter = painter;
            }
    
            public BufferedImage getSampleText() {
                return sampleText;
            }
    
            public BufferedImage getSampleImage() {
                return sampleImage;
            }
    
        }
    
    }
    

    Update

    In order to paint multiple images, you have to save the origin of the images in a List. I've modified the application model to hold a List of origin Point instances. I also corrected the code to create a random point.

    Here's the GUI with multiple images.

    enter image description here

    Here's the modified code

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Font;
    import java.awt.Graphics;
    import java.awt.Point;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.swing.BorderFactory;
    import javax.swing.BoxLayout;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.JTextArea;
    import javax.swing.SwingUtilities;
    
    public class DrawingProject implements Runnable {
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new DrawingProject());
        }
    
        private final DrawingModel model;
    
        private GraphicPanel graphicPanel;
    
        private JTextArea textArea;
    
        public DrawingProject() {
            this.model = new DrawingModel();
        }
    
        @Override
        public void run() {
            JFrame frame = new JFrame("Drawing Project");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            graphicPanel = new GraphicPanel(model);
            frame.add(createButtonPanel(), BorderLayout.WEST);
            frame.add(graphicPanel, BorderLayout.CENTER);
            frame.add(createTextPanel(), BorderLayout.EAST);
    
            frame.pack();
            frame.setLocationByPlatform(true);
            frame.setVisible(true);
        }
    
        private JPanel createButtonPanel() {
            JPanel panel = new JPanel();
            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
            panel.setBackground(Color.orange);
            panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 3));
    
            JButton buttonOne = new JButton("Button number 1");
            buttonOne.addActionListener(new ButtonOneListener());
            panel.add(buttonOne);
    
            JButton buttonTwo = new JButton("Button number 2");
            buttonTwo.addActionListener(new ButtonTwoListener());
            panel.add(buttonTwo);
    
            return panel;
        }
    
        private JPanel createTextPanel() {
            JPanel panel = new JPanel(new BorderLayout());
            panel.setBorder(BorderFactory.createLineBorder(Color.BLACK, 3));
    
            JLabel popeLabelTitle = new JLabel("What does the label say?");
            popeLabelTitle.setFont(new Font(Font.MONOSPACED, Font.BOLD, 24));
            panel.add(popeLabelTitle, BorderLayout.NORTH);
    
            textArea = new JTextArea(4, 30);
            textArea.setEditable(false);
            textArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 20));
            textArea.setText("Default label");
            textArea.setLineWrap(true);
            textArea.setWrapStyleWord(true);
            panel.add(textArea, BorderLayout.CENTER);
    
            return panel;
        }
    
        public class GraphicPanel extends JPanel {
    
            private static final long serialVersionUID = 1L;
    
            private final DrawingModel model;
    
            public GraphicPanel(DrawingModel model) {
                this.model = model;
                this.setPreferredSize(new Dimension(640, 480));
            }
    
            @Override
            protected void paintComponent(Graphics g) {
                super.paintComponent(g);
    
                paintMyBorder(g);
    
                if (model.getPainter() == 1) {
                    createSampleImage(g);
                }
    
                paintSampleText(g);
                BufferedImage image = model.getSampleImage();
                List<Point> origin = model.getImageOrigin();
    
                for (Point point : origin) {
                    g.drawImage(image, point.x, point.y, this);
                }
            }
    
            private void paintMyBorder(Graphics g) {
                int width = getWidth();
                int height = getHeight();
                int lineThickness = 3;
                g.setColor(Color.BLACK);
                g.fillRect(0, 0, width, height);
                g.setColor(Color.YELLOW);
                g.fillRect(0, lineThickness, width, height - 2 * lineThickness);
            }
    
            private void paintSampleText(Graphics g) {
                BufferedImage image = model.getSampleText();
                int x = (getWidth() - image.getWidth()) / 2;
                int y = (getHeight() - image.getHeight()) / 2;
                g.drawImage(image, x, y, this);
            }
    
            private void createSampleImage(Graphics g) {
                BufferedImage image = model.getSampleImage();
                int x = (int) (Math.random() * (getWidth() - image.getWidth()));
                int y = (int) (Math.random() * (getHeight() - image.getHeight()));
                model.addNewImageOrigin(new Point(x, y));
            }
    
        }
    
        public class ButtonOneListener implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent e) {
                textArea.setText("Reaction to button number 1: change of label.");
                model.setPainter(0);
                graphicPanel.repaint();
            }
        }
    
        public class ButtonTwoListener implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent e) {
                textArea.setText("Reaction to button number 2: change of label + "
                        + "drawing images over gPanel.");
                model.setPainter(1);
                graphicPanel.repaint();
            }
        }
    
        public class DrawingModel {
    
            private int painter;
    
            private final BufferedImage sampleText;
            private final BufferedImage sampleImage;
    
            private final List<Point> imageOrigin;
    
            public DrawingModel() {
                this.painter = 0;
                this.sampleText = createBufferedImage(Color.BLUE);
                this.sampleImage = createBufferedImage(Color.MAGENTA);
                this.imageOrigin = new ArrayList<>();
            }
    
            private BufferedImage createBufferedImage(Color color) {
                BufferedImage image = new BufferedImage(64, 64,
                        BufferedImage.TYPE_INT_RGB);
                Graphics g = image.getGraphics();
                g.setColor(color);
                g.fillRect(0, 0, image.getWidth(), image.getHeight());
                g.dispose();
                return image;
            }
    
            public void addNewImageOrigin(Point point) {
                this.imageOrigin.add(point);
            }
    
            public int getPainter() {
                return painter;
            }
    
            public void setPainter(int painter) {
                this.painter = painter;
            }
    
            public BufferedImage getSampleText() {
                return sampleText;
            }
    
            public BufferedImage getSampleImage() {
                return sampleImage;
            }
    
            public List<Point> getImageOrigin() {
                return imageOrigin;
            }
    
        }
    
    }