Easy to Learn Java: Programming Articles, Examples and Tips

Start with Java in a few days with Java Lessons or Lectures

Home

Code Examples

Java Tools

More Java Tools!

Java Forum

All Java Tips

Books

Submit News
Search the site here...
Search...
 

Chapter 2. (Introduction) Swing Mechanics. Easy for reading, Click here!

Custom Search
Chapter 2. (Introduction) Swing Mechanics. Easy for reading, Click here!

[ Return to Swing (Book) ]

Page: 11/14 



Previous Page Previous Page (10/14) - Next Page (12/14) Next Page
Subpages: 1. JComponent properties, size, and positioning 
2.  Event handling and dispatching 
3. Multithreading
4. Timers
5. AppContext service
6. Inside Timers & the TimerQueue
7. JavaBeans architecture
8. Fonts, Colors, Graphics and text
9. Using the Graphics clipping area
10. Graphics debugging
11. Painting and validation
12. Focus Management
13. Keyboard input, KeyStrokes, and Actions
14. SwingUtilities

2.11  Painting and validation

At the heart of JComponent's painting and validation mechanism lies a service class called RepaintManager. It is the RepaintManager that is responsible for sending painting and validation requests to the AWT system event queue for dispatching. To summarize, it does this by intercepting repaint() and revalidate() requests, coalescing any requests where possible, wrapping them in Runnable objects, and sending them to invokeLater(). There are a few issues we have encountered in this chapter that deserve more attention here before we actually discuss details of the painting and validation processes.

Note: This section contains a relatively exhaustive explanation of the most complex mechanism underlying Swing. If you are relatively new to Java or Swing we encourage you to skim this section now, but to come back at a later time for a more complete reading. If you are just looking for information on how to override and use your own painting methods, see section 2.8. For customizing UI delegate rendering see chapter 21.

2.11.1  Double buffering

We've mentioned double-buffering, how to disable it in the RepaintManager, and how to specify  the double-buffering of individual components with JComponent's setDoubleBuffered() method. But how does it work?

Double buffering is the technique of painting into an off-screen image rather than painting directly to a visible component. In the end, the resulting image is painted to the screen (which occurs relatively quickly). Using awt components, developers were required to implement their own double-buffering to reduce flashing. It was clear that double-buffering should be a built-in feature because of its widespread use. Thus, it is not much of a surprise to find this feature in all Swing components.

Behind the scenes, double-buffering consists of creating an Image and retrieving its Graphics object to use in all painting methods. If the component being repainted has children, this Graphics object will be passed down to them to use for painting, and so on. So if we are using double-buffering for a component, all its children will also be using double-buffering (whether or not they have double-buffering enabled or not) because they will be rendering into the same Graphics object. Note that there is only one off-screen image per RepaintManager, and there is normally only one RepaintManager instance per applet or application (RepaintManager is a service class that registers a shared instance of itself with AppContext--see section 2.5).

