Link to home
Start Free TrialLog in
Avatar of moshecristel
moshecristel

asked on

Creating a freehand drawing program with a menu bar...

I'm trying to create a small application that allows me to draw freehand lines on a JPanel (which will reside in the Content Pane of a JFrame.  I also added a JMenuBar with a JMenu (containing a couple of JMenuItems) to the Root Pane using its setJMenuBar method.  

The problem is that if I don't call super.paintComponent(g) in the JPanel's paintComponent method, the background (red) doesn't get painted and there's a duplicate menubar painted on the Content Pane for some reason (?).  If I do call the super method, it repaints the background every time (which covers the duplicate menu bar but erases any previous lines that were drawn).  The call to super in paintComponent is commented out in the code below.



The code I've got so far is below:

-----------------------
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;


public class DrawTest
{


      /**
       *
       *  INNER class that represents the frame that contains the panel on which the drawing
       *  will be done (it is also the event listener).
       *
       */


    public class AFrame extends JFrame
    {

            private APanel mainPanel;
            private JMenuBar jmb;

        public AFrame()
        {

            super("DrawTest");
            setSize(500,500);
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

                  // Create Menu Bar

                  jmb = new JMenuBar();
                  JMenu jm = new JMenu("Draw");
                  JMenuItem jmi_1 = new JMenuItem("Freehand");
                  JMenuItem jmi_2 = new JMenuItem("Rectangle");
                  jm.add(jmi_1);
                  jm.add(jmi_2);
                  jmb.add(jm);
                  getRootPane().setJMenuBar(jmb);


                  // Create the panel on which the drawing will be done

                  mainPanel = new APanel();
                  mainPanel.setBounds(0,0,500,500);
                  mainPanel.setBackground(Color.RED);
                  mainPanel.setVisible(true);
                  mainPanel.setOpaque(true);

                  getContentPane().setLayout(null);
                  getContentPane().add(mainPanel);


                  // Register the Panel/EventListener with itself

            mainPanel.addMouseMotionListener(mainPanel);
            mainPanel.addMouseListener(mainPanel);

        } // end AFrame constructor


        public void paint(Graphics g)
        {
                  super.paint(g);

        } // end paint






    } // end AFrame INNER class



      /**
       *
       *  INNER class that represents the single panel on which the drawing will actually
       *  be done.
       *
       */

      public class APanel extends JPanel implements MouseMotionListener, MouseListener
      {


            private Point mCurr;      // refers to the current point to which the line is to be drawn
            private Point mLast;      // refers to the last point that the line left off at


            public void paintComponent(Graphics g)
            {
                       
                  //super.paintComponent(g);
                  Graphics2D g2 = (Graphics2D) g;



                  // Ensure that both references have a legitimate instance

                  if (getLast() != null && getCurr() != null)
                  {

                        Line2D.Double line = new Line2D.Double(this.getLast(), this.getCurr());

                        g2.setPaint(Color.YELLOW);
                        g2.draw(line);

                        // Connect to the current point on the next event

                        setLast(getCurr());

                  }




            } // end paintComponent


            public void initializePanel()
            {
                  super.paintComponent(getGraphics());
            }



            // Get and set methods

            public void setCurr(Point p) {      mCurr = p; };
            public void setLast(Point p) {      mLast = p; };
            public Point getCurr() { return mCurr; };
            public Point getLast() { return mLast; };

            public void mouseReleased(MouseEvent e)
            {
                  this.setLast(null);
            }


            public void mouseDragged(MouseEvent e)
            {

                  // Set the panel's current point to the point at which this event occurred

                  this.setCurr(e.getPoint());

                  if (this.getLast() == null)
                        this.setLast(this.getCurr());

                  this.repaint();

            } // end mouseDragged


            // Unused method from MouseMotionListener Interface
            public void mouseMoved(MouseEvent e){};

            // Unused methods from the MouseListener Interface
            public void mousePressed(MouseEvent e){};
            public void mouseEntered(MouseEvent e){};
            public void mouseExited(MouseEvent e){};
        public void mouseClicked(MouseEvent e){};


      } // end APanel INNER class



      /**
       *
       *  Constructor of OUTER class that instantiates frame object of
       *  inner class <code>AFrame</code>.
       *
       */

    public DrawTest()
    {

          AFrame aframe = new AFrame();
        aframe.setVisible(true);

    } // end DrawTest constructor



    public static void main(String[] args)
    {

          DrawTest app = new DrawTest();

    } // end main




} // end DrawTest class

-----------------------


I'm not sure if I'm going about things the right way or not.  Any ideas would be much appreciated.
Avatar of Mick Barry
Mick Barry
Flag of Australia image

You should not do this:

          public void initializePanel()
          {
               super.paintComponent(getGraphics());
          }

You only paint a line between the last two points.
So whenever your panel gets repainted that is what you are going to see.

You need to paint lines between *all* points.
This is unnecessary:

      public void paint(Graphics g)
       {
               super.paint(g);

       } // end paint

Avatar of moshecristel
moshecristel

ASKER

objects:

I've tried storing all lines in a Vector and then just repainting them all each time the paintComponent method of the panel is called but as the size of the drawing grows the repaint becomes slower and slower and the events seem to fire less often (or something ?) because the line is drawn more jagged.

By just drawing one line each time an event fires (and not erasing the panel) I was hoping that things would be more efficient.

I could get rid of the initializePanel method and put a boolean in the panel called clear (that was initialized to true) so in paintComponent I would start out with something like:

...
if (clear)
{
    super.paintComponent(g)
    clear = false;
}
...

This way it would only call the super the first time through and paint the background.

There would still be the problem of the duplicate menu bar (which has been haunting me).  It's still there, just covered.  Any ideas of why that second menu bar might be showing up?
Your misunderstanding how the painting works.
paintComponent is called whenever your component needs repainting and is responsible for painting the entire component. You cannot simply paint new stuff.
ie. everytime it is called it needs to paint the background, and all the lines.
Try dragging another window over your window and you will see your painting disappear becuase you do not repaint it.

The duplicate menu bar is a similiar problem, it's not actually a duplicate, you're just not repainting what needs to be underneath it.

If the performance of painting all the lines is a problem as I would expect it to be, then what you need to do is use an offscreen image and paint your lines to it. Then in the paintComponent method just paint this image to the screen.
objects:

This makes sense but I'm not sure what you mean by "use an offscreen image and paint your lines to it".  

How would you go about doing this?
ASKER CERTIFIED SOLUTION
Avatar of Mick Barry
Mick Barry
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
objects:

Excellent!  It worked beautifully.  Very fast and no more problems with needing to paint over the menu bar mirage :)

Thanks.
No worries, happy to help :-)
No comment has been added lately, so it's time to clean up this TA.

I will leave a recommendation in the Cleanup topic area that this question is:

- points to objects

Please leave any comments here within the
next seven days.

PLEASE DO NOT ACCEPT THIS COMMENT AS AN ANSWER !

girionis
Cleanup Volunteer
objects:

Sorry, didn't realize I hadn't accepted your answer :(