Win a copy of Cross-Platform Desktop Applications: Using Node, Electron, and NW.js this week in the JavaScript forum!
  • Post Reply Bookmark Topic Watch Topic
  • New Topic

The sneaky, overridden paintComponent and the 'this' keyword  RSS feed

 
Skye Antinozzi
Ranch Hand
Posts: 68
3
Eclipse IDE Java
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Today, I have been playing with Swing again and stumbled upon something I can't get myself to fully figure out.

Here is a class I've written that, when run, creates a JFrame and paints a blue rectangle to the canvas.



From the title you can tell I'm going to be talking about the 'this' keyword and how it relates to the paintComponent method.

I've read the tutorials and the know-hows on what the 'this' keyword is and how it should be used but in this case I am still confuzzled.
If you take away the code 'frame.add(this)' the rectangle will not be painted and you are left with a lonely and empty canvas, as far as I can see.
Replacing the 'frame.add(this)' and the rectangle is painted.

The line says, okay frame I want you to add the currently being used object to your frame.
This object is then added, the paintComponent method is called and the rectangle lives happily inside the pixel box on my display.

Most people, when asked how the paintComponent method is called, like to say it is called 'when it needs to be called.'

Sure that's fine, I know a lot of things are called and created in the background without my knowledge. And if I want, I can use something like repaint() to make it paint itself.

But in this case, how does 'frame.add(this)' know to call the paintComponent method?

What object is 'this' referring to?

How does this object know, or not know, that once it is added that the paintComponent method must be called?

So please my fellow Java Ranchers, enlighten me on how this sorcery all works. ^_^



 
Piet Souris
Rancher
Posts: 1942
66
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
hi Skye,

first of all, your code reminds me a little bit of these nasty Oracle exam questions:
giving you a very complicated situation that no one in his right mind would ever
write, and then asking some nasty questions about it.

But that said, instead of answering your questions, I would like to propose
a small change to your code.

First, delete lines 10 and 12, and put in:


Then, replace line 15 with:


Now, compare the two codes. What do you think?

Greetz,
Piet
 
Campbell Ritchie
Sheriff
Posts: 55329
157
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Don't double‑space all your writing; it makes it harder to read. I have deleted some unnecessary blank lines.