As we discuss in chapter 3, JRootPane is the top-level Swing component in any window (this includes JInternalFrame -- although it isn't really a window). By enabling double-buffering on JRootPane, all of its children will also be painted using double-buffering. As we saw in the last section, RepaintManager also provides global control over all component double-buffering. So another way to guarantee that all components will use double-buffering is to call:

 RepaintManager.currentManager(null).setDoubleBufferingEnabled(true);

2.11.2  Optimized drawing

We didn't really discuss the fact that components can overlap each other in Swing yet, but they can. JLayeredPane, for example, is a container allowing any number of components to overlap each other. Repainting such a container is much more complex than repainting a container we know does not allow overlapping, mainly because of the ability for components to be transparent.

What does it mean for a component to be transparent? Technically this means its isOpaque() method returns false. We can set this property by calling setOpaque(). What opacity means, in this context, is that a component will paint every pixel within its bounds. If this is set to false, we are not guaranteed that this will happen. This is generally set to true, but we will see that when it is set to false it increases the workload of the whole painting mechanism. Unless we are constructing a component that must not fill its entire rectangular region (as we will do in chapter 5 with custom polygonal buttons), we should always leave this set to true, as it is by default for most components. (This value is normally set by a component's UI delegate.)

JComponent's isOptimizedDrawingEnabled() method is overriden to return true for almost all JComponent subclasses except: JLayeredPane, JViewport, and JDesktopPane (a subclass of JLayeredpane). Basically, calling this method is equivalent to asking a component: is it possible that any of your child components can overlap each other? If it is then there is a lot more repainting work to do to take into account that fact that any number of components, from virtually anywhere in our container hierarchy, can overlap each other. Additionaly, since components can be transparent, components layered completely behind others may still show through. Such components are not necessarily siblings (i.e. in the same container) because we could conceivably have several non-opaque containers layered one on top of another. In situations like this, we must do a whole lot of 'tree walking' to figure out which components need to be refreshed. If isOptimizedDrawingEnabled() is overriden to return true, then we assume we do not have to consider any situations like this. Thus, painting becomes more efficient, or 'optimized.'

2.11.3 Root validation

A revalidate() request is generated when a component needs to be laid out again. When a request is received from a certail component there must be some way of determining whether laying that component out will affect anything else/ JComponent's isValidateRoot() method returns false for most components. Basically, calling this method is equivalent to asking: if I lay your contents out again, can you guarantee that none of your parents or siblings will be adversely affected (i.e. need to be laid out again)? By default only JRootPane, JScrollPane, and JTextField return true. This seems surprizing at first, but apparently it is true that these components are the only Swing components whose contents can be successfully laid out, in any situation (assuming no heavyweight components), without affecting parents or siblings. No matter how big we make anything inside a JRootPane, JScrollPane, or JTextField, they will not change size or location unless some outside influence comes into play (i.e. a sibling or parent). To help convince you of this, try adding a multi-line text component to a container without placing it in a scroll pane. You may notice that creating new lines will change its size (depending on the layout). The point is not that it rarely happens or that it can be prevented, but that it can happen. This is the type of thing that isValidateRoot() is supposed to warn us about. So where is this method used?

A component or its parent is normally revalidated when a property value changes and that component's size, location, or internal layout has been affected. By recursively calling isValidateRoot() on a Swing component's parent until we obtain true, we will end with the closest ancestor of that component that guarantees us its validation will not affect its siblings or parents. We will see that RepaintManager relies on this method for dispatching validation requests.

Note: By siblings we mean components in the same container. By parents we mean parent containers.

2.11.4  RepaintManager

As we know there is usually only one instance of a service class in use per applet or application. So unless we specifically create our own instance of RepaintManager, which we will almost never need to do, all repainting is managed by the shared instance which is registered with AppContext. We normally retreive it using RepaintManager's static currentManager() method:

myRepaintManager = RepaintManager.currentManager(null);

This method takes a Component as its parameter. However, it doesn't matter what we pass it. In fact the component passed to this method is not used anywhere inside the method at all (see the RepaintManager.java source code), so a value of null can safely be used here. (This definition exists for sub-classes to use if they want to work with more than one RepaintManager, possibly on a per-component basis.)

RepaintManager exists for two purposes: to provide efficient revalidation and repainting. It intercepts all repaint() and revalidate() requests. This class also handles all double-buffering in Swing and maintains a single Image used for this purpose. This Image's maximum size is, by default, the size of the screen. However, we can set its size manually using RepaintManager's setDoubleBufferMaximumSize() method. (All other RepaintManager functionality will be discussed throughout this section where applicable.)

Note: Cell renderers used in components such as JList, JTree, and JTable are special in that they are wrapped in instances of CellRendererPane and all validation and repainting requests do not propogate up the containment hierarchy. See chapter 17 for more about CellRendererPane and why this behvaior exists. It suffices to say here that cell renderers do not follow the painting and validation scheme discussed in this section.

2.11.5  Revalidation

RepaintManager maintains a Vector of components that need to be validated. Whenever a revalidate request is intercepted, the source component is sent to the addInvalidComponent() method and its "validateRoot" property is checked using isValidateRoot(). This occurs recursively on that component's  parent until isValidateRoot() returns true. The resulting component, if any, is then checked for visibility. If any one of its parent containers is not visible there is no reason to validate it. Otherwise RepaintManager 'walks down its tree' until it reaches the 'root' component, a Window or Applet. RepaintManager then checks the invalid components Vector and if the component isn't already there it is added. After being successfully added, RepaintManager then passes the 'root' to the SystemEventQueueUtilities' queueComponentWorkRequest() method (we saw this class in section 2.3). This method checks to see if there is a ComponentWorkRequest (this is a private static class in SystemEventQueueUtilities that implements Runnable) corresponding to that 'root' already stored in the work requests table. If there isn't one, a new one is created. If there is one already, we just grab a reference to it. Then we synchronize access to that ComponentWorkRequest, place it in the work requests table if it is a new one, and check if it is pending (i.e. it has been added to the AWT system event queue). If it isn't pending, we send it to SwingUtilities.invokeLater(). It is then marked as pending and we leave the synchronized block. When it is finally run from the event-dispatching thread it notifies RepaintManager to execute validateInvalidComponents(), followed by paintDirtyRegions().

The validateInvalidComponents() method basically checks RepaintManager's Vector containing the components in need of validation, and calls validate() on each one. (This method is actually a bit more careful than we describe here, as it synchronizes access to prevent the addition of invalid components while executing).

Note: Remember that validateInvalidComponents() should only be called from within the event-dispatching thread. Never call this method from any other thread. The same rules apply for paintDirtyRegions().

The paintDirtyRegions() method is much more complicated, and we'll discuss some of its details below. For now, it suffices to say that this method paints all damaged regions of each component maintained by RepaintManager.

2.11.6  Repainting

JComponent defines two repaint() methods, and the no-argument version of repaint() is inherited from java.awt.Container:

   public void repaint(long tm, int x, int y, int width, int height)

   public void repaint(Rectangle r)

   public repaint() // inherited from java.awt.Container

If you call the no-argument version the whole component is repainted. For small, simple components this is fine. But for larger, more complex components this is certainly not efficient. The other two methods take the bounding region to be repainted (the dirtied region) as parameters. The first method's int parameters correspond to the x-coordinate, y-coordinate, width, and height of that region. The second method takes the same information encapsulated in a Rectange instance. The second repaint() method shown above just sends its traffic to the first. The first method sends the dirtied region's parameters to RepaintManager's addDirtyRegion() method.

Note: The long parameter in the first repaint() method represents absolutely nothing and is not used at all. It does not matter what value you use for this. The only reason this is here is to override the correct repaint() method from java.awt.Component.

RepaintManager maintains a Hashtable of dirty regions. Each component will have at most one dirty region in this table at any time. When a dirty region is added, using addDirtyRegion(), the size of the region and the component are checked. On the off chance that either has a width or height <= 0 the method returns and nothing happens. If it is bigger than 0x0, the source component's visibility is then tested along with each of its ancestors, and, if they are all visible, its 'root' component, a Window or Applet, is located by 'walking down its tree' (similar to what occurs in addInvalidateComponent()). The dirty regions Hashtable is then asked if it already has a dirty region of our component stored. If it does it returns its value, a Rectangle, and the handy SwingUtilities.computeUnion() method is used to combine the new dirty region with the old one. Finally, RepaintManager passes the 'root' to the SystemEventQueueUtilities' queueComponentWorkRequest() method. What happens from here on is identical to what we saw for revalidation (see above).

Now we can talk a bit about the paintDirtyRegions() method we summarized earlier. (Remember that this should only be called from within the event-dispatching thread.) This method starts out by creating a local reference to RepaintManger's dirty regions Hashtable, and redirecting Repaintmanager's dirty regions Hashtable reference to a different, empty one. This is all done in a critical section so that no dirty regions can be added while the swap occurs. The remainder of this method is fairly long and complicated so we'll conclude with a summary of the most significant code (see RepaintManager.java for details).

The paintDirtyRegions() method continues by iterating through an Enumeration of the dirty components,  calling RepaintManager's collectDirtyComponents() method for each. This method looks at all the ancestors of the specified dirty component and checks each one for any overlap with its dirty region using the SwingUtilities.computeIntersection() method. In this way each dirty region's bouds are minimized so that only its visible region remains. (Note that collectDirtyComponents() does take transparency into account.) Once this has been done for each dirty component, the paintDirtyRegions() method enters a loop. This loop computes the final intersection of each dirty component and its dirty region. At the end of each iteration paintImmediately() is called on the associated dirty component, which actually paints each minimized dirty region in its correct location (we'll discuss this below). This completes the paintDirtyRegions() method, but we still have the most significant feature of the whole process left to discuss: painting.

2.11.7 Painting

JComponent includes an update() method which simply calls paint(). The update() method is never actually used by any Swing components at all, but is provided for backward compatibility. The JComponent paint() method, unlike typical AWT paint() implementations, does not handle all of a component's painting. In fact it very rarely handles any of it directly. The only rendering work  JComponent's paint() method is really responsible for is working with clipping areas, translations, and painting pieces of the Image used by RepaintManager for double-buffering. The rest of the work is delegated to several other methods. We will briefly discuss each of these methods and the order in which painting operations occur. But first we need to discuss how paint() is actually invoked.

As we know from our discussion of the repainting process above, RepaintManager is responsible for invoking a method called paintImmediately() on each component to paint its dirty region (remember there is always just one dirty region per component because they are intelligently coalesced by RepaintManager). This method, and the private ones it calls, make an intelligently crafted repainting process even more impressive. It first checks to see if the target component is visible, as it could have been moved, hidden, or disposed since the original request was made. Then it recursively searches the component's non-opaque (using isOpaque()) parents and increases the bounds of the region to repaint accordingly until it reaches an opaque parent:

1. If the parent reached is a JComponent subclass, the private _paintImmediately() method is called and passed the newly computed region. This method queries the isOptimizedDrawing() method, checks whether double-buffering is enabled (if so it uses the off-screen Graphics object associated with RepaintManager's buffered Image), and continues working with isOpaque() to determine the final parent component and bounds to invoke paint() on.

A. If double-buffering is enabled, it calls paintWithBuffer()(another private method). This method works with the off-screen Graphics object and its clipping area to generate many calls to the parent's paint() method (passing it the off-screen Graphics object using a specific clipping area each time). After each call to paint(), it uses the resulting off-screen Graphics object to draw directly to the visible component. (In this specific case, the paint() method will not use any buffers internally because it knows, by checking certain flags we will not discuss, that the buffering is being taken care of elsewhere.)

B. If double-buffering is not enabled, a single call to paint() is made on the parent.

2. If the parent is not a JComponent, the region's bounds are sent to that parent's repaint() method, which will normally invoke the java.awt.Component paint() method. This method will then forward traffic to each of its lightweight children's paint() method. However, before doing this it makes sure that each lightweight child it notifies is not completely covered by the current clipping area of the Graphics object that was passed in.

In all cases we have finally reached JComponent's paint() method!

Inside JComponent's paint() method, if graphics debugging is enabled a DebugGraphics instance will be used for all rendering. Interestingly, a quick look at JComponent's painting code shows heavy use of a class called SwingGraphics. This isn't in the API docs because its package private. It appears to be a very slick class for handling custom translations, clipping area management, and a Stack of Graphics objects used for caching, recyclability, and undo-type operations. SwingGraphics actually acts as a wrapper for all Graphics instances used during the painting process. It can only be instantiated by passing it an existing Graphics object. This functionality is made even more explicit, by the fact that it implements an interface called GraphicsWrapper, which is also package private.

The paint() method checks whether double-buffering is enabled and whether this method was called by paintWithBuffer() (see above):

1. If paint() was called from paintWithBuffer() or if double-buffering is not enabled, paint() checks whether the clipping area of the current Graphics object is completely obscured by any child components. If it isn't, paintComponent(), paintBorder(), and paintChildren() are called in that order. If it is completely obscured, then only paintChildren() needs to be called. (We will see what these three methods do shortly.)

2. If double-buffering is enabled and this method was not called from paintWithBuffer(), it will use the off-screen Graphics object associated with RepaintManager's buffered Image throughout the remainder of this method. Then it will check whether the clipping area of the current Graphics object is completely obscured by any child components. If it isn't, paintComponent(), paintBorder(), and paintChildren() will be called in that order. If it is completely obscured, only paintChildren() needs to be called.

A. The paintComponent() method checks if the component has a UI delegate installed. If it doesn't it just exits. If it does, it simply calls update() on that UI delegate and then exits. The update() method of a UI delegate is normally responsible for painting a component's background, if it is opaque, and then calling paint(). A UI delegate's paint() method is what actually paints the corresponding component's content. (We will see how to customize UI delegates extensively throughout this text.)

B. The paintBorder() method simply paints the component's border if it has one.

C. The paintChildren() method is a bit more involved. To summarize, it searches through all child components and determines whether paint() should be invoked on them using the current Graphics clipping area, the isOpaque() method, and the isOptimizedDrawingEnabled() method. The paint() method called on each child will essentially start that child's painting process from part 2 above, and this process will repeat until either no more children exist or need to be painted.

The bottom line: When building or extending lightweight Swing components it is normally expected that if we want to do any painting within the component itself (vs. in the UI delegate where it normally should be done) we will override the paintComponent() method and immediately call super.paintComponent(). In this way the UI delegate will be given a chance to render the component first. Overriding the paint() method, or any of the other methods mentioned above should rarely be necessary, and it is always good practice to avoid doing so.



[ Return to Swing (Book) ]


Top 10 read Java Articles
 Get free "1000 Java Tips eBook"

 Java Calendar and Date: good to know facts and code examples

 Array vs ArrayList vs LinkedList vs Vector: an excellent overview and examples

 How can I convert any Java Object into byte array? And byte array to file object

 The Java Lesson 1: What is Java?

 How do I compare two dates and times, date between dates, time between times and

 Maven vs Ant or Ant vs Maven?

 How to open, read, write, close file(s) in Java? Examples on move, rename and de

 Java Array

 Java: JLabel font and color


[ More in News Section ]
Java Lessons

The Java Lesson 1:
What is Java?
The Java Lesson 2:
Anatomy of a simple Java program
The Java Lesson 3:
Identifiers and primitive data types
The Java Lesson 4:
Variables, constants, and literals
The Java Lesson 5:
Arithmetic operations, conversions, and casts
The Java Lesson 6:
Boolean expressions and operations
The Java Lesson 7:
Bitwise operations
The Java Lesson 8:
Flow control with if and else
The Java Lesson 9:
switch statements
The Java Lesson 10:
for, while, and do-while statements
The Java Lesson 11:
Using break and continue
The Java Lesson 12:
Class methods and how they are called
The Java Lesson 13:
Using the Math class
The Java Lesson 14:
Creating and calling custom class methods
The Java Lesson 15:
Overloading class methods
The Java Lesson 16:
An introduction to objects and object references
The Java Lesson 17:
The String class
The Java Lesson 18:
The StringBuffer class
The Java Lesson 19:
Initializing and processing arrays of primitives
The Java Lesson 20:
Initializing and processing arrays of objects
The Java Lesson 23:
Inheritance and overriding inherited methods
The Java Lesson 24:
abstract classes and polymorphism
The Java Lesson 25:
Interfaces, instanceof, and object conversion and casting
The Java Lesson 26:
Introduction to graphical programming and the java.awt packa
The Java Lesson 27:
The Component class
The Java Lesson 28:
Containers and simple layout managers
The Java Lesson 29:
The Color and Font classes
The Java Lesson 30:
Drawing geometric shapes
The Java Lesson 31:
Choice, List, and Checkbox controls
The Java Lesson 32:
Using the Scrollbar graphical control
The Java Lesson 33:
Menus and submenus
The Java Lesson 34:
An introduction to applets and the Applet class
The Java Lesson 35:
Essential HTML to launch an applet and pass it parameters
The Java Lesson 36:
Mouse event processing
Java Lesson 37:
Menus and submenus
Java Lesson 38:
The WindowListener interface and the WindowAdapter class
Java Lesson 39:
An introduction to GridBagLayout
Java Lesson 40:
An introduction to the Java Collections API
Java Lesson 41:
Exception handling with try, catch, and finally blocks
Java Lesson 42:
Claiming and throwing exceptions
Java Lesson 43:
Multithreading, the Thread class, and the Runnable interface
Java Lesson 44:
An introduction to I/O and the File and FileDialog classes
Java Lesson 45:
Low-level and high-level stream classes
Java Lesson 46:
Using the RandomAccessFile class
Java Lessons by
Joh Huhtala: Update

Latest articles
 Java Profiler JProbe to Resolve Performance Problems Faster

 SSL with GlassFish v2, page 5

 SSL with GlassFish v2, page 4

 SSL with GlassFish v2, page 3

 SSL with GlassFish v2, page 2

 The Java Lesson 2: Anatomy of a simple Java program, page 2

 New site about Java for robots and robotics: both software and hardware.

 Exceptions -III: What's an exception and why do I care?

 Exceptions -II: What's an exception and why do I care?

 Exceptions: What's an exception and why do I care?

 Double your Java code quality in 10 minutes, here is receipt

 Murach's Java Servlets and JSP

 How to get ascii code from a char in Java?

 Can we just try without catch? Yes!

 Make Tomcat page load faster

 Make your Tomcat More secure - limit network address for certain IP addresses

 New Java book online starts now here...

 Implementing RESTful Web Services in Java

 Firefox trimming from 1 GB to 40 Mb with many tabs opened

 SSL with GlassFish v2

 My request to replublish Tech Tips

 Search JavaFAQ.nu site here

 New Advanced Installer for Java 6.0 brings XML updates and imports 3rd party MSI

 EJB programming restrictions

 Maven vs Ant or Ant vs Maven?

 Why Java does not use default value which it should?

 How to unsign signed bytes in Java - your guide is here

 The Java Lesson 3: Identifiers and primitive data types. Page 2

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 4

 The Java Lesson 7: Bitwise operations with good examples, click here! Page 3


[ More in News Section ]


Home Code Examples Java Forum All Java Tips Books Submit News, Code... Search... Offshore Software Tech Doodling

RSS feed Java FAQ RSS feed Java FAQ News     

    RSS feed Java Forums RSS feed Java Forums

All logos and trademarks in this site are property of their respective owner. The comments are property of their posters, all the rest 1999-2006 by Java FAQs Daily Tips.

Interactive software released under GNU GPL, Code Credits, Privacy Policy