I'm new to Java and teaching myself and trying to build this program. It will create dots when the mouse clicks, but won't draw a line when the button is pushed. (eventually the line will connect the dots, but I'm not there yet.) First I just need to get it to draw something when pushed and work from there. I've tried multiple things and I can't get it to work. Here is my code:
public void init()
{
LineDrawListener listener = new LineDrawListener ();
addMouseListener (listener);
Button lineGraph = new Button("Graph Line");
lineGraph.addActionListener (this);
add (lineGraph, BorderLayout.EAST);
setBackground (Color.yellow);
setSize (APPLET_WIDTH, APPLET_HEIGHT);
}
public void paint(Graphics g)
{
// draws dots on screen
g.setColor(Color.blue);
if (current != null)
{
xcoordinates.addElement (current.x);
ycoordinates.addElement (current.y);
count++;
g.fillOval (current.x-4, current.y-4, 10, 10);
repaint();
}
if (push == true)
{
g.drawLine (5, 5, 30 , 30);
repaint();
}
}
class LineDrawListener extends MouseAdapter
{
public void mouseClicked (MouseEvent event)
{
current = event.getPoint();
repaint();
}
}
public void actionPerformed (ActionEvent event)
{
Object action = event.getSource();
if(action==lineGraph)
{
push = true;
}
}
Any help on how to get the button to work would be much appreciated. Thanks in advance.
The reason you can not get the line to draw is that repaint() posts a request to repaint the component. You are using it as if it refreshes the view somehow. You need to change three things in your code, all in paint():
Do not call repaint() in paint()
Override paintComponent() rather than paint() so your borders get drawn if you should ever have them at a later time.
Call super.paintComponent() in paintComponent() to do some initialization for you. It is probably OK not to for something this simple, but good practice for the future.
Here is what the code would look like:
public void paintComponent(Graphics g)
{
super.paintComponent(g);
// draws dots on screen
g.setColor(Color.blue);
if (current != null)
{
xcoordinates.addElement (current.x);
ycoordinates.addElement (current.y);
count++;
g.fillOval (current.x-4, current.y-4, 10, 10);
}
if (push == true)
{
g.drawLine (5, 5, 30 , 30);
}
}
Don't paint on top-level containers like JApplet
Rather paint on a JPanel and override the paintComponent method and call super.paintComponet(g)
#Override
protected void paintComponent(Graphic g){
super.paintComponent(g);
...
}
Don't call repaint() from inside the paint() method. Call it in your actionPerformed()
Learn to post an SSCCE
Number 3 is your most dire problem
Don't override paint(). Don't invoke repaint() in a painting method, this can cause in infinite loop.
Check out Custom Painting Approaches for working examples of the two common ways to do custom paintint:
by using a List to track the objects to be painted
by painting to a BufferedImage
Related
I have a problem when trying to draw some elements using paint method in Swing.
As title says, my whole frame collapses and does some weird repeating.
I made a separate JPanel so I can manipulate drawn shapes:
public class PanelPovrsina extends JPanel{
private ArrayList<Oblik> listaOblika;
public PanelPovrsina() {
// svi oblici
this.listaOblika = new ArrayList<Oblik>();
this.listaOblika.add(new Kvadrat(new Tacka(50, 50), 50, "zuta", "crvena"));
this.setBackground(Color.WHITE);
this.setVisible(true);
}
public void paint(Graphics g) {
if(this.listaOblika.isEmpty()) return;
Iterator<Oblik> it = this.listaOblika.iterator();
while(it.hasNext()) {
it.next().crtajUBoji(g);
}
repaint(); // this causes problems!
}
public ArrayList<Oblik> getListaOblika() {
return this.listaOblika;
}
}
Here is the frame with this code:
And here it is without repaint method:
No, I know repaint method is essential in order to dynamically add shapes and actually draw, but I can't make this work correctly.
Also, as you can see from the code above, background of panel is set to white, but my frame would'n render it.
Hope there is enough information to solve my problem, if not, I will add code of my JFrame!
Thank you!
You should never override the paint method, as it handles a number of other things behind the scenes. You should override paintComponent instead.
As #Joe C answered, I should have been using paintComponent method, not paint! Working code:
public class PanelPovrsina extends JPanel{
private ArrayList<Oblik> listaOblika;
public PanelPovrsina() {
// svi oblici
this.listaOblika = new ArrayList<Oblik>();
this.listaOblika.add(new Kvadrat(new Tacka(50, 50), 50, "zuta", "crvena"));
this.setBackground(Color.PINK);
this.setVisible(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Oblik obl : this.listaOblika) {
obl.crtajUBoji(g);
}
repaint();
}
public ArrayList<Oblik> getListaOblika() {
return this.listaOblika;
}
}
I have a very basic little JFrame with JToggleButtons and subclassed JPanels that know how to draw what I want them to draw. Selecting a button causes an oval to appear in the corresponding panel. Unselecting the buttons makes the drawings disappear.
Unfortunately, minimizing (iconifying) and then restoring (deiconifying) causes any drawn shapes to disappear. So I need to trigger redrawings manually. The problem is that I can only get the redrawing done (that is, I only see it) if I show a message box first.
Here's the deiconify event for the JFrame:
private void formWindowDeiconified(java.awt.event.WindowEvent evt)
{
//having this message makes everything work
JOptionPane.showMessageDialog(null, "Useless message this is.");
//but if I skip it, I'm SOL
//what's going on?
drawAll();
}
This method goes over all of my buttons and asks for the redraws when necessary:
public void drawAll()
{
for (int i=0; i<channels; i++)
{
if (buttons[i].isSelected())
{
lightboxes[i].drawMe();
}
}
}
and here is my subclassed JPanel:
class MyJPanel extends JPanel {
public void drawMe()
{
Graphics myGraphics = this.getGraphics();
myGraphics.fillOval(0, 0, this.getWidth(), this.getHeight());
}
public void unDraw()
{
this.invalidate();
this.repaint();
}
}
The window should automatically be repainted once it is restored by the RepaintManager. The problem is you are not performing custom painting like you should...
This is not how to do custom painting...
public void drawMe()
{
Graphics myGraphics = this.getGraphics();
myGraphics.fillOval(0, 0, this.getWidth(), this.getHeight());
}
getGraphics can return null and is, at best, a snapshot of the graphics state.
Painting in Swing can occur at any time for many different reasons, most of which you don't have control over (nor should you care).
Your job is simply to respond to these repaint requests and update your components state.
Swing has a detailed paint chain which is called automatically and which you can use.
You should be overriding paintComponent and performing all painting within this method
Take a look at Performing Custom Painting and Painting in AWT and Swing for more details
Firstly, for speed I would use double buffering. It's best to paint your graphics off screen and display them to the screen when the drawing has completed. The below should sort you out.
public class MyPanel extends JPanel {
private BufferedImage buffer;
private Graphics2D canvas;
#Override
public void paintComponent(Graphics g) {
if(buffer == null) {
buffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
canvas = buffer.createGraphics();
}
canvas.fillOval(0, 0, this.getWidth(), this.getHeight());
g.drawImage(buffer, 0, 0, this);
}
}
I'm just providing this answer so people can see what I ended up doing. The major lesson pointed out by everyone was to use the component's paintComponent. See the comments for issues that you might be experiencing yourself.
Edit: Updated to reflect comments from MadProgrammer.
//with help from Slihp and MadProgrammer on StackOverflow
//http://stackoverflow.com/q/17331986/1736461
class MyJPanel extends JPanel {
private boolean buttonSelected = false;
#Override
public void paintComponent(Graphics g) {
//make sure the background gets painted (wacky results otherwise)
super.paintComponent(g);
//now if the corresponding toggle button is on, plop on the circle
if (buttonSelected)
{
g.fillOval(0, 0, this.getWidth(), this.getHeight());
}
}
//an action listener for the button calls this
public void setButtonSelected(boolean buttonStateIn)
{
buttonSelected = buttonStateIn;
}
}
I subclassed the buttons too, so I can get its "ID" off of it from the event handler:
class MyJToggleButton extends JToggleButton
{
private int whoAmI;
public MyJToggleButton(int whoAmIn)
{
//the string given to the constructor becomes the button's label
//("1", "2", "3", etc..)
super(Integer.toString(whoAmIn + 1));
whoAmI = whoAmIn;
}
public int getWho()
{
return whoAmI;
}
}
The JFrame code that makes the buttons:
private void makeButtons(int howMany)
{
buttons = new MyJToggleButton[howMany];
for (int i=0; i<howMany; i++)
{
buttons[i] = new MyJToggleButton(i);
buttons[i].addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
//find out which button this is
MyJToggleButton button = (MyJToggleButton) evt.getSource();
int which = button.getWho();
//send the button state to the corresponding lightbox
lightboxes[which].setButtonSelected(button.isSelected());
//trigger its redrawing
lightboxes[which].invalidate();
lightboxes[which].repaint();
}
});
this.add(buttons[i]);
}
}
And that's the only manual redrawing I have to do - resizing and reshowing and all those other fun things eventually hit up the paintComponent, and it just has to know if its button is pushed to know what to do. Super clean and just what I wanted.
I would like to know if it's possible to set an image as background for a jmenubar+jtoolbar (not only for one of theym, not one for each of theym But on for BOTH) ...
Anyone's got an idea ??
How should I do that if it's possible ?
Thanks !
Here an image to explain :
Solved :: I used two images (cutted to the right size to suite my jmenubar+jtoolbar) and added these to the object's declarations as overrides and it works great ! Here is a piece of code :
///////////////////////////////
JToolBar toolBar = new JToolBar(){
#Override
protected void paintComponent(Graphics g){
Image photo = getToolkit().getImage("src/MainFrame/Images/xtremeCalliBottom.png");
super.paintComponent(g) ;
int x=(mainFrame.getWidth()-200), y=0 ;
if(photo != null)
g.drawImage (photo, x, y, this);
}
};
// ............
//========== Menu Bar
jMenuBar = new JMenuBar(){
#Override
protected void paintComponent(Graphics g){
Image photo = getToolkit().getImage("src/MainFrame/Images/xtremeCalliTop.png");
super.paintComponent(g) ;
int x=(mainFrame.getWidth()-200), y=0 ;
if(photo != null)
g.drawImage (photo, x, y, this);
}
};
// ................
jMenuBar.setPreferredSize(new Dimension(100, 25));
toolBar.setPreferredSize(new Dimension(100,40));
Sure, but you'll have to override them separately. You'll also need to keep some global variable (or one that you can pass between the two) so that they can know how big each of them are.
You'll need to override paintComponent() or add your own UI delegate to do the painting. You can load the image and paint just the top portion (or relative percentage) on the menubar, then paint just the bottom portion or relative percentage on the toolbar.
This is a part of my code which does some sort of animation; however, there seems to be something wrong: Whenever I try to use the passed 'g' from inside the anonymous class to draw anything, it does nothing, yet when I used it outside the anonymous class (inside the rollBalls method) it does what it's supposed to do. Any idea why? And how do I fix this? Thank you.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
rollBalls(g);
}
private void rollBalls(final Graphics g) { //Roll all 3 balls on the bar
new Timer(1, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
g.setColor(Color.red);
g.fillRect(0, 0, 500, 500);
}
}).start();
}
Your problem is several fold but first and foremost you understand that the Graphics object passed into a paint or paintComponent method usually does not persist and may be disposed of after the method completes. Next you have program logic being called from within a paintComponent method which should never be done. You do not have full control of when or even if the paint or paintComponent methods are called and thus should not have it dictating your app's logic.
For this reason you do not do Swing graphics in this way. Instead, have your timer outside of any paintComponent method, have it update class fields, have it then call repaint() and have your paintComponent use these class fields to do drawing as needed and this time with a stable Graphics object that is passed into it via its parameters.
I understand that your code is "just a sample", but your doing things wrong by trying to paint directly from within actionPerformed. You simply shouldn't do this.
I agree with Hovercraft Full Of Eels's comment. you should do something like this
class MyClass extends JComponent{
MyClass(){
new Timer(1, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MyClass.this.repaint();
}
}).start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(0, 0, 500, 500);
}
Maybe living on the wrong thread for GUI update? Try putting the drawing commands in the anonymous class into a Runnable and pass that to SwingUtilities.invokeLater.
I don't think it has anything to do with inner-class-ness.
I am trying to achieve double buffering of my game in Java by overriding the update method for my JPanel, I do all the usual code etc and still it won't work, it throws a stack overflow error, below is the specific error:
Exception in thread "AWT-EventQueue-0" java.lang.StackOverflowError
at java.awt.Rectangle.<init>(Rectangle.java:193)
at java.awt.Rectangle.<init>(Rectangle.java:208)
at sun.awt.image.BufImgSurfaceData.getBounds(BufImgSurfaceData.java:369)
at sun.java2d.loops.GraphicsPrimitive.convertFrom(GraphicsPrimitive.java:533)
at sun.java2d.loops.GraphicsPrimitive.convertFrom(GraphicsPrimitive.java:523)
at sun.java2d.loops.MaskBlit$General.MaskBlit(MaskBlit.java:171)
at sun.java2d.loops.Blit$GeneralMaskBlit.Blit(Blit.java:186)
at sun.java2d.pipe.DrawImage.blitSurfaceData(DrawImage.java:927)
at sun.java2d.pipe.DrawImage.renderImageCopy(DrawImage.java:550)
at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:54)
at sun.java2d.pipe.DrawImage.copyImage(DrawImage.java:982)
at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:2979)
at sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:2964)
at epicgame.Menu.displayMenu(Menu.java:71)
at epicgame.GUI$1.paintComponent(GUI.java:64)
at javax.swing.JComponent.paint(JComponent.java:1029)
at epicgame.GUI$1.update(GUI.java:117)
at epicgame.GUI$1.paintComponent(GUI.java:98)
at javax.swing.JComponent.paint(JComponent.java:1029)
My code isn't particularly complex either:
mainPanel = new JPanel()
{
#Override protected void paintComponent(Graphics g)
{
//super.paintComponent(g);
if(menuEnabled == 1)
{
Menu.displayMenu(g, mainPanel);
}
else if(gameNum == 1)
{
StreetFighter.StreetFighter(g, mainPanel);
// Calls the controls method within the controls class.
Controls.controls(Calendar.getInstance().getTimeInMillis() - timeOld);
timeOld = Calendar.getInstance().getTimeInMillis();
}
else if(gameNum == -1)
{
Menu.scoreBoard(g, mainPanel);
if(loaded != true)
{
Menu.loadScoreBoard(mainPanel);
loaded = true;
}
}
if(gameNum > 0)
{
if(longcat == true && longcatloaded != true)
{
Extras.loadLongCat();
longcatloaded = true;
}
if(longcatloaded == true && longcat == true)
{
Extras.displayLongCat(g, mainPanel);
}
}
// Causes an infinite loop, e.g makes the screen render over and over.
//repaint();
update(g);
}
#Override public void update(Graphics g)
{
System.err.println("Updating screen and using double buffer!");
// initialize buffer
if(dbImage == null)
{
dbImage = createImage (this.getSize().width, this.getSize().height);
dbg = dbImage.getGraphics ();
}
// clear screen in background
dbg.setColor (getBackground ());
dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);
// draw elements in background
dbg.setColor (getForeground());
paint(dbg);
// draw image on the screen
g.drawImage (dbImage, 0, 0, this);
try
{
Thread.sleep(200);
}
catch (InterruptedException ex)
{
System.err.print("cant delay repaint.");
}
}
};
I was hoping someone could point out where I went wrong, I'm thinking maybe something to do with the update being called too many times, or possible update is the wrong method?
Don't call paint() or update() methods from paintComponent().
Also don't call Thread.sleep() in any painting methods. Instead, create a thread that updates your game model every x milliseconds and then calls repaint() on your custom component where you have overridden paintComponent() so that it draws the game state.
You're calling paint within the component's paintComponent, which will cause the component to keep repainting itself. This will cause a StackOverflowException. Also, the API admonishes a developer about explicitly invoking paint in an application:
Invoked by Swing to draw components.
Applications should not invoke paint
directly, but should instead use the
repaint method to schedule the
component for redrawing.
This method actually delegates the
work of painting to three protected
methods: paintComponent, paintBorder,
and paintChildren. They're called in
the order listed to ensure that
children appear on top of component
itself. Generally speaking, the
component and its children should not
paint in the insets area allocated to
the border. Subclasses can just
override this method, as always. A
subclass that just wants to specialize
the UI (look and feel) delegate's
paint method should just override
paintComponent.
You need to call g.dispose() every frame after you are done with it, otherwise it will never be released from memory and you get the stack overflow error you see. http://download.oracle.com/javase/1.3/docs/api/java/awt/Graphics.html