Win a copy of Getting started with Java programming language this week in the Beginning Java forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

Best approach for repainting custom objects on a JPanel  RSS feed

 
Emma Sophia Jones
Greenhorn
Posts: 19
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Hi guys,

So I'm just playing around with JPanels as I haven't done a huge amount on the GUI side of Java before and I'm wondering what the best way to approach a repaint is when I've created a custom object.

The task I've set myself is basically:

1) Create a JPanel
2) Create a ball object that extends runnable
3) Get the ball to bounce around a bit

I've managed to pull it off but my implementations doesn't feel right and I was wondering if you would do it the same way I have.

Basically in the ball's move() method, I have it calling "repaint" on the parent JPanel (slightly customised JPanel, the class is called DrawPanel but it extends JPanel). But to do that, it seems like I have to pass the parent JPanel into the ball's constructor and then store it as an instance variable:




I've worked out that an alternative is to have the ball extend JComponent and then call getParent() on it, but that feels like it may be restrictive (it stops me from being able to set a getX() method that returns a double for example).

So how would you approach this?

Hopefully that's clear, feel free to ask for clarifications if not!
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Welcome to the Ranch

I am afraid that isn't correct. You are not extending Runnable but implementing it; that looks to me as though you were running multiple threads on a Swing application, which is wrong. Look here in the Java® documentation. You need to do all painting on the EDT (=Event Dispatch Thre‍ad), and you don't want to call T‍hread.sleep() on that, otherwise your whole GUI grinds to a painful halt.
Try using a Swing Timer and add an ActionListener; if you know about λs, try this:-When you get it working, tell us whether you get decent results with a delay as short as 10ms. The last time I tried that sort of thing (a long time ago), I couldn't get a decent repainting in under about 25ms, but chips have probably got faster since then.

I am not happy to see you are implementing a constants interface. Search for constant interface antipattern. What you are doing is adding all the contents of that interface to the public interface of your circle class, and you probably don't want that.

Watch this space for further posts. Or more precisely our GUIs forum, whither I shall move you.
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Alternative Approach:-
Write yourself a Paintable interface. Make all your shape classes implement that interface.Now, you can store location size etc in the shape objects and all you have to do is tell the Graphics object to paint whichever shape you want. Remember the paintShape method can call move() and you can now give move() private access if you so wish.
Now your Timer tells the panel to repaint itself:-
myTimer.addActionListener(e -> myDrawPanel.repaint());
Note the timer is now outwith the shape objects, somewhere in the GUI. Now all you have to do is override the paintComponent method:-Access=protected not public. Remember the Graphics object passed is an instance of a subclass of Graphics2D, so you can safely cast it to 2D if you need to.
 
Emma Sophia Jones
Greenhorn
Posts: 19
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Oh man, sounds like I have quite a bit of reading to do :P

Thanks for the advice Campbell - I'll read through what you've advised, have another go and then repost at the next hurdle (or at the finish line!)
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Emma Sophia Jones wrote:. . . Thanks for the advice Campbell
That's a pleasure
. . . repost at the next hurdle (or at the finish line!)
No, repost soon, so we can see how you are getting on. Show us the new version of the circle class.
 
Emma Sophia Jones
Greenhorn
Posts: 19
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Alright, so I’m trying to get my head around this conceptually before I start trying to recode the class.

What I’ve picked up is that the EDT is a thread that Java provides specifically for updating the UI. Though it’s technically possible for another thread to directly affect GUI components, this will occasionally lead to errors when the EDT tries to update the same component at the same time. (Why it will lead to errors wasn’t really made clear anywhere that I looked, most sources just said to take it on faith… so I’ll call this the mysterious multi-thread error)

Still, if we take the sources at their word, any time we want to paint a component this should be done in the EDT. Actions can be queued in the EDT by using SwingUtilities.invokeLater().

Further, because the EDT handles all GUI updates, if you give it a task to complete that takes time it will be unresponsive to user inputs during that time (hence why a pause would be a bad idea within the EDT). That’s why you have the SwingWorker class – to handle these time consuming operations while allowing the GUI to remain responsive.



So I guess here I have a few questions about the code I wrote – I think understanding the problems with it will help me to understand the benefits of the approaches you suggested. Setting aside the issue with the constants for now, I figure that the problem is that either:

A) The repaint() method I’m calling from the Ball class does something different to what I was expecting. My expectation is that this would queue a paint command in the EDT. Is it actually the case that this causes the JPanel to be updated directly? (which has the potential to cause the mysterious multi-thread error).

And/Or

B) The run() method for the ball is actually executing within the EDT. So the sleeping has the potential to make the GUI unresponsive.

B seems unlikely as I can resize the window etc even when the thread is asleep… even when the sleep timer is set to 10 seconds… so is the problem A?
 