You have made a mistake, doing anything to your display after setVisible. Either:-
  • 1: Use setVisible(...) as the last statement in the method/constructor, or
  • 2: Call revalidate() afterwards.
  • You are adding something to the display after setting it visible, so it may not behave normally.

    You are writing the paintComponent() method wrongly. It should have protected access not private. You should also write super.paintComponent(g); as its first statement, otherwise changes might not appear correctly. You will see the old display and the new display superimposed, when you only want to see the new display. If the display is static, then you can probably get away without the super. call (as here). Also always write @Override before any method you think you are overriding, otherwise you can get problems with tiny spelling errors which are surprisingly difficult to find.

    Right: you are inside a panel class. If you use the keyword this without following () it refers to the current object. You are inside an object of the panel class, so this means whichever object of the panel class you happen to have at the moment. You can only use this(...) in constructors. Now you have a frame object. It should probably be a local variable in the setUpGUI method rather than a field, but that doesn't affect the use of “this”. What you are saying is, “Create a frame object and add the current panel object I happen to be inside to the frame.”
    The frame.add(this) bit does not call paintComponent. Whenever you hide the frame (partially) or re‑expose it or resize it or move it, the OS tells the JVM that there have been changes to the frame and it needs to be repainted. The JVM receives those messages and repaints the frame and all its child components (but it may restrict itself to parts if the whole display does not need repainting). Because the JVM calls paintComponent it does not need public access. You never call paintComponent directly yourself.
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Piet Souris,

    I'm not sure what you mean. I believe Campbell Ritchie took out the white space and it made your lines incorrect. Even so, I put the blank lines back in and still do not see what your code would change. If you could clarify, I will gladly look again. ^_^

    Campbell Ritchie,

    Thank you for insight, it has helped me. So I think I understand... Once I use frame.add(this) it adds a panel object to the frame. After that panel object has been added then there is an area that the rectangle can be painted onto. If I take away frame.add(this), the rectangle does not paint because there is no area, or canvas, for it to be painted on. Also you say that I wrote paintComponent incorrectly. You say that its access is private, but it is public? Am I missing something here? Thanks again!
     
    Piet Souris
    Rancher
    Posts: 1942
    66
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    hi Skye,

    my intention was primarily to draw your attention to your code.
    Your DrawPanel class is extending JPanel. Yet you create (in line 10
    of the code after Campbells changes) a separate instance of your class.
    This is defeating the purpose of having your DrawPanel class extending
    JPanel. I have seen similar constructions at this forum, and they always
    lead to unclear coding, if not errors.
    In my simplified version, it is obvious to what object the 'this' is referring.
    In your version, well, that is what my opening was about.

    And indeed, my comments are not applicable to the code that is now visible.

    Lastly, for a good undertanding of all 'paintComponent' and related,
    I would advise to give the Oracle tutorials on Swing a thorough read.

    Greetz,
    Piet
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Piet Souris wrote: . . . delete lines 10 and 12, and put in:
    That is now lines 10-11
    . . .Then, replace line 15
    That is now line 14

    Sorry, I thought it was becoming easeir to read and didn't expect that side‑effect.
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Wow! This makes way more sense now! I had no idea you could just instantiate an object. I thought you HAD to assign it to a reference, otherwise how would you use it? In this case, just making the object is enough to call the constructor and create the JFrame. Also, since frame.add(this) is being used in the constructor of DrawPanel it makes much more sense on what object 'this' is referring to. Thank you very much for that clear way of explaining this and I have full intention of going through Oracle's tutorials for almost everything in Java. I've already completed a large majority of their instruction. ^_^
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Also, I extend DrawPanel with JPanel to override the paintComponent method. How is instantiating a new object of my class wrong? Would it be best practice to create a JPanel object and use that instead? Because in the code we still make a new Drawpanel by using new DrawPanel(). Thanks in advance!
     
    Piet Souris
    Rancher
    Posts: 1942
    66
    • Likes 1
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    hi Skye,

    well, your original code works, so actually, there is nothing wrong!
    But as I said, I've seen similar costructions before on this site,
    and my first reaction is always: I hope OP knows what he is doing.

    You do not have to make your class extends JPanel.
    Often, you can get away by creating an anonymous class, like

    and then you can add 'panel' to whatever container you like.

    Now, in your case, my favorite way of coding would be something like:

    As you see, there are many ways of achieving what you want. Practice and experience is
    your guide here.

    Greetz,
    Piet
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Thanks buddy! This is helping me out a great deal. I'm going to keep playing around with this but I do like using the object constructor to set everything up rather than using a method. ^_^
     
    Piet Souris
    Rancher
    Posts: 1942
    66
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Piet Souris wrote:You do not have to make your class extends JPanel.
    Often, you can get away by creating an anonymous class, like


    and then




    Please read this as:


     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Yes, it has been made very clear to me that I should be using annotations for overridden methods. Thanks ^_^
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Piet Souris wrote:// and of course

    Not public access. And start with a super. call, though in this instance the absence of the super. call doesn't make much difference. If you have something to draw which moves, then you will notice the absence of a super. call.
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Skye Antinozzi wrote:Yes, it has been made very clear to me that I should be using annotations for overridden methods. Thanks ^_^
    The reason for the @Override annotation is that it is very easy to write this sort of thing:-… and wonder why System.out.println(myFoo); displays Foo@a7b6d0e1. After the @Override annotation was introduced, we could precede the method with @Override and the compiler would complain bitterly. According to the Java Tutorials, you write @Override one line before the method heading, but that is only a style thing.
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Ah I'm so glad I started posting to these forums, I have learned so much already.

    If you take a look at the API documentation and find the paintComponent method the access is protected and not public.
    paintComponent Method

    When overriding a method you should not change the access modifier to something that further restricts access to the method. For example, if a method in the API is written as public you would not override it and change the access to private. You can, however, change the access to something friendlier than the overridden method, like protected to public. Even though I originally wrote paintComponent as public instead of protected, it will still work. Work, however, is used loosely and I agree that maintaining the access modifier as protected is better than increasing access to public.

    Using the super.paintComponent(g) is something that should be used when you are animating on the screen. Moving a circle across the screen is a great example. Every time the system calls the paintComponent method the circle is repainted on the screen and its coordinates are changed according with the direction it is programmed to move. Using super.paintComponent(g) calls the overridden paintComponent method from JComponent and clears the screen, after that the code to move the circle further would execute. If you take away super.paintComponent(g) your circle will still move but it will leave a trail of color in its wake.

    Annotations are starting to become more popular throughout my code. It will definitely be a good habit for me to include @Override where my methods override a method of a superclass. If the method is not overridden properly the compiler will yell at me and, hopefully, I will know exactly where the problem is.

    I wrote this out to just give you an idea of what's going on in my head. Understanding this stuff is important to me and this only helps me to learn it more effectively. Thanks.
     
    Piet Souris
    Rancher
    Posts: 1942
    66
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    hi Skye,

    great post! Yes, you really do get the hang of it. Compliments.

    Just some notes:

    I've used 'public void paintComponent()' all my Java life, and unless someone
    gives me a really compelling reason why 'protected' should be used, I stick to my habit.
    And that is just plain old stubborness!

    And indeed, usually you should start with 'super.paintComponent(g)'. However,
    there are many situations thinkable where you just draw a BufferedImage to your
    panel, completely covering your panel.
    Now, since this BI is completely covering your panel, it would be a waste
    of time to first have a call to 'super.paintComponent(g). Why clear the
    panel to the background first, if you are going to cover it anyway? This
    can also be the case when doing animation.

    For this reason, I would never say 'always start with super...', since it is simply
    incorrect.

    Finally, I would like to make another remark, before someone else will flame me for it.
    If you look at the main method, you see I simply do a 'new DrawPanel()';
    Well, that is actually incorrect. For all things GUI, you must make sure
    that the code involved runs on the EDT.
    Now, since you're on the tutorials, I'll leave it up to you to look up what
    actual code I should have used. (hint: look at the class SwingUtilities).

    Well done!

    Greetz,
    Piet
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    After everything I've decided to re-write this entire class. To include all of our fixes and to implement the cliffhanger Piet Souris had left me.
    Here she is.



    What do we think fellow Ranchers?
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    I am particularly impressed by your using the EDT. Challenge: use Java8 and get rid of the anonymous class by using a λ.
    It is easier than you think; even I can do it.
     
    Piet Souris
    Rancher
    Posts: 1942
    66
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Yes, mucho better than your very first code!

    However, I'm still not satisfied. Why? Because you do not use the fact that DrawPanel
    extends JPanel. I'll illustrate with the following version of your code. Here goes:

    Now, which of the two versions do you prefer? Why? Or are they indifferent?

    Greetz,
    Piet
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    That is a good question. Challenge: Justify it that your use of an anonymous class is better than creating a named class.
    And let's see how to get rid of the anonymous Runnable class in Java8.
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Challenges accepted! I'll post back later today with my results!

     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Sorry about the wait. ^_^

    I'll start by addressing the code Piet Souris posted. You say that I do not utilize the fact that DrawPanel extends JPanel but I believe that I do. After looking at your code I can see how it differs from my version. You take out the JPanel extension from the public DrawPanel class declaration; you utilize the javax.swing.* import to instantiate a JPanel object within the DrawPanel constructor and simultaneously implement the paintComponent( Graphics g ) method and finally, you use frame.add() to add drawPanel rather than using the 'this' keyword, which would require the JPanel extension.

    In my version there is one key difference that I do prefer not only because it has more clarity to me but that it is also used from an example you presented to me yourself. When extending DrawPanel with JPanel it makes it possible to use the 'this' keyword to add the JPanel object to the JFrame. If you play with the code by taking out the JPanel extension and maintaining the frame.add(this) you will get a compiler error that says an object of DrawPanel is being passed as an argument to the add() method. If you then return the JPanel extension to the declaration the program will compile fine as a subclass of Component, in this case JPanel, is being passed through the add() method; which is also represented by the 'this' keyword. Therefore, only by having the JPanel extension can I use the 'this' keyword to make my program run properly. Thank you for that, buddy. ^_^
    In addition, the paintComponent( Graphics g ) method is inherited by the JPanel class from the Component class, so when extending JPanel the DrawPanel class also inherits this method. Without the extension, I would have to do something similar to what you did and implement paintComponent( Graphics g ) during the declaration and assignment of a JPanel object.

    Quick note: You forgot a semi-colon in your DrawPanel class. Look at your JPanel. ^_^

    In all respect, I really do like your version of the code. It has simplicity and pulls up a lot of good points. Not to mention I can understand it. ^_^

    Moving on to the challenges I accepted!

    To recap, I was to justify that the use of an anonymous inner class is better than creating a named class. And of course I was to also rid the program of the anonymous Runnable class by using Lambda Expressions. Whoo! Let's get started. Using the anonymous inner class may not be easier to write than writing a separate named class or interface, at least at first, but it definitely works to my advantage for being concise in coding. The anonymous inner class in the DrawPanel class is created inside this snippet:



    In this case there is an anonymous class being created within the parenthesis of the invokeLater method. If you find Runnable in the API you will notice that it only has one method, the run() method, which needs to be implemented when Runnable comes into a class. Since I am only going to be using the run() method a single time it makes sense to place it into just a small block, or even line, of code rather then implementing the Runnable interface and implementing the method otherwise. I am using the run method as my one time use to get the GUI ball rolling synchronously with the EDT and that is all that needs to be done. Less code for less work. It is also worthwhile to note that the Runnable interface is what is known as a Functional Interface, as it only contains one abstract method. This pairs up greatly when modifying this snippet of code to work well with a lambda expression.

    The following class is a modification of the previous DrawPanel class that used an anonymous inner class inside of the main method. This modification includes use of a lambda expression.



    How does that look?



     
    Piet Souris
    Rancher
    Posts: 1942
    66
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    hi Skye,

    I certainly admire your attitude and dedication!

    As for the lambda part: that was Campbells point, so I let him be the judge of it.
    (but I'd say: don't worry!).

    Then: I agree, I prefer your version, since the all important paintComponent
    is now clearly visible, instead of being somewhat hidden in the anonymous
    version. Nevertheless I showed you the alternative, since that is something
    you will encounter frequently. For instance, say you want an extra panel, above
    your DrawPanel, where you want to have some buttons. Very likely, in
    the Drawpanel constructor, you create another JPanel for this purpose,
    (although for this scenario, you wouldn't need to override paintComponent),
    and add it to your frame, position BorderLayout.PAGE_START.

    So, well done.

    My final remark:
    what I really dislike, are the lines

    and especially
    (hope I put the ";" in ;)
    since that is hiding important stuff.

    You see this code a lot around, unfortunately.

    A JFrame has many parts, RootPane, GlassPane, a MenuBar,
    and, more down to Earth, one or more panels.
    I think that each panel should be responsible for the determination
    of its own size. Either by using setPreferredSize, or letting
    its LayoutManager taking care of that.

    All the frame has to do, is going through its components and
    getting their sizes. You do this by issuing: frame.pack().

    If you look at my version of a couple of replies ago, you can
    see an example of this.

    Well, that's it from me.

    Greetz,
    Piet
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    I haven't tested the Runnable but it looks spot on . Now go back to your folder and list all the .class files in it. Delete them all and recompile. Then revert to the older syntax and repeat the count of .class files. What do you notice?
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Piet Souris,

    Haha well I have to say that I do like using preferredSize and pack more as well. I'm just finishing up my chapters on Swing and have been writing the majority of my new classes by using preferred size. The Layout managers are very nice and I would like for them to accompany my components with as much space as they need! ^_^

    Campbell Ritchie,

    So I did what you recommended and look what we have here... another class file! I didn't even notice that from the old syntax. To be clear for anybody reading, if you use a lambda expression when running the constructor take a look in your directory and you''ll notice one .class file. If you change that and use the old syntax that uses an anonymous inner class you get a separate class file! In this case it is named DrawPanel$1.class. Very interesting!
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    For each anonymous class, you get a little class file called Foo$123 or similar. Not any more when you use λs, though.
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Diggin' Java 8 now.
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Well done, all. I think it't time I flung some darn cows around!
     
    Skye Antinozzi
    Ranch Hand
    Posts: 68
    3
    Eclipse IDE Java
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    A cow! Already!? Awesome! And thanks! *High Fives!*
     
    Piet Souris
    Rancher
    Posts: 1942
    66
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Thanks, Campbell! I really appreciate it.

    Greetz,
    Piet
     
    Campbell Ritchie
    Sheriff
    Posts: 55329
    157
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    You're welcome, both of you. And you only got the cows because the posts deserved it. Not for disagreeing with me, though lots of people would say disagreeing with Campbell always make you right
     
    Don't get me started about those stupid light bulbs.
    • Post Reply Bookmark Topic Watch Topic
    • New Topic
    Boost this thread!