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(J Frame.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,50 0,500);
mainPanel.setBackground(Co lor.RED);
mainPanel.setVisible(true) ;
mainPanel.setOpaque(true);
getContentPane().setLayout (null);
getContentPane().add(mainP anel);
// Register the Panel/EventListener with itself
mainPanel.addMouseMotionLi stener(mai nPanel);
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(getGr aphics());
}
// 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.
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(J
// 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(
// Create the panel on which the drawing will be done
mainPanel = new APanel();
mainPanel.setBounds(0,0,50
mainPanel.setBackground(Co
mainPanel.setVisible(true)
mainPanel.setOpaque(true);
getContentPane().setLayout
getContentPane().add(mainP
// Register the Panel/EventListener with itself
mainPanel.addMouseMotionLi
mainPanel.addMouseListener
} // 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
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(getGr
}
// 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.
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.
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
public void paint(Graphics g)
{
super.paint(g);
} // end paint
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?
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.
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.
ASKER
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?
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
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
objects:
Excellent! It worked beautifully. Very fast and no more problems with needing to paint over the menu bar mirage :)
Thanks.
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
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
ASKER
objects:
Sorry, didn't realize I hadn't accepted your answer :(
Sorry, didn't realize I hadn't accepted your answer :(
public void initializePanel()
{
super.paintComponent(getGr
}