Rob Camick
Ranch Hand
Posts: 2727
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
A) the repaint() method passes the paint request to the "RepaintManager". The RepaintManager will then consolidate the requests (received within a given time period) into a single call to paint the component to make painting more efficient.

B) If you actually "start" the Thread, then the code will execute on a separate Thread. If you instead invoke the "run()" method directly, then your code could be executing on the EDT. Since we don't have access to your parent panel and its invoking code we don't know exactly what you are doing.

In general you should not be calling the repaint() method from the Ball class and you should not be implementing runnable in the Ball class. What if you want to paint more than 1 ball, you don't want to start a separate Thread for each Ball.

What you should be doing is have your "drawing panel" keep a List of Ball objects. Then you can start a Timer in the "drawing panel" class. When the Timer fires you iterate through the list of Balls and change the balls location. Then you invoke repaint() on the "drawing panel" and the panel will iterate through the List of Balls and paint each Ball in its new location.
 
Emma Sophia Jones
Greenhorn
Posts: 19
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
So my plan actually was to start a separate thread for each Ball - maybe that's why I'm not understanding the disadvantage to my approach currently.

If I have three balls what's the disadvantage to giving each its own thread to handle movement?
 
Rob Camick
Ranch Hand
Posts: 2727
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Having too many Threads (or Timers) will start to overload the system. Try it both ways to see which solution is more scalable.

I doubt 3 will be an issue, but try 50 or 100.
 
Fred Kleinschmidt
Bartender
Posts: 498
3
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Also, when you do call repaint, you should use the method with no parameters.

By calling the repaint with x,y,width, and height as parameters, you are adding that rectangle to the region that will get repainted; other parts of the panel may not get redrawn. This method is usually used by the system when it determines that part of the panel needs to be redrawn due to it becoming newly exposed (because some other window that was covering it has gone away, and backing store is not capable of restoring the view). It should not be used if the contents of the JPanel have actually changed.
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Emma Sophia Jones wrote:. . . Why it will lead to errors wasn’t really made clear anywhere that I looked, most sources just said to take it on faith… so I’ll call this the mysterious multi-thread error)
Horstmann (and Cornell in older editions) Core Java II has an example in where you alter an array and get Exceptions if you run multiple threads. Can't remember the page number or even the volume. Sorry. Ask again in about 15 hours and I can probably find that information.
. . . That’s why you have the SwingWorker class – to handle these time consuming operations while allowing the GUI to remain responsive.
Well done reading all that lot
. . . A) The repaint() method I’m calling from the Ball class does something different to what I was expecting. My expectation is that this would queue a paint command in the EDT. Is it actually the case that this causes the JPanel to be updated directly? . . .
Don't know. Sorry. I would have expected that too; what does it say in the documentation for repaint()? That isn't that helpful, but if you look at some of the overridings in lightweight subclasses, e.g. this, it says something about after other events have occurred, so that does sound like something queued on the EDT.
B) The run() method for the ball is actually executing within the EDT. . . . B seems unlikely as I can resize the window etc  . . .
A Runnable won't run until you tell it to. You would have to write
new Thr‍ead(myRunnableCircleObject).start();
in order for anything to happen at all. And now you have created another th‍read running in parallel with the EDT.

Note what RC says about threading; he is spot on there. Whenever I have tried that sort of thing, I have done what he suggested: put all the objects to draw in a List and iterate the List.
 
Emma Sophia Jones
Greenhorn
Posts: 19
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Fred Kleinschmidt wrote:Also, when you do call repaint, you should use the method with no parameters.

By calling the repaint with x,y,width, and height as parameters, you are adding that rectangle to the region that will get repainted; other parts of the panel may not get redrawn. This method is usually used by the system when it determines that part of the panel needs to be redrawn due to it becoming newly exposed (because some other window that was covering it has gone away, and backing store is not capable of restoring the view). It should not be used if the contents of the JPanel have actually changed.


That's interesting. Using repaint with no arguments does seem like the easier option but the (x, y, w, h) version of the method was recommended in the Oracle docs which is why I started using it. https://docs.oracle.com/javase/tutorial/uiswing/painting/step3.html

Is that not standard practice either?

As for the rest of the posts, I'm gonna have a go at redoing the program. Will do a simplified version of my current technique so that you guys can see the whole thing and also try to do it in the way it's been recommended here... then test both for scalability. The current version scales up to about 1024 balls without a hit in performance on my laptop but does start to lag a bit at 2048. Will be interesting to see how that works out.

Might not be til weekend as I need to do some job, but will post back when I have both versions up and running.

Thanks for the help so far all
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
I tried with up to 14400 balls in a List and that seemed to run smoothly with the Timer firing every 10ms. It was flickery at 20ms or 33ms, so I suspect the GUI isn't repainting every 10ms. That was of course all on one threa‍d. Had I tried that many threads, the computer would have overheated and everything would have run really slowly.
 
