Search code examples
javaswingjava-2dcustom-painting

Draw on one side of a JPanel


I want to program an application that lets you draw circles with a mouse click on the left side of a JFrame, and all the points are getting "mirrored" to the right side. The first problem I encountered was that when I try to implement this draw-mechanic in my frame, no circles appear.

public class Application{
  int x,y;
  private JPanel container;

  public static void main(String[] args)
  {
    SwingUtilities.invokeLater(new Runnable() {
      @Override
      public void run() {
        new Application().gui();
      }
    });  
  }

  public void gui()
  {
    int height = 250;
    int width = 700;
    JFrame jframe = new JFrame();
    container = new JPanel();
    container.setLayout(new BorderLayout());

    container.add(new DrawCircle(), BorderLayout.WEST);
    container.setVisible(true);
    jframe.add(container);
    //jframe.add(new DrawCircle());

    jframe.setSize(500,700);
    jframe.setVisible(true);
    jframe.setTitle("Title");
    jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    jframe.setResizable(false);

  } 
}

It works (on the whole frame) when I use container.add(new DrawCircle) but if I want to add constraints, it doesn't.

Here is the circle class:

public class DrawCircle extends JPanel implements MouseListener
{
  ArrayList<Point> p = new ArrayList<Point>();

  public DrawCircle()
  {
    addMouseListener(this);
  }

  public void paintComponent(Graphics g)
  {
    super.paintComponent(g);
    for(Point point : p)
    {
      g.fillOval(point.x,point.y,30,30);    
    }  
  }

  @Override
  public void mouseClicked(MouseEvent e) {
  }

  @Override
  public void mouseEntered(MouseEvent e) {
    // TODO Auto-generated method stub
  }

  @Override
  public void mouseExited(MouseEvent e) {
    // TODO Auto-generated method stub
  }

  @Override
  public void mousePressed(MouseEvent e) {
    p.add(new Point(e.getY(), e.getX()));
  }

  @Override
  public void mouseReleased(MouseEvent e) {
  }

  @Override
  public void mouseDragged(MouseEvent e) {
  }

  @Override
  public void mouseMoved(MouseEvent arg0) {
  }
}

Solution

  • Let's go through your problems:

    The first problem I encountered was that when I try to implement this draw-mechanic in my frame, no circles appear.

    This is because you forgot to call JPanel#revalidate() and JPanel#repaint() whenever you click somewhere in the DrawCircle class.

    So, you could change your mousePressed() method to:

    @Override
    public void mousePressed(MouseEvent e) {
        p.add(new Point(e.getX(), e.getY()));
        revalidate();
        repaint();
    }
    

    Note that I also changed the e.getX() and e.getY() calls, because they were on the wrong places (unless you want them that way).

    That will make your circles to appear, but, your DrawCircle is really thin (I changed the height of your JFrame to 200 for this image, otherwise it would be really tall):

    enter image description here

    The red part is your DrawCircle panel.

    To fix this you need to override its getPreferredSize() method:

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }
    

    That will make your JPanel to return half the size, width and height were passed as parameters to the constructor, and your class DrawCircle should now look like this:

    class DrawCircle extends JPanel implements MouseListener {
        ArrayList<Point> p = new ArrayList<Point>();
        
        int width = 0;
        int height = 0;
        
        public DrawCircle(int width, int height) {
            this.width = width;
            this.height = height;
            addMouseListener(this);
        }
        
        @Override
        public Dimension getPreferredSize() {
            return new Dimension(width, height);
        }
    
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            for (Point point : p) {
                g.fillOval(point.x, point.y, 30, 30);
            }
        }
    
        @Override
        public void mouseClicked(MouseEvent e) {
        }
    
        @Override
        public void mouseEntered(MouseEvent e) {
            // TODO Auto-generated method stub
        }
    
        @Override
        public void mouseExited(MouseEvent e) {
            // TODO Auto-generated method stub
        }
    
        @Override
        public void mousePressed(MouseEvent e) {
            p.add(new Point(e.getX(), e.getY()));
            revalidate();
            repaint();
        }
    
        @Override
        public void mouseReleased(MouseEvent e) {
        }
    }
    

    And the output would be something like this:

    enter image description here

    It works (on the whole frame) when I use container.add(new DrawCircle)

    That's because by default BorderLayout places by default the elements on the CENTER region, and if you have nothing else in the rest of orientations (NORTH, SOUTH, etc) it will take the whole space.

    Now let's continue with how to solve your problem:

    I also made some changes to the Application class (which in my case I renamed to CustomPaintingInHalfFrame):

    These changes were:

    • Create final constants for the WIDTH and HEIGHT attributes.
    • Removal of unnecessary JPanel with BorderLayout layout, as JFrame already has this layout by default, I simply added our DrawClass to it.
    • Drawing of a border for the DrawCircle panel (as you don't want a division between both (left and right) parts of your JFrame as stated in your previous question you can simply remove it (I recommend you to leave it there while you're testing so you know where left panel ends and right panel starts.
    • Passing WIDTH / 2 and HEIGHT as parameters for DrawCircle constructor, so it can return the correct Dimension.

    So, our class should now look like this:

    public class CustomPaintingInHalfFrame {
        int x, y;
        public static final int WIDTH = 500;
        public static final int HEIGHT = 200;
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new CustomPaintingInHalfFrame().gui();
                }
            });
        }
    
        @SuppressWarnings("serial")
        public void gui() {
            JFrame jframe = new JFrame("Title");
            
            DrawCircle dc = new DrawCircle(WIDTH / 2, HEIGHT);
            
            dc.setBorder(BorderFactory.createLineBorder(Color.RED));
            
            jframe.add(dc, BorderLayout.WEST);
            
            jframe.setSize(WIDTH, HEIGHT);
            jframe.setVisible(true);
            jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jframe.setResizable(false);
    
        }
    }
    

    Additional Tips

    1. I recommend you to rename your DrawCircle to Circle or something like that. As a convention, Classes names should be nouns

    2. Rename gui() method to createGui() for example because, as Classes names, method names should be verbs