Solved

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

Posted on 2002-07-03
12
894 Views
Last Modified: 2010-08-05
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.
0
Comment
Question by:moshecristel
  • 7
  • 4
12 Comments
 
LVL 92

Expert Comment

by:objects
ID: 7128342
You should not do this:

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

0
 
LVL 92

Expert Comment

by:objects
ID: 7128350
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.
0
 
LVL 92

Expert Comment

by:objects
ID: 7128398
This is unnecessary:

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

       } // end paint

0
NAS Cloud Backup Strategies

This article explains backup scenarios when using network storage. We review the so-called “3-2-1 strategy” and summarize the methods you can use to send NAS data to the cloud

 

Author Comment

by:moshecristel
ID: 7128706
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?
0
 
LVL 92

Expert Comment

by:objects
ID: 7128749
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.
0
 

Author Comment

by:moshecristel
ID: 7128873
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?
0
 
LVL 92

Accepted Solution

by:
objects earned 150 total points
ID: 7128888
Create an image the same size as your component.
Then get a graphics context for this image and perform your painting to this, instead of to the components graphic context:

Graphics g = image.getGraphics();
g.drawLine(....

Then in your paintComponent method simply paint this image to screen:

g.drawImage(image, 0, 0, this);
0
 

Author Comment

by:moshecristel
ID: 7128983
objects:

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

Thanks.
0
 
LVL 92

Expert Comment

by:objects
ID: 7129002
No worries, happy to help :-)
0
 
LVL 35

Expert Comment

by:girionis
ID: 8893899
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
0
 

Author Comment

by:moshecristel
ID: 8894015
objects:

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

Expert Comment

by:objects
ID: 8897942
0

Featured Post

Optimizing Cloud Backup for Low Bandwidth

With cloud storage prices going down a growing number of SMBs start to use it for backup storage. Unfortunately, business data volume rarely fits the average Internet speed. This article provides an overview of main Internet speed challenges and reveals backup best practices.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

For customizing the look of your lightweight component and making it look opaque like it was made of plastic.  This tip assumes your component to be of rectangular shape and completely opaque.   (CODE)
Introduction This article is the last of three articles that explain why and how the Experts Exchange QA Team does test automation for our web site. This article covers our test design approach and then goes through a simple test case example, how …
This tutorial covers a practical example of lazy loading technique and early loading technique in a Singleton Design Pattern.
This video teaches viewers about errors in exception handling.

770 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question