Rob Camick
Ranch Hand
Posts: 2727
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Using repaint with no arguments does seem like the easier option but the (x, y, w, h) version of the method was recommended in the Oracle docs


Repainting a smaller region can be more efficient, if you only have a couple regions to paint. But I would suggest that painting many smaller regions can add overhead. Each smaller region needs to be merged into a larger region before the painting is done. So if you have a region at (0, 0, 10, 10) and a region at (1000, 700, 10, 10) you will end up painting a region of (0, 0, 1010, 710) which is the whole screen anyway.

So in this case when you have multiple smaller regions that will likely result in painting the whole screen anyway (especially as you get more balls being painted) it is simpler and more efficient to just repaint the entire component after resetting the location of all the balls.
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yesterday, I wrote:. . . Can't remember the page number or even the volume. Sorry. Ask again in about 15 hours and I can probably find that information.
Cay S Horstmann and Gary Cornell, Core Java 9/e vol I‑Fundamentals, Redwood Shores CA: Oracle/Pearson 2013, chapter 14.11 pages 909‑925. I don't think that information was in the older edition I have, for Java5, and am not sure about the newer edition (10th) for Java8.
 
Rob Camick
Ranch Hand
Posts: 2727
11
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
The current version scales up to about 1024 balls without a hit in performance 


Wow, kind of surprises me you don't take much of a hit with that many Threads running.

Anyway, since you have a working version here is some old code I had lying around that may (or may not) give you some ideas:



 
Fred Kleinschmidt
Bartender
Posts: 498
3
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Using the 4-argument version of repaint() is not appropriate for this problem. You not only have to draw the ball(s) at their new positions, but you also have to repair the region where the ball used to be. Much easier to repaint the entire panel. Note that  using the 4-arg repaint may set the clip rectangle of the Graphics object to exclude regions outside the specified rectangle, so even drawing each ball inside your paintComponent() method might not redraw stuff that is outside that rectangle.
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Yesterday, I wrote:. . . not sure about the newer edition (10th) for Java8.
I have found my 10th edition:
Cay S Horstmann, Core Java 10/e vol I‑Fundamentals, Redwood Shores CA: Oracle/Pearson 2016, pages 937‑952.
 
Emma Sophia Jones
Greenhorn
Posts: 19
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Okay, so the method you guys recommended has better performance for sure. It actually seems to slow down a bit as the number of balls increases, but the movement remains smooth throughout. Plus, now that I have a basis for comparison, it looks like the movement is smoother on the list approach even with a single ball. It's a bit of a bummer as it feels less intuitive to me but I guess I'll have to try to get my head around it!

My two projects are posted below anyway. graphicsPracticeTwo is the one where I was using the multi-thread approach and graphicsPracticeThree uses the list approach.

Any suggestions for tweaks to either would be welcome

graphicsPracticeTwo




graphicsPracticeThree

 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Not bad
The only thing to worry me is making a display component implement interfaces. I do not believe that a panel ought to implement action listener for example. I would have used an anonymous class until Java8 appeared; now I would go for a λ (see Java™ Tutorials). Because you want two statements inside the right half, you will need {} and ;Anyway, have a cow for asking an interesting question and putting lots of effort into following it up.
 
Darryl Burke
Bartender
Posts: 5167
11
Java Netbeans IDE Opera
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
With Java8, why not replace the for-each loop with a Stream?  I guess this would do it:
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Sorry for delay. I was wondering whether the forEach method is better than a for‑each loop or not, then suddenly remembered you can write this:-Since paintShape takes a Graphics parameter, can you simply use a method reference?
 
Darryl Burke
Bartender
Posts: 5167
11
Java Netbeans IDE Opera
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Campbell Ritchie wrote:Since paintShape takes a Graphics parameter, can you simply use a method reference?

Probably not.  I plead guilty to not reading the method declaration in earlier posts, but if that method requires a Graphics reference, how would you invoke it from a Timer's ActionListener?  Rather, the Timer would simply repaint() the panel and paintComponent(...) would take care of the rest.
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Darryl Burke wrote:. . . how would you invoke it from a Timer's ActionListener?  Rather, the Timer would simply repaint() the panel and paintComponent(...) would take care of the rest.
I am pretty sure that is what I did.
The class Campbell tried it out with wrote:
There is another action listener not shown here to close the app after a certain time. It works if I change the method to this:-It even works if I spell dleay correctly.
 
Darryl Burke
Bartender
Posts: 5167
11
Java Netbeans IDE Opera
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Campbell Ritchie wrote:I am pretty sure that is what I did.

Sorry, I was confused by this version you posted later.
Campbell Ritchie wrote:
 
Campbell Ritchie
Sheriff
Posts: 53561
126
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
You're right; I must have been asleep when I wrote that . It won't even compile, will it.

Sorry.
  • Post Reply Bookmark Topic Watch Topic
  • New Topic
Boost this thread!