1

Below is my code to draw a swimming pool and at the same time calculate its volume.

The method in question is paintSwimmingPool(), which works fine when called by stateChanged() listener method. However, the problem is that I want to draw the rectangle upon launching the program. I have added in some console message to help debug which tells me that the method is called correctly from createGUI() method. But why paintSwimmingPool() does not draw anything on the panel? Sorry I have spent quite some time on solving this but got no where.

Thank you in advance for any pointers.

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.Graphics;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.JTextField;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SwimingPoolCalculator implements ChangeListener {

    private JFrame frame;
    private JPanel primaryJPanel, drawingJPanel;
    private JLabel deepJLabel, shallowJLabel;
    private JSlider deepJSlider, shallJSlider;
    private JTextField resultTextField;

    final private int xPos = 20;
    final private int yPos = 20;
    final private int swimmingPoolLength = 200;
    final private int swimmingPoolWidth = 50;

    public static void main(String[] args) {

        SwimingPoolCalculator swimingPoolCalculator = new SwimingPoolCalculator();

        swimingPoolCalculator.createGUI();

    }

    private void createGUI(){

        frame = new JFrame("Swiming Pool Calculator");

        primaryJPanel = new JPanel();
        primaryJPanel.setPreferredSize(new Dimension(250, 500));

        drawingJPanel = new JPanel();
        drawingJPanel.setPreferredSize(new Dimension(250, 300));
        drawingJPanel.setBackground(Color.yellow);

        shallowJLabel = new JLabel("S End");
        deepJLabel = new JLabel("D End");

        shallJSlider = new JSlider(JSlider.HORIZONTAL, 10, 100, 10);
        deepJSlider = new JSlider(JSlider.HORIZONTAL, 50, 200, 100);

        shallJSlider.addChangeListener(this);
        deepJSlider.addChangeListener(this);

        resultTextField = new JTextField(15);

        primaryJPanel.add(drawingJPanel);
        primaryJPanel.add(shallowJLabel);
        primaryJPanel.add(shallJSlider);
        primaryJPanel.add(deepJLabel);
        primaryJPanel.add(deepJSlider);
        primaryJPanel.add(resultTextField);

        frame.getContentPane().add(primaryJPanel);
        frame.pack();
        frame.setVisible(true);

        // why the first call does not draw any line?
        paintSwimmingPool(deepJSlider.getValue(), shallJSlider.getValue());

    }

    private void paintSwimmingPool(int deepEnd, int shallowEnd){

        Graphics drawingBoard = drawingJPanel.getGraphics();

        drawingBoard.setColor(Color.yellow);
        drawingBoard.fillRect(0, 0, 250, 250);
        drawingBoard.setColor(Color.black);

        drawingBoard.drawLine(xPos, yPos, xPos + swimmingPoolLength, yPos);  // the top line
        drawingBoard.drawLine(xPos, yPos, xPos, yPos + deepEnd); // the left line
        drawingBoard.drawLine(xPos + swimmingPoolLength, yPos, xPos + swimmingPoolLength, yPos + shallowEnd); // the right line
        drawingBoard.drawLine(xPos, yPos + deepEnd, xPos + swimmingPoolLength, yPos + shallowEnd); // the bottom line

    }

    private void calculateVolume(double averageDepth){

        int swimmingPoolVolume = (int)(averageDepth * swimmingPoolLength * swimmingPoolWidth);

        resultTextField.setText("Volume: " + swimmingPoolVolume);

    }

    @Override
    public void stateChanged(ChangeEvent e) {

        int shallowEnd = shallJSlider.getValue();
        int deepEnd = deepJSlider.getValue();

        double averageDepth = (shallowEnd + deepEnd) / 2;

        paintSwimmingPool(deepEnd, shallowEnd);

        calculateVolume(averageDepth);

    }

}
Dennisboys
  • 583
  • 3
  • 9
  • 22
  • possible duplicate of [Drawing an object using getGraphics() without extending JFrame](http://stackoverflow.com/questions/15986677/drawing-an-object-using-getgraphics-without-extending-jframe) – user253751 Feb 01 '15 at 08:55
  • Take a look at [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) and [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/) – MadProgrammer Feb 01 '15 at 09:01

2 Answers2

3

You cannot draw like this, the correct technique is

  • override the paintComponent() method in a JPanel subclass and use the Graphics parameter instead.
  • call repaint() to trigger a repaint instead of your paintSwimmingPool() method
  • move parameters i.e. deepEnd and shallowEnd into fields and reference these from your paintComponent() method - the inner class we be able to 'see' these fields. Ideally these would be in a separate model shared by the drawing panel and the control code, but the inner class gets the job done.
  • Look at the oracle tutorial on custom painting

Fully worked example

public class SwimingPoolCalculator implements ChangeListener {

    private JFrame frame;
    private JPanel primaryJPanel, drawingJPanel;
    private JLabel deepJLabel, shallowJLabel;
    private JSlider deepJSlider, shallJSlider;
    private JTextField resultTextField;

    final private int xPos = 20;
    final private int yPos = 20;
    final private int swimmingPoolLength = 200;
    final private int swimmingPoolWidth = 50;
    private int deepEnd;   // <== moved to fields
    private int shallowEnd;  // <== moved to fields

    private class InnerDrawingPanel extends JPanel {
        @Override
        protected void paintComponent(Graphics drawingBoard) {
            drawingBoard.setColor(Color.yellow);   // <== drawing code now here
            drawingBoard.fillRect(0, 0, 250, 250);
            drawingBoard.setColor(Color.black);

            drawingBoard.drawLine(xPos, yPos, xPos + swimmingPoolLength, yPos);  // the top line
            drawingBoard.drawLine(xPos, yPos, xPos, yPos + deepEnd); // the left line
            drawingBoard.drawLine(xPos + swimmingPoolLength, yPos, xPos + swimmingPoolLength, yPos + shallowEnd); // the right line
            drawingBoard.drawLine(xPos, yPos + deepEnd, xPos + swimmingPoolLength, yPos + shallowEnd); // the bottom line
        }
    }

    public static void main(String[] args) {

        SwimingPoolCalculator swimingPoolCalculator = new SwimingPoolCalculator();

        swimingPoolCalculator.createGUI();

    }

    private void createGUI(){

        frame = new JFrame("Swiming Pool Calculator");

        primaryJPanel = new JPanel();
        primaryJPanel.setPreferredSize(new Dimension(250, 500));

        drawingJPanel = new InnerDrawingPanel();  // <== use inner class
        drawingJPanel.setPreferredSize(new Dimension(250, 300));
        drawingJPanel.setBackground(Color.yellow);

        shallowJLabel = new JLabel("S End");
        deepJLabel = new JLabel("D End");

        shallJSlider = new JSlider(JSlider.HORIZONTAL, 10, 100, 10);
        deepJSlider = new JSlider(JSlider.HORIZONTAL, 50, 200, 100);

        shallJSlider.addChangeListener(this);
        deepJSlider.addChangeListener(this);

        resultTextField = new JTextField(15);

        primaryJPanel.add(drawingJPanel);
        primaryJPanel.add(shallowJLabel);
        primaryJPanel.add(shallJSlider);
        primaryJPanel.add(deepJLabel);
        primaryJPanel.add(deepJSlider);
        primaryJPanel.add(resultTextField);

        frame.getContentPane().add(primaryJPanel);
        frame.pack();
        frame.setVisible(true);

        // why the first call does not draw any line?
        shallowEnd = deepJSlider.getValue(); // <== now update fields
        deepEnd = shallJSlider.getValue();
        drawingJPanel.repaint();        // <== trigger repaint

    }


    private void calculateVolume(double averageDepth){

        int swimmingPoolVolume = (int)(averageDepth * swimmingPoolLength * swimmingPoolWidth);

        resultTextField.setText("Volume: " + swimmingPoolVolume);

    }

    @Override
    public void stateChanged(ChangeEvent e) {

        shallowEnd = shallJSlider.getValue();  // <== update fields
        deepEnd = deepJSlider.getValue();

        double averageDepth = (shallowEnd + deepEnd) / 2;


        drawingJPanel.repaint();   // <== trigger repaint
        calculateVolume(averageDepth);

    }

}
Adam
  • 35,919
  • 9
  • 100
  • 137
  • (cough)`super.paintComponent`(cough) – MadProgrammer Feb 01 '15 at 09:01
  • 1
    `g.drawImage(image, 0, 0, null);` should be `g.drawImage(image, 0, 0, this);` as `JPanel` implements the `ImageObserver` interface via `JComponent`. There could also be an issue with not disposing of a `Graphics` context that you create (`image.getGraphics();`) on some systems, but that's a workflow issue. – MadProgrammer Feb 01 '15 at 09:12
  • Hi @Adam, but I have another question. I set up another class and try to paint the swimming pool in it. However, how can I pass the parameters needed (like xPos, yPos) to paintComponent(Graphics g)? I could not work out using the technique you shown above. Can you be more specific? Thank you. – Dennisboys Feb 01 '15 at 10:30
  • I've reworked my answer to show you exactly what to do with the parameters, i.e. not have parameters and use shared state instead. Ideally these would be in a formal model shared by the drawing panel and the control code, but the inner class I've used gets the job done. – Adam Feb 01 '15 at 12:19
0

Swing is probably repainting the JPanel after you painted on it. Swing can repaint components at any time, for any reason it feels like. For this reason, you should not use getGraphics.

Instead, you need to create your own component class (extending an existing component, such as JPanel), and override paintComponent.

See this question for more details.

Community
  • 1
  • 1
user253751
  • 57,427
  • 7
  • 48
  • 90