The Source for Java Technology Collaboration
User: Password:



Alexander Potochkin's Blog

Swing Archives


JXLayer 3.0 - MouseScrollableUI

Posted by alexfromsun on June 24, 2008 at 10:39 AM | Permalink | Comments (1)

The mouse auto-scrolling is very popular these days for modern applications. I usually use this feature in the Firefox browser - you click the mouse wheel somewhere on the page and can immediately start scrolling.

A few months ago Kirill wrote an great review of the "auto-scrolling feature" implementation in Swing. I'd like to add one more item to the list.

Suppose you create a JTable, wrap it with JScrollPane and add it to a frame:

JScrollPane sp = JScrollPane(createMyTable());
add(sp);             

To enable auto-scrolling you just need to wrap a JScrollPane with JXLayer and set the MouseScrollableUI to it:

JScrollPane sp = JScrollPane(createMyTable());
JXLayer<JScrollPane> l = new JXLayer<JScrollPane>(sp, new MouseScrollableUI());        
add(l);             

After that you can you can activate auto-scrolling by clicking the mouse wheel:

demo screenshot

When the scrolling indicator appears, your can scroll by moving your mouse towards the desirable direction. Auto-scrolling is deactivated by clicking any keyboard or mouse button.

Implementation

Things like MouseScrollableUI are very easily implemented with JXLayer, because it provides a way to catch all mouseEvents for all components inside a JXLayer.
class MyLayerUI extends AbstractLayerUI<JComponent> {
    
    // This method catches all focus, mouse and keyboard events
    // for the layer and all its subcomponents
    @Override
    public void eventDispatched(AWTEvent e, JXLayer<JComponent> l) {
        super.eventDispatched(e, l);
        System.out.println("AWTEvent is dispatched: " + e);
    }

    // Utility methods which are called from eventDispatched()
    // to provide a hook for catching particular type of events
    @Override
    protected void processMouseEvent(MouseEvent e, JXLayer<JComponent> l) {
        System.out.println("MouseEvent on component: " + e.getComponent());
    }
    
    @Override
    protected void processMouseMotionEvent(MouseEvent e, JXLayer<JComponent> l) {
        System.out.println("MouseMotionEvent on component: " + e.getComponent());
    }    
}

Once you have a way to catch all mouseEvents for all subcomponents, everything else is straightforward. The only specific details is the scrolling indicator, initially I thought to paint it, as usual, in the paintLayer() method.

However I realized that I want to change the mouse cursor when I move the mouse over scrolling indicator and the best solution is to implement it as a custom component, set a resize mouse cursor and place it at the layer's glassPane.

The source code of the MouseScrollableUI is quite compact, the MouseScrollableDemo for the webstart demo is even shorter.
(the runnable link is above the screenshot)

See you on the JXLayer forum

Thanks
alexp

JXLayer 3.0 - Getting started

Posted by alexfromsun on June 05, 2008 at 09:49 AM | Permalink | Comments (10)

It is my pleasure to announce a major update of JXLayer component. The new version is hosted on its own java.net project jxlayer.dev.java.net, where I will also provide links to all my blogs about this component.

So, why I encourage everybody to try out the new JXLayer?
Actually to answer this question I am going to write several more blogs, here is a short answer:

  1. more consistent and efficient API
  2. easier to use
  3. new functionality

The JXLayer 3.0 API is a bit different from the previous version and I had good reasons to make it this way. The good news is that I am very happy with the current API and I am not gonna introduce incompatible changes in the future. So, if you are using the previous JXLayer, it won't take much time to adjust your code for the new one, if you haven't tried it yet, it's high time to have a look.

JXLayer is the universal decorator for Swing components, it means that you have a flexible way to enrich the visual appearance of your components and control the input events for the whole component's hierarchy.

Just wrap your component with JXLayer and set a LayerUI which implements the required functionality. On jxlayer.dev.java.net you'll find demos of several carefully tested and ready to use LayerUI's implementations. In this entry we are going to see how to create your own ones.

Here is a simple example how to create and use LayerUI which paints translucent foreground over the layer:

        // wrap your component
        JXLayer<JComponent> layer = new JXLayer<JComponent>(myComponent);

        // create custom LayerUI
        AbstractLayerUI<JComponent> layerUI = new AbstractLayerUI<JComponent>() {

            @Override  
            protected void paintLayer(Graphics2D g2, JXLayer<JComponent> l) {
                // this paints layer as is
                super.paintLayer(g2, l);
                // custom painting:
                // here we paint translucent foreground
                // over the whole layer
                g2.setColor(new Color(0, 128, 0, 128));
                g2.fillRect(0, 0, l.getWidth(), l.getHeight());
            }
        };

        // set our LayerUI
        layer.setUI(layerUI);

        // add the layer as a usual component
        frame.add(layer);

BufferedLayerUI provides more functionality, since it paints the layer to the double buffer image, it allows filtering the image with the BufferedImageOp subclasses. With it you can invert colors of a layer, blur it apply any other fancy effects.

For the Rainbow project I used BufferedLayerUI to implement the image editor dialog.
(Here are some more Rainbow screenshots)

Let's see how to apply the grayScale effect to a layer:

        // wrap you component
        JXLayer<JComponent> layer = new JXLayer<JComponent>(myComponent);

        // here we use BufferedLayerUI which can work with BufferedImageOps  
        BufferedLayerUI<JComponent> bufferedLayerUI = new BufferedLayerUI<JComponent>();
        
        // create a ColorConvertOp to apply grayScale effect
        BufferedImageOp grayScaleOp = 
                new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null);

        // create a BufferedImageOpEffect with the provided BufferedImageOp 
        BufferedImageOpEffect imageOpEffect = new BufferedImageOpEffect(grayScaleOp);
        
        // set BufferedImageOpEffect to the bufferedLayerUI
        bufferedLayerUI.setLayerEffects(imageOpEffect);
        
        // set the bufferedLayerUI to the layer 
        layer.setUI(bufferedLayerUI);

        // add the layer as a usual component
        frame.add(layer);

And now is the very important news: with JXLayer 3.0 you can catch layer's input events as easy as implement a custom painting. AbstractLayerUI introduces several methods: processFocusEvent, processMouseEvent, processMouseMotionEvent, etc...
The names of the methods speak for themselves. If you'd like to catch all mouseEvents for all components inside your layer - just override processMouseEvent in your custom AbstractLayerUI subclass.

MouseDrawingDemo combines painting and mouse events processing together.

In this demo you can paint over the layer with your mouse, just like you do in any other image editor.
Note that components are alive when you paint over them.

I hope this short overview will help you to invent your own ways of using JXLayer in your projects.
See you one the JXLayer forum

With best wishes
alexp

RepaintManager's side effect

Posted by alexfromsun on April 13, 2008 at 07:49 AM | Permalink | Comments (2)

When I worked on the lightweight disabling of compound Swing components for my JXLayer project, I tried different approaches to workaround the problems with the custom painting of double-buffered components.

The last try was using the JComponent.print() method instead of JComponent.paint(), which effectively disables the double-buffering for the whole hierarchy of components. While it seems to be a good solution, it has a flaw: print methods may be overridden to provide a specific painting logic when a component is printed rather than painting on the screen.
(Thanks to David Browne and Kirill Grouchnikov for this information)

Finally I found a correct way of disabling swing containers without recursive disabling of double buffering for the subcomponents or using print() method. I'll describe it in my next blogs
(Some great news are coming)

Anyway, you may be surprised why I spent my time on that tricks when there is a much cleaner solution for disabling double buffering in Swing. I am talking about RepaintManager.setDoubleBufferingEnabled(boolean) method. At the beginning of painting you can switch the double buffering off and switch it on again at the end. Despite the fact that it changes the global setting for the whole Swing, no one will get hurt because Swing is a single-threaded library. It makes sense, doesn't it?

I must admit that I don't use RepaintManager in my java.net projects because I am aware of one side effect it causes.

Do you remember the famous GrayRect fix for JDK 6?

The main feature of this fix is the true per-window double buffer implementation, which eliminates unnecessary painting when a window of your java application is brought to the front after it was overlapped by another window. With JDK 5 you often see a gray rectangle when the damaged area is repainted, with JDK 6 the buffered image of the window's content is immediately copied to the screen so can't notice any traces of that.

RepaintManager.currentManager(null).setDoubleBufferingEnabled(false) disables the all Swing double buffering. Unfortunately, it is a one-way street for the gray rect fix, calling setDoubleBufferingEnabled(true) after that, enables Swing double buffering, but the gray rect fix is to be disabled forever.

The system RepaintManager is created by a private constructor with a special flag which enables the true double buffering. Once you set your own implementation of RepaintManager, the gray rect fix is gone, because there is no public API to enable true double buffering for RepaintManager subclasses.

So there is the same problem when you install a custom implementation of the RepaintManager.

This demo demonstrates the effect (JDK 6 is required):

Each time the JFrame.paint() is called, the new line is added to the JTextArea. When the demo window is shown, the frame is painted once. Check out the gray rect fix by moving another window on top of the demo's frame, you'll see that JFrame.paint() is not get called, because Swing uses the per-window buffer instead. This can also be checked without another application, just move the demo's frame partly outside the window back and forth, it will not trigger frame's repainting as well.

With the Options menu you can set a new RepaintManager or switch the double buffering off and on again, any of these action will disable the gray rect fix. Choose one of the actions and move the other window on top of the demo again, you'll see that the frame starts to repaint itself and the true double buffering is gone.

The source code for this demo is available.

That's all for today,
take care and see you soon

alexp

JTrayIcon update

Posted by alexfromsun on February 14, 2008 at 11:12 AM | Permalink | Comments (21)

JDK 6 introduced plenty of cool desktop features, like famous gray-rect fix or advanced drag and drop support,
all of them made java much closer to the desktop application market and I am very exited about them.

There have been many blogs how useful new features are, and I don't want to repeat them. Among all successful and well-designed features there is one which actually has not been completed,
I am speaking about JTrayIcon.

Swing and AWT team work together (by the way, AWT team seats in 10 steps from my room), AWT is responsible for the "native" parts of the code, like topLevel frames, event processing etc,
when Swing team if focused on their lightWeight components. Actually it is AWT who isolates Swing from any native code and makes Swing cross-platform.

It wasn't a surprise that TrayIcon feature came to AWT team, and they implemented it perfectly - it works well and it is supported for various Windows, Linux and Solaris systems.
They made it customizable by providing mouse event handling support and a special method setPopupMenu() which takes java.awt.Popup (which is quite old-fashioned but remember it was the AWT feature)

JPopupMenu doesn't extend awt.Popup but it looked like it would be easy to attach JPopupMenu to the trayIcon with help of MouseListener, but it was not the case.

The fact is that there is no way (or at least easy one) to correctly attach JPopupMenu to a TrayIcon.
It means that you can't set an icon for a TrayIcon's menuItem or make it look according to the current Look and Feel and, from my point of view, it makes TrayIcon much less useful than it could be.

It is our fault, sorry about that. Now let's see how we can solve this problem.

To make JTrayIcon possible I fixed three particular bugs for the next JDK 6 update release (the famous update #10, aka 6uN) and for the JDK 7 beta

ClassCastException in Swing with TrayIcon

If JPopupMenu is shown from a trayIcon's mouseListener an ugly ClassCastException was thrown in some cases.

Keyboard navigation problems and highlighting of the first item

If popup menu lost focus it stopped processing keyboard events in some cases, the first item a popup was highlighed by default which is very different from any other GUI I've seen

Swing Popups should overlap taskbar

Finally it was impossible to implement a good looking trayicon's popup menu, because Swing popups couldn't overlap taskbar

Having integrated all three fixes I managed to write the JXTrayIcon class which extends java.awt.TrayIcon and contains setJPopuMenu(JPopupMenu) method

Here is how JXTrayIcon looks when you click on it, with the current JDK 6

JXTrayIcon with the current JDK 6

the same code is run under JDK 6 update 10 or JDK 7

JXTrayIcon with JDK 6u10 or JDK 7

The red quarter circle icon is owned by the JXTrayIcon's test.
Note that the first item is not highlighted by default when the popup menu is shown,
if you move the mouse over it or press the arrow button on your keyboard, the proper item will be highlighted as with a system popup menu.

I encourage you to download the JDK 6u10 early bits and try out the JXTrayIcon demo
The runnable jar file is also available for your convenience

I particularly interested how it works in different environments,
any comments and suggestions are welcome as usual

Thank you and sorry this took so long.I hope it was worth the wait.

alexp

Making Rainbow: Spotlight effect and soft clipping

Posted by alexfromsun on November 29, 2007 at 11:32 AM | Permalink | Comments (6)

Few weeks ago I submitted a session for the next JavaOne and it reminded me the previous session which we presented with Kirill

Before J1, he published a nice teaser with links to blogs about his famous ghost effects and transition layout, so it's hight time for me drop a few lines about implementing a spotLight effect. You can test the whole application on the main page of the Rainbow project, here is a flash demo which I made specially for this blog (press the green button on the bottom left side)


The shortest version of the SpotLightPainter was about 30 lines of code. I slightly modified the original painter from the Rainbow project and included the SpotLightPainter and the SpotLightDemo to the demo direcotry for JXLayer.
The painter's code is quite straightforward, there are just two things I'd like to comment:

ForegroundPainter

SpotlightPainter has a layered structure: the scrollPane's view component and the tranclucent foreground with the "holes" for matched components. I divide the painting code between two painters - SpotLightPainter extends DefaultPainter, so the wrapped component is automatically painted when you call super.paint(g2, l), the foregroundPainter is responsible for painting the tranclucent foreground:
    @Override
    public void paint(Graphics2D g2, JXLayer<V> l) {
        super.paint(g2, l);
        foregroundPainter.paint(g2, l);
    }

The foregroundPainter extends AbstractBufferedPainter which means it is painted via the intermediate image, this is the best solution when it comes to translucency. I fill the whole area with the translucent color and then clip out the shapes over the matched components:

        this.foregroundPainter = new AbstractBufferedPainter<V>() {
            @Override
            protected void paintToBuffer(Graphics2D g2, JXLayer<V> l) {
                
                // cleaning up
                g2.setComposite(AlphaComposite.Clear);
                g2.fillRect(0, 0, l.getWidth(), l.getHeight());
                
                // filling with overlayColor
                g2.setComposite(AlphaComposite.SrcOver);
                g2.setColor(overlayColor);
                g2.fillRect(0, 0, l.getWidth(), l.getHeight());
                
                // clipping out the "holes" for matched components
                for (Shape shape : clipList) {
                    g2.setClip(shape);
                    g2.setComposite(AlphaComposite.Clear);
                    g2.fill(shape);
                    softClipping(g2, shape);
                }
            }
        };

The important thing that by default, AbstractBufferedPainter.isIncrementalUpdate() returns false, it means that this painter will be updated only if the whole JXLayer is repainted, e.g. when the frame is resized or when SpotLightPainter.fireLayerItemChanged() is called. This optimization allows reusing the foreground image for faster painting, it doesn't slow down the transition effect animation because when you move the mouse over a button, foregroundPainter paints the cached image.

Soft clipping

When we prepared our session, Kirill drew my attention to the fact that spotlights didn't look smooth, here the zoomed out screenshot which shows how it looked like:
Initial clipping

as the result it was possible to see the jaggies on the spotlight's borders and that made this effect look unprofessional. The remedy for this problem is called "soft clipping" and the pefect article by Chris Cambell is the first thing that came to my mind when I thought about that.

The trick from Chris produces the very good result but it works for fixed shapes only, I mean you should know the exact shape in advance to create a mask for it. I decided to find a way how to make soft clipping for arbitrary shapes and I actually found it, but... a few minutes ago I came across the next Chris's blog Light and Shadow (I missed it for some reason) where he described exactly what I thought was invented by me! :-)
In the comments for that blog there are a few more reports from people who came to this solution even earlier.

The idea is to mix in the alpha value to the pixels on the shape's border by drawing the same shape with a set BasicStroke. For the Rainbow project I painted it once with the 50% alpha and doubled basicStroke width.

Initial clipping

For the JXLayer's demo I updated SpotLightPainter to be able to set the custom width for the soft clipping, and make it as smooth as you want:

    private void softClipping(Graphics2D g2, Shape shape) {
        g2.setComposite(AlphaComposite.Src);
        for (int i = 0; i < softClipWidth; i++) {
            int alpha = (i + 1) * overlayColor.getAlpha()
                    / (softClipWidth + 1);
            Color temp = new Color(
                    overlayColor.getRed(),
                    overlayColor.getGreen(), 
                    overlayColor.getBlue(), alpha);
            g2.setColor(temp);
            g2.setStroke(new BasicStroke(softClipWidth - i));
            g2.draw(shape);
        }
    }

Spotlight demo

MouseEvents filtering

Components which are outside the spotlights shouldn't react on the mouse events and this was the simplest task for the SpotLightPainter
    @Override
    public boolean contains(int x, int y, JXLayer<V> l) {
        for (Shape shape : clipList) {
            if (shape.contains(x, y)) {
                return true;
            }
        }
        return false;
    }

Conclusion

To add this functionality to your application you just need to wrap you component with JXLayer:
JXLayer<JComponent> layer = 
     new JXLayer<JComponent>(myComponent, spotLightPainter)

add(new JScrollPane(layer));

All source and binary files you can find on the SwingHelper project site

Thanks
alexp

Debug Swing repainting

Posted by alexfromsun on November 06, 2007 at 11:22 AM | Permalink | Comments (9)

As everybody knows if you need to repaint a Swing component you should call Component.repaint() method, another well-known tip for the fast painting is:

If only part of your component needs to be updated, make paint requests using a version of repaint that specifies the painting region.

It is very simple piece of advice - not to paint more than you need, but sometimes this situation is not easy to catch. Usually it is obvious when you paint less than you need, but repainting something which hasn't been changed is a kind of "invisible" operation.

If the whole frame is repainted when any of its children is repainted you would hardly notice it on modern computer and that is the reason why I decided to return to my old love, Swing debugging and add one debugging tool to JXLayer project

A visual indication for repainting events must work for every component, no matter which color it is painted, so I created an elegant layer's UI delegate which catches repainting and shows fade-out effect using inverted colors for a repainted area.

With this painter you can study your GUI and eliminate unnecessary painting, moreover it is very interesting just to see how Swing paints the core components - what part of a JTree is repainted when you open a node? What is repainted when you open a popup ?
Now it easy to get answers for this kind of questions.

For example, it is easy to see what part of JTextField is repainted when you type in it and what happens when JTree's selection is changed.I was also very pleased to check that we don't paint more than we need when selection of JTable is changed (second tab in the demo)

I hope you'll find it useful for painting optimization for your custom components and LookAndFeels.
As usual it works for all LookAndFeels, the only thing you need is to wrap a container with your components with JXLayer and set DebugPainter from JXLayer's demo package.

A flash presentation is available, press the green button below to start:

Have a nice day
alexp

Why I don't use Swing hacks (continuation)

Posted by alexfromsun on October 09, 2007 at 04:50 AM | Permalink | Comments (7)

In one of my previous blogs I answered to Kirill's comment that some of techniques he described in his Swing painting pipeline overview are "not good enough for me",
here I want to explain my point by brifely commenting some of the techinques he covered.

Implementing validation overlays

First of all I want to mention my requirements to the validation overlays implementation:

  1. Solution shouldn't change global settings, it has to be as compatible with any other projects as possible
  2. It should work well inside JScrollPane
  3. Validation icons should respect Z-order of sibling components

RepaintManager

When a component requires a custom RepaintManager to be installed it sounds for me as strange as if I require to customize your operating system for my application.
RepaintManager is a global resouce and changing it to support a particular component is not just awkward but it may cause problems if another component wants to install its own RepaintManager at the same time.
Since Swing repaints components asynchronously it is impossible to fix this potential conflict by saving and restoring the old RM after repaint() is called.

Note: SwingX uses custom RM for their needs but it's a special case.
SwingX is a project which contains extentions for the Swing GUI toolkit which may become a part of the JDK.
If we decide to include e.g. translucency to Swing, the default RM will be the best choice to add support for this feature and no doubt, in this case their experience will be very useful.
Since a custom RepaintManager works well if you are sure that no one else will change it, I take RepaintManagerX as the only recommended alternative implementation

GlassPane

For me it is quite obvious that GlassPane is not the best solution for validation overlays.
I know that with GlassPane it is impossible to write a robust code which will quickly and correctly:

  1. move/hide/show validation icon when its component is moved/hidden/shown
  2. crop the icon when its component is overlapped by another component

I don't even need to repeat that GlassPane is a global resource etc, because all mentioned issues together with the popups issue make GlassPane unusable for validation overlays implementation

JLayeredPane

It is the most interesting hack and it deserves special attention because of two remarkable projects.
Long time ago I came across the Decorating/Overpainting Swing Components blog with an impressive demo by Timothy Wall.
He transparently adds all kinds of custom components to the frame's default JLayeredPane to decorate existing components.

The demo is really cool, but...

  • It fails to meet my second requirement about quick painting inside JScrollPane
    (I can see the labels' flickering when I scroll the JScrollPane in the demo, sorry about that)
  • It also fail to meet the third requrement because if two sibling non-opaque components are overlapped it is impossible to detect which component's pixels are transparent to correctly mix their decorations
    (I'll give an example later on)

In addition in the AbstractComponentDecorator.java code I found a bunch of FIXME and TODO notes which reminds the situation with my JXTransformer
- you can make it work for a demo, but it will never be completely ready for production use

The IconFeedbackPanel by Karsten Lentzsch is another tricky solution. I wrote a simple test with a JTextField inside IconFeedbackPanel with a validation icon,
then I wrapped the JTextField with JScrollPane and the icon unexpectedly was gone.
My first though was that IconFeedbackPanel doesn't support inner scrollPanes at all but then I read this note from its constructor javaDoc:

Note: Typically you should wrap component trees with getWrappedComponentTree(ValidationResultModel, JComponent)}, not this constructor.

When I tried the magic getWrappedComponentTree method the validation icon appeared in the inner JScrollPane and were repainted very quickly during scrolling.
After a few minitues I found the reason - this method traverses the component's hierarchy and wraps JScrollPane's views with additional IconFeedbackPanels!
This is an important detail, I'll return to it later

The Karsten's code is clear and well-written as usual, only a few things about IconFeedbackPanel caught my attention

  • It removes all validation icons, adds them again and repaints the whole panel during every layout change, which is pretty expensive
  • Quote from javadoc:

    you must ensure that the wrapped content provides enough space to display the overlaid components.
    Since the current implementation positions the overlay components in the lower left, just make sure that there are about 6 pixel to the left and bottom of the input components that can be marked.


    But what if I don't want to think about 6 pixel to the left and bottom and like to have a solution which will make it right?

Anyway, IconFeedbackPanel works well with JScrollPane, what about the second requirement?

Okay, you can already guess that this solution doesn't work well for overlapped sibling components either,
to make it work you have to wrap every JTextField with a kind of an IconFeedbackPanel

Nevertheless, among all mentioned techniques, IconFeedbackPanel is probably the best one

JXLayer

In his comments Kirill mentioned an interesting term - "component-level techniques", comparing JXLayer with GlassPanes and LayeredPanes.
Actually if you need to decorate an existing component you have only three recommended choices:

  • Subclass a component
  • Make a custom UI delegate
  • Wrap it with a special decorator

All of them are "component-level techniques" and there is no other valid tecniques in Swing, because only in that cases your components will work in predictable way for all common cases

JXLayer is a component's wrapper, which meets all my requirements:

  1. It doesn't change any global resources
  2. Its decorations are painted quickly and correctly inside JScrollPane

  3. It respects Z-order of sibling components
    (move the decorated component and note that decorations correctly overlaps with the top circular component)

Decorated components behave exactly like any other Swing components and there are no unexpected "special cases" where something might go wrong.
That what I meant when I said that solutions like GlassPanes or LayeredPanes are not good enough for me.
I want to be sure that my components will work well with all existing layouts, like StackLayout

Try to make a component which is decorated with JLayeredPane, correctly overlap a non-opaque sibling component

In the previous blog I showed two examples of validation decorations: when it is completely within the bounds of wrapped component, and JXLayer has the same size as its inner component;
the second variant is when decorations are partially outside (the little red icon on the corner).

I personally like the first one, because it doesn't affect component's layout and doesn't require a bunch of listeners to be added to the TextField's document.
If I needed to support that fancy outer icon for some TextFields I would use Karsten's trick - he automatically wraps JScrollPane's views only when I'd wrap all TextFields to set them identical insets

Conclusion

Sometimes the "no-hacks rule" is not easy to follow and may require some extra work,
but use it or not is always up to you

Thanks
alexp

Why I don't use Swing hacks (in production code)

Posted by alexfromsun on September 27, 2007 at 07:03 AM | Permalink | Comments (15)

One of the big part of my work is fixing bugs in Swing but not all of them are to be fixed, some of them are closed because they describe the situations when someone uses Swing in undocumented way or trying to make Swing do something which it doesn't support.

My favourite example is so-called "Blocking GlassPane". If you file a bug with a description that you implemented a GlassPane to block your frame and it doesn't work, most likely it will immediately be closed as "not a bug". The reason is simple - glassPane is an internal feature of Swing and the library may use wherever it is necessary, for example currently we make it visible during resizing of internal frames to show a correct mouse cursor above any existing components.

Swing does it well if you just paint something on a GlassPane but when your GlassPane affects the processing of events it becomes what I call a "hack". There are many definitions what a "hack" is, either positive or negative, but I want to give a definition what I mean by this word


Hack is an approach when you use something in an unexpected way

Hacks don't have to be alway good or bad. Some of them are creative and effective, some of them are just creative, but they are always different from recommended or well-known solutions.

Going back to Swing I want to mention the main feature of a code which uses hacks:


If you use recommended techniques and something doesn't work as it should
Swing team is to be blame.

If you use a hack and something doesn't work - it is you to be blame.

Hacks usually work well for a presentation or a blog entry, but very few of them work as good and stable as the core library code. When you think it is good enough, someone else finds a problem whith your code and when you fix it, another issue appears so when you fix the "last" problem you know enough to write another valid test case which will be broken with your code.

Even if you are pretty sure that your hack works well on your computer, there usually no guarantee that it doesn't fail for another OS or with some custom components which use similar hack.

The "Blocking GlassPane" is an interesting example because it is a well-known hack which has never worked well. It is easy to put a fancy animation to a GlassPane and add an empty MouseListener to block the MouseEvents but people tend to forget about KeyBoard events and it is usually possible to type something in a TextField under a Blocking GlassPane.

I already blogged about this issue and offered some solutions to make this hack more robust however a month ago I got a message from one of my readers, he told me that my improved Blocking Glasspane fails to block the menu accelerators, so if you use Alt+MenuShortCut the menu will appear.

Problems like that is a usual thing when you use hacks. The most straightforward fix for this particular problem would be to disable frame's JMenuBar before showing GlassPane and enabling it again when the GlassPane is gone, but don't ask me how give user a chance to interrupt the time-consuming operation which blocks his frame or how to ignore keyboards actions for other components.


Modal dialog is the only recommended solution to block a top level frame

During my work on JXTransformer I used any kind of hacks to make transformed components work as well as usual ones. I managed to make it work for unsigned applet however I used some fishy techniques which I can hardly recommend. And again I realized that it might work for the most usual cases but there will always be some cases when transformed components behave incorrectly and the only right fix is to change the Swing itself to support AffineTransform for JComponents.

After that I added a special note that JXTransformer has several known limitations and decided that my next project will use only recommended techniques which always work well.

That's the reason why I don't use any hacks in JXLayer component. When I started this project it was tempting to add some creative hacks to implement some unique features but I keep following the "no hacks rule" because I don't want to be blame if JXLayer wouldn't work for your project.

When hacks are welcome

If you think that I don't use hacks at all, it's not true. There are some situations where hacks (even the dirty ones) are welcome, I am talking about testing and debugging. Let's say you need to find a cause of a bug, and in this case all means which may help are good (until they are not a part of production code).

On SwingHelper project you can find a CheckThreadViolationRepaintManager which helps finding a Event Dispatch Thread violation in Swing applications as well as EventDispatchThreadHangMonitor to catch time-consuming tasks on Event Dispatch Thread.
I would hardly use custom RepaintManager or EventQueue for my components, but they work very well when it comes to debugging.

Testing is a similar story, when I have a choice between a manual regression test with no hacks and automated one with hacks, I always choose the last one, just because manual tests don't have much sense

Conclusion

I am a realist and I know that unfortunately it is almost impossible to make a big project without any hacks. The only recommendation I can give for this matter is do not use hacks just because you know them, try to follow the documented patterns as often as possible and you code will be more reliable

To be continued...

KISS principle: JXButtonGroupPanel followup

Posted by alexfromsun on November 01, 2006 at 10:28 AM | Permalink | Comments (5)

In one of my previous blog I presented a component which enables arrow keys to transfer focus and selection for Swing buttons.
And it worked very well, but there were two things which worried me

  • JXButtonGroupPanel offered a different way to bind buttons to a ButtonGroup
    since it created ButtonGroup and filled it up behind the scene
  • It was just overcomplicated

That why I mentioned Keep It Simple principle
Actually I like to control what is going on in my applications and don't like when componets are trying to be too smart.
Indeed, when you need to add some JRadioButtons to a panel, you usually do something like this:

JPanel panel = new JPanel(); 
// Create a ButtonGroup         
ButtonGroup group = new ButtonGroup(); 
 
JRadioButton r1 = new JRadioButton("One"); 
// Add a button to the panel    
panel.add(r1); 
// Add a button to the ButtonGroup 
group.add(r1); 
 
JRadioButton r2 = new JRadioButton("Two"); 
panel.add(r2); 
group.add(r2); 
JRadioButton r3 = new JRadioButton("Three"); 
panel.add(r3); 
group.add(r3); 
 
frame.add(panel);

I would prefer to change the existing code as little as possible if I just want to enable arrow keys support.

With all mentioned reasons in mind I rewrote JXButtonGroupPanel to make it shorter and easier to use
I even cut down its name from JXButtonGroupPanel to JXButtonPanel :-)

On the SwingHelper project you can as usual find the buttongroup.jar and buttonpanel-src.zip

Direct link to JXButtonPanel sources

The demo is also rewritten to take advantage of the new implementation

Summary

  • Just change your JPanel to JXButtonPanel and arrow keys will work
  • It doesn't fill a ButtonGroup up behind the scene anymore
  • Based on a comment from paulo_matos I added a groupSelectionFollowFocus property
    which controls whether arrow keys should transfer button's selection as well as focus or not
  • It is fully tested and ready for production

Your comments are welcome !
Thanks
alexp

Crazy JButton painters

Posted by alexfromsun on October 05, 2006 at 10:51 AM | Permalink | Comments (6)

Inspired by some latest blogs I decided to find the most hacky and crazy method to paint on a component
I chose JButton as the most well-known component to play with

The goal is to implement some custom painting to a button without subclassing it and with no custom UI delegate

Custom component

As you probably know all Swing components are Containers, that means that you can add any children components to them. However not all Swing components support it, e.g. it usually doesn't make sense to add children to a components like JSlider or JProgressBar

JButton is not supposed to be used as a Container but we certainly can try:

JButton button = new JButton("I am a JButton");         
// JButton's layout is null by default 
button.setLayout(new FlowLayout()); 
         
button.add(new JButton("Surprise!"));

It is the clue to the first trick, you can add a translusent component to the button to make it looks different

button.setLayout(new BorderLayout()); 
button.add(new ComponentPainter()); 
                     
button.revalidate(); 
button.repaint(); 
.                     
.

class ComponentPainter extends JPanel { 
    protected void paintComponent(Graphics g) { 
        Graphics2D g2 = (Graphics2D) g.create(); 
        // It enables painting outside the component's border 
        g2.setClip(null); 
        // Make it translucent 
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 
        // Take button's insets into account 
        Insets insets = button.getInsets(); 
        // Custom painting 
        g2.setColor(Color.ORANGE); 
        g2.fillOval(-insets.left, -insets.top, button.getWidth(), button.getHeight()); 
        g2.setColor(Color.GRAY); 
        g2.drawOval(-insets.left, -insets.top, button.getWidth(), button.getHeight()); 
        g2.dispose(); 
    } 
}

Unusual border

Borders in Swing are used to paint decorations around a Component,
but we are going to break the rules and create a Border instance to paint to the whole component's area

class BorderPainter extends AbstractBorder { 
    
    public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { 
        Graphics2D g2 = (Graphics2D) g.create(); 
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 
        g2.setPaint(new GradientPaint(0, 10, Color.BLACK, 10, 10, Color.RED, true)); 
        // We are free to paint wherever we want :-) 
        g2.fillRect(0, 0, button.getWidth(), button.getHeight()); 
    } 
}

The only problem with this approach is that a component can have only one border, and new border will replace the old one.
javax.swing.border.CompoundBorder will help us to compose the existing border with the additional one:

Border oldBorder = button.getBorder(); 
button.setBorder(new CompoundBorder(oldBorder, new BorderPainter()));

Misuse of Icon

The last dirty trick involves javax.swing.Icon

it has a paintIcon() method with suggested coordinates as parameters, but as you can guess we are not going to use them.
There might be the same problem as we already mentioned for Borders: you can set only one icon per a button

But it is not a problem for a real hacker :-)

Icon oldIcon = button.getIcon(); 
button.setIcon(new IconPainter(oldIcon)); 
. 
.
class IconPainter implements Icon { 
    private final Icon delegateeIcon; 
 
    public IconPainter(Icon innerIcon) { 
        this.delegateeIcon = innerIcon; 
    } 
 
    public int getIconWidth() { 
        if (delegateeIcon != null) { 
            return delegateeIcon.getIconWidth(); 
        } 
        return 0; 
    } 
 
    public int getIconHeight() { 
        if (delegateeIcon != null) { 
            return delegateeIcon.getIconHeight(); 
        } 
        return 0; 
    } 
 
    public void paintIcon(Component c, Graphics g, int x, int y) { 
        if (delegateeIcon != null) { 
            delegateeIcon.paintIcon(c, g, x, y); 
        } 
        Graphics2D g2 = (Graphics2D) g.create(); 
        g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f)); 
        g2.setPaint(new GradientPaint(10, 0, Color.RED, 10, 10, Color.BLUE, true)); 
        // It is a special icon, it is not interesed in x, y and icon's size  
        g2.fillRect(0, 0, button.getWidth(), button.getHeight()); 
    } 
}

Demo

There is a demo with all this crazy stuff implemented

I have finally found the modern and fresh look for my buttons !

Source code

Conclusion

Despite the fact the provided tricks work
please consider this blog as a joke

I hope no one takes it seriously and starts using something like described "IconPainter"

Have a nice day
alexp
;-)

A well-behaved GlassPane

Posted by alexfromsun on September 20, 2006 at 12:02 PM | Permalink | Comments (22)

I have seen a lot of custom GlassPane tricks, which use GlassPane to "disable" the frame, to provide a visual effects during the lengthy tasks or to give more rich feedback during drag and drop etc...

The distinctive feature of that tricks is that GlassPane is shown only temporarily,
so when the lengthy tast or any other action is completed the custom GlassPane is made hidden and doesn't affect the frame anymore

I am going to examine special cases when you might want to show a GlassPane to provide a constant visual effects,
this way the frame is supposed to function as usual except the big translucent text e.g. "Hello Mom !" is shown on the top of the content pane :-)

So let's create a GlassPane which shows a colored rectangle at the top right corner of the frame and draws the colored circle to follow the mouse's cursor.
As a testing environment please launch this simple application

This application contains two JInternalFrames, components on the first frame don't have any special features, only JTextField has a popupMenu,
the second frame contains components which have a rollover effect - their background turns green when you move the mouse cursor to them, moreover each of them has a tooltip

To make it more interesting I implemented a kind of a "focus follow mouse" policy for both internal frames, so when mouse's cursor crosses frames border, the frame becomes selected

Don't close this application we will use it to test

The initial GlassPane version

Most of the similar GlassPane implementations which I tested, copied the code from the How to use Root Pane Swing tutorial.
The main idea is to add mouseListener and mouseMotionListener to the GlassPane, catch the mouseEvent and then redispatch it to the component found with help of SwingUtilities.getDeepestComponentAt()

Based on this solution I created the InitialGlassPane version.

Please return to the testing application and select the "GlassPane is visible" checkBox from the Options menu or simply press Alt+G to activate this menuItem.

Note: Diring the initial glassPane is visible, it is impossible to select any menuItems, use key accelerators instead

Notice the red rectangle appeared at the right top frame's corner and the red translucent circle started follow the mouse.
Let's check how it affects the frame's contents: Click to the every component from the first internalFrame, as you can see, they seem to work:
You still can select the CheckBox and edit the JTextField

Now play with the components from the second internal frame Here we can quickly notice some odd things like absence of rollover effects.

Here the list of problems you can see during initial GlassPane is visible:

  1. When Options menu is visible it doesn't react to the mouse, the textField's popup doesn't work as well
  2. Component's rollover effect is missed
  3. When you move the mouse cursor between the internal frames they don't get selected as they did before
  4. Tooltips become very unreliable, they might appear in unusual places or not appear at all

There is one more problem which is not so visible:

  • press the JButton
  • drag out of its border
  • release the mouse button
  • The button remains pressed !

Here is a screenshot with the mess you can get with the initial GlassPane

Now hide the glassPane by pressing Alt+G and see the difference, everything works much better, right ?

Notice one more little feature:

  • press the slider
  • drag out of the *frame* border
  • note that you can still move the slider's thumb

It doesn't work this way for the initial GlassPane, but after all mentioned problems this doesn't look very bad, does it ?

Why it doesn't work

GlassPane doesn't get all events you need, e.g. when you move the mouse inside it you redispatch the mouseMotion events only.
That's why rollover effect didn't work - just because components didn't receive mouseEntered/MouseExited events.

That glassPane also breaks the inner mouseEvents processing so it leads to broken drag and drop, menus and tooltips

Actually redispatching mouseEvents this way is just a bad idea

and I am going to ask to update the Swing tutorial
you can fix some problems for the current implementation but it will never work perfectly

To fix the problems our GlassPane should be absolutely transparent for mouseEvents, it means that we shouldn't add any Mouse/MouseMotion/MouseWheelListeners to it

For more information about mouseEvent transparency please refer to the my my BoF session from the latest JavaOne conference (see the MouseEvents topic)

But wait, how can I process the mouseMotion events if I didn't add any mouseListeners to the glassPane ?

To get the answer please try the

Better GlassPane

We can catch mouseEvents without mouseListeners with AWTEventListener, this way we will keep our GlassPane transparent for mouseEvents and it will not affect the mouseEvents processing

Check out the Better GlassPane implementation

Let's return to the testing application, switch the glassPane implementation to the Better GlassPane and make it visible.
After the yellow rectangle and circle appear you will not find any differences in mouse processing !

Menus, buttons, rollover everything work with no surprises

This is a really better GlassPane, but it also has a little problem (the first GlassPane has it as well by the way):

  • Hide the better GlassPane (press Alt+G)
  • Move the mouse cursor inside the JTextField
  • The default mouse cursor turns to "text editing" cursor
  • Show the better GlassPane (press Alt+G)
  • Move the mouse cursor inside the JTextField
  • Look that mouse cursor doesn't change

You can see the same problem if you move the cursor to the internal frame border -
the cursor is supposed to change to show that resizing is allowed

This GlassPane prevents mouse cursor to be changed, it is always the same no matter which component you work with

Why it doesn't work

The problem is that AWT system show the cursor of the topmost visible component
and since our topmost component is GlassPane and it has the default cursor it is always the same

The fix is simple and I am happy to present the

Final GlassPane

To fix the "cursor problem" we should make our GlassPane even more transparent,
there is a special method which is used by AWT to detect the target component for the mouseEvents

public boolean contains(int x, int y)

The hot fix is to override this method to alway return false for the GlassPane,
after that you'll start to see proper cursors and your GlassPane will be finally and absolutely transparent,

because even if you add a MouseListener to it, it will never be notified
since such a GlassPane doesn't "contain" any geometrical points

And here it is time to reveal a big secret:

GlassPane might be used by someone else as well

Here is a scenario for our testing application:

  • Hide the current glassPane
  • Resize an internalFrame with help of the mouse
  • Notice that during resizing GlassPane becomes visible
  • When resizing is over GlassPane is made hidden

In this case GlassPane is made visible to provide a proper mouse cursor during internalFrame's resizing

Moreover someone else might implement special logic with help of the GlassPane
e.g. add some MouseListeners to it during drag and drop and than remove them to restore mouseEvents transparency

So, if we prepare our GlassPane for our application only and pretty sure that no one else will use it, it is ok to override contains method to always return false

But this blog is about a well-behaved GlassPane which is supposed to behave well for any situation

So if someone added a mouseListener to the GlassPane or set a new mouse cursor I don't change contains method behaviour
but return false otherwise

We have finally solved all problems and our final GlassPane does well

Conclusion

Here you can find the runnable jar and archive with all source files

I hope this tutorial will help you to better understand Swing and AWT mouseEvents processing and
let your GlassPanes be really transparent !

See also the follow-up blog Enabling/Disabling Swing Containers

The ButtonGroup of my dreams

Posted by alexfromsun on August 23, 2006 at 10:34 AM | Permalink | Comments (15)

I have fixed quite a lot of bugs and RFE's in Swing for Java 1.6 and don't really remember all of them,
but I do remember one remarkable bug because it took me unusually long time to find a good solution
and after I had fixed it I had to fix several regressions and finally I completely rolled the fix back

Let me introduce you the bug number 4226243 (and 10 related bugs)

So why fixing it was so difficult ?

We just should be able to move the focus and selection inside the RadioButtonGroup with help of arrow keys...

That doesn't look like a hard problem

But we also need to skip unselected radioButtons if we move focus from component to component pressing "Tab" button,
just like Windows or GTK do it.
Test any native application with RadioButtonGroup and you will see that
if a RadioButtonGroup has a selected button it is impossible to focus unselected one with help of "Tab" of "Shift+Tab" buttons.

Native ButtonGroup behaviours like a one component:

you press "Tab" -> focus gets the selected button
press "Tab" again -> focus goes out
selection can be changed with help of arrow keys (or by clicking mouse)

This part turned out to be really problematic...

The real killer was the fact that javax.swing.ButtonGroup is not a Swing component,
it implements a set of buttons which can be placed on differect panels and
theoretically speaking even on different frames !

it might explain why our focus management wasn't ready to such an unnatural request

Finally with great help of AWT team we found a workable fix
but since it is supposed to use some reflection and other hacks
I doubt we will accept this version

It probably was a sad story, but the time wasn't spent in vain
as a result of lengthy work on that bug I got a clear vision of

Better ButtonGroup implementation

Please welcome JXButtonGroupPanel from SwingHelper project

  1. It transparently adds all its children buttons to the group
  2. Can be used as for exclusive JRadioButtons group as well as for group of JButtons
  3. Supports arrow keys and correctly works with focus for exclusive groups
  4. Just makes it easier to implement typical button groups

You just add your buttons to JXButtonGroupPanel and it will do the rest of the work

Please try it and share you opinion !

Note: Please read the Follow up blog



JXTransformer: The power of a real Swing !

Posted by alexfromsun on July 17, 2006 at 08:40 AM | Permalink | Comments (18)

A long time ago I wanted to have a component which could paint rotated text for my application

I definitely could create my own component, override paintComponent(), implement correct resizing etc...

Yes, it was clear how to implement your own rotatable JLabel, but what if I want to have a rotatable JButton and I want to have it for each Look and Feels, how many classes I would have to sublclass in this case?

At the same time Swing controls the entire painting routine using the power of Java2D library and there is
Graphics2D.setTransform(AffineTransform Tx) method
which can scale, rotate and shear Graphics2D instance.

It sounded like a good starting point and I decided to quicky implement a Swing container which can transform its children component without affecting its functionality.

Nevertheless, this task turned out to be so challenging so I spent about 3 months looking for the best solution from time to time.
(I feel I am ready to give a lecture about the long way to the current implementation :-))

My goal was to implement it using public interfaces only with no reflection hacks and with no affecting the whole system (no custom EventQueue, custom RepaintManager or AWTEventListener) because it would allow using the component in restricted evironments like WebStart applicaitons

Finally I am happy to present the result: to rotate your JButton just do

JButton button = new JButton("Hello");
JXTransformer t = new JXTransformer(button);
t.rotate(Math.PI/2);

And you'll have a rotated and live button, which works like a usual JButton

Check the WebStart demo and play with three bottom sliders

Source and demo files are published on SwingHelper.dev.java.net and don't forget to check
CheckThreadViolationRepaintManager and EventDispatchThreadHangMonitor as well

Check it, Test it, Use it and send your valuble feedback !

The JXTransformer is pretty hacky sometimes, so if you believe we should better support things like that for the next JDK
leave a comment here

Note: JXTransformer is an experimental project and has several known limitations

Thanks

Swing testing and debugging on JavaOne

Posted by alexfromsun on June 28, 2006 at 09:40 AM | Permalink | Comments (2)

Hello everybody !

After spending a fantastic after JavaOne vacation, travalling Arizona and Florida I am happy to say hello to the comminity again.
Thanks to everyone who visited JavaOne and especially who visited my BOF session about Swing testing and debugging.

I'd like to underline that it were you guys who gave me much of the ideas to complete the presentation.
Thank you once again and let me add two little debugging-related addons which I didn't mention earlier:

CTRL+SHIFT+F1

I was really surprised when AWT guys told me about this funny feature, because I hadn't ever used it before.
So, this combo is hardcoded in java.awt.Window.preProcessKeyEvent() method and is used to dump the list of children of this window to System.out.

For example if you press CTRL+SHIFT+F1 when an empty JFrame is active and check the console
you'll discover the compound nature of JFrame:

javax.swing.JFrame[frame0,0,0,200x200,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=EXIT_ON_CLOSE,rootPane=javax.swing.JRootPane[,4,28,192x168,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
 javax.swing.JRootPane[,4,28,192x168,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=]
  javax.swing.JPanel[null.glassPane,0,0,192x168,hidden,layout=java.awt.FlowLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777217,maximumSize=,minimumSize=,preferredSize=]
  javax.swing.JLayeredPane[null.layeredPane,0,0,192x168,alignmentX=0.0,alignmentY=0.0,border=,flags=0,maximumSize=,minimumSize=,preferredSize=,optimizedDrawingPossible=true]
   javax.swing.JPanel[null.contentPane,0,0,192x168,layout=javax.swing.JRootPane$1,alignmentX=0.0,alignmentY=0.0,border=,flags=9,maximumSize=,minimumSize=,preferredSize=]

-Xbootclasspath

This might be a well known feature, but I'd like to mention it here despite the fact it's not related to Swing directly.
In the presentation I collected some useful debugging techniques, but what if you tried everyting and still don't find a bug?

With help of -Xbootclasspath you can substitute any core classes from JDK and add as many System.out.println as you want.

For instance:

  1. Create java/awt folders and put there the Component.java from JDK
  2. Add System.out.println(this) to the very beginning of requestFocusHelper() method
  3. Compile this class
  4. Run your application as java -Xbootclasspath/p:<path to your java/awt folder> YourApplication

After that you'll be notified when any Component.requestFocusXXX() is invoked

This technique is very helpful for our dear JDK contributors, because it makes it so easy to test updated JDK on any OS with the installed JDK remains intact

Afterword

Don't forget to check out SwingHelper project
and send your valuable comments either to the project's mailing lists or leave it here.

Please share your ideas how to make Swing testing and debugging easier
and what we should add to the next version of JDK to prevent Swing programmers from all that tricky bugs !

Swing team reveals their secrets !

Posted by alexfromsun on April 01, 2006 at 12:00 AM | Permalink | Comments (14)

Everybody knows that Swing and AWT behaviour can be changed with command line properties,
for example you may set the desirable Look and Feel with help of the following command line property

swing.defaultlaf

see here for more info, e.g. the following command:

java -Dswing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel <YourApp>

allows you to have the Windows LaF to be set for your application by default;
or let's take the

java.awt.headless

which switches your application to a mysterious headless mode (don't try it at home, by the way)

But the same time it is not widely known that Swing has much more exciting undocumented properties, and even Swing team members are not aware of the complete list of them.

You might spend a lot of time implementing a feature, and after that bump into the property wich can solve the problem straight away

Remember the famous grayrect improvement which is implemented for Mustang?

It turned out that the the only thing you should do to solve the problem is set the

swing.grayrect.color

from Gray (it was the default value) to Transparent, with:

java -Dswing.grayrect.color=Transparent

your will never see the gray rect and what is the most interesting that it works even for java 1.1 !

if you don't like the grayrect to be transparent, you can fill it with you favourite color,
for instance

swing.grayrect.color=Violet turns the boring grayrect to a beautiful Violet !

Here the list of additional properties, with valid values which are devided with | symbol, the default value marked with asterisk:

swing.runtwiceasfast=true*|false

Make the Swing two times faster

swing.completeleremoveawt=true|false*

This one is in progress, AWT team doubts that we really need it

swing.bugsnumber=ALOT | MEDIUM | AFEW* | NONE

NONE value is not completely tested yet, so I don't recommend trying it out

java2d.speedofpainting=LOW | MEDIUM | FAST* | UNIMAGINABLE

If you set UNIMAGINABLE value for this property the Swing components will be painted so fast that it will hardly be possible to notice them

I am absolutely sure that there are more properties to be added in this list,
so if you know any ones which are not mentioned here, please let me know !

Debugging Swing, the final summary

Posted by alexfromsun on February 16, 2006 at 11:49 AM | Permalink | Comments (39)

It's taken some time to study all possible ways of detecting Event Dispatch Thread rule violations,
and now I feel I this topic is about to be closed.

But let me tell from the beginning:

ThreadCheckingRepaintManager

I was really surprised when I got to know about the smart solution invented by Scott Delap
who created a RepaintManager which finds EDT mistakes, I started to play with it and noticed that


Note: Please take the latest version of the ThreadCheckingRepaintManager from the SwingHelper project

This solution has the following advantages:

  1. It definitely helps in finding EDT problems
  2. You can use it right now, no third-party libs are required
  3. It is simple and elegant

But from the other side I thought about the following problems

  1. repaint() method is thread-safe and it is not a mistake to invoke it out of EDT
  2. There are a lot of methods in Swing which don't produce repaint events, the most obvious example is getters.
    If you call e.g. JTextField.setText() from EDT and the same time JTextField.getText() from another thread the result is unpredictable

The first problem is easy to solve:
to skip repaint() invocations the stack trace can be checked, I ended up with the following code:

public class CheckThreadViolationRepaintManager extends RepaintManager {
    // it is recommended to pass the complete check  
    private boolean completeCheck = true;

    public boolean isCompleteCheck() {
        return completeCheck;
    }

    public void setCompleteCheck(boolean completeCheck) {
        this.completeCheck = completeCheck;
    }

    public synchronized void addInvalidComponent(JComponent component) {
        checkThreadViolations(component);
        super.addInvalidComponent(component);
    }

    public void addDirtyRegion(JComponent component, int x, int y, int w, int h) {
        checkThreadViolations(component);
        super.addDirtyRegion(component, x, y, w, h);
    }

    private void checkThreadViolations(JComponent c) {
        if (!SwingUtilities.isEventDispatchThread() && (completeCheck || c.isShowing())) {
            Exception exception = new Exception();
            boolean repaint = false;
            boolean fromSwing = false;
            StackTraceElement[] stackTrace = exception.getStackTrace();
            for (StackTraceElement st : stackTrace) {
                if (repaint && st.getClassName().startsWith("javax.swing.")) {
                    fromSwing = true;
                }
                if ("repaint".equals(st.getMethodName())) {
                    repaint = true;
                }
            }
            if (repaint && !fromSwing) {
                //no problems here, since repaint() is thread safe
                return;
            }
            exception.printStackTrace();
        }
    }
}

Some comments about the code:

Initially there was a rule that it is safe to create and use Swing components until they are realized
but this rule is not valid any more, and now it is recommended to interact with Swing from EDT only

That's why completeCheck flag is used - if you test the old program switch it to false,
but new applications should be tested with completeCheck set to true

By the way, there is a chance that an application already uses a custom RepaintManager,
so it would be better to be able to "wrap" a custom RepaintManager to support existing functionality

public class CheckThreadViolationRepaintManager extends RepaintManager {
    private final RepaintManager delegatee;

    public CheckThreadViolationRepaintManager() {
        this(new RepaintManager());
    }

    public CheckThreadViolationRepaintManager(RepaintManager delegatee) {
        if (delegatee == null || delegatee instanceof CheckThreadViolationRepaintManager) {
            throw new IllegalArgumentException();
        }
        this.delegatee = delegatee;
    }

    public synchronized void addInvalidComponent(JComponent component) {
        checkThreadViolations(component);
        // use delegatee instead of super for *all* methods
        delegatee.addInvalidComponent(component);
    }
    
    //other methods skipped

}

Visit the Scott Delap's blog where you can find all information about this technique

CheckThreadViolationRepaintManager is really useful and can detect a lot of EDT mistakes, but... not all of them
As I said above if methods doesn't send repaint events, RepaintManager doesn't work for it

That's why I continued finding the complete solution

My first intention was to find a solution which can easily detect EDT violations and which doesn't need any third party tools or libraries (like CheckThreadViolationRepaintManager works) and that explains my next try:

JVMTI agent

Thanks to Stepan Rutz I got an example of JVMTI which can detect EDT problems via bytecode instrumentation,
the agent can instrument classes from javax.swing package and insert required checking to the beginning of any methods in runtime.

Stepan, I didn't post your code here since I don't have your permission, could you publish a link to the code you sent me ?

I started to work with it and realized this solution has some drawbacks

  1. It is not a pure java solution, using JNI to invoke java methods from C code is not as clear as that
  2. Since JVMTI agent uses native code it requires to be compiled for each platform differently

Hence I switched to java.lang.instrument package which allows to create agents without C coding

Anyway it was a interesting experience, and I realized that JVMTI is very powerful and you can get a lot of specific information from JVM e.g. garbage collection or object allocations events tracking

java.lang.instrument.ClassFileTransformer

ClassFileTransformer has the only method, it takes the class and returns modified bytecode,
sounds pretty simple but to use it you need to know the format of java bytecode very well

Fortunately I found the BCEL library which simplifies bytecode transformations and when I started reading manuals
the AWT lead Oleg Sukhodolsky came to see me and I told him about my current task.

He just smiled and the next day I got email from him with the java agent which inserts

if (!java.awt.EventQueue.isDispatchThread()) {
    Thread.dumpStack();
} 

to the beginning of each method which name start with "set" or "get" for selected classes in javax.swing package

Binary and source code of the agent is here

To compile and run it you need another bytecode manipulation tool the ASM framework

But wait, if I tried ASM framework, why shouldn't I tried AOP,
which is known as a suitable tool for advanced logging of existing applications?

AspectJ

I downloaded AspectJ, spent one evening and voila:

import javax.swing.*;

aspect EdtRuleChecker {
    private boolean isStressChecking = true;
    
    public pointcut anySwingMethods(JComponent c):
         target(c) && call(* *(..));

    public pointcut threadSafeMethods():         
         call(* repaint(..)) || 
         call(* revalidate()) ||
         call(* invalidate()) ||
         call(* getListeners(..)) ||
         call(* add*Listener(..)) ||
         call(* remove*Listener(..));

    //calls of any JComponent method, including subclasses
    before(JComponent c): anySwingMethods(c) && 
                          !threadSafeMethods() &&
                          !within(EdtRuleChecker) {
     if(!SwingUtilities.isEventDispatchThread() &&
         (isStressChecking || c.isShowing())) 
     {
             System.err.println(thisJoinPoint.getSourceLocation());
             System.err.println(thisJoinPoint.getSignature());
             System.err.println();
      }
    }

    //calls of any JComponent constructor, including subclasses
    before(): call(JComponent+.new(..)) {
      if (isStressChecking && !SwingUtilities.isEventDispatchThread()) {
          System.err.println(thisJoinPoint.getSourceLocation());
          System.err.println(thisJoinPoint.getSignature() +
                                " *constructor*");
          System.err.println();
      }
    }
}

This aspect detects all EDT rule violations in your Swing code and has the only drawback -
to run it you need to download an additional jar file

Conclusion

We are going to make Swing debugging easier in Java 1.7
but good news that it is possible to detect Event Dispatch Thread rule violations in your Swing code right now.

ThreadCheckingRepaintManager can find a lot of problematic methods invocations,
but if you want to be absolutely sure - make your own java agent or simply use aspects.



Debugging Swing, the summary #1

Posted by alexfromsun on January 12, 2006 at 09:38 AM | Permalink | Comments (12)

Well, I've got more than 30 comments for the previous blog.

It's high time to summarize them and make up a conclusion.

There are two separate enhancements were suggested in comments:

  1. Make compiler move Swing methods to EDT automatically
  2. Invent something to make finding out-of-EDT methods invocations easier

Making Swing methods threadsafe, simple case

Really, if you call Swing method out from EDT why compiler can't move it to EDT automatically ?

For example we can mark necessary methods with @OnEdt annotaions and...
what's next ?

The compiler could probably insert SwingUtilities.invokeLater automatically:

From

        @OnEdt
        public void removeAll() {
               // implementation skipped...
        }

To

        public void removeAll() {
            if (!SwingUtilities.isEventDispatchThread()) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {                    
                        removeAll();
                    }
                });
                // implementation skipped...
              }              
        }

No need to write InvokeLater(..) yourself anymore, compiler is doing it for us.
Sounds good, but let's think about implementation of this feature.

One of the serious drawback of such solution is a performance:

You'll need an additional inner class for each @OnEdt method,
it will make Swing bigger and slower

The second problem is not so visible,
because this approach works relatively good for simple methods without parameters and return type only

Making Swing methods threadsafe, real case

consider the more usual case:

        @OnEdt
        public Component getComponent(int number) {
             // implementation skipped... 
            return aComponent;
        }

This method takes a parameter and returns a value,
so to make this method thread safe we need:

  1. turn parameter into final int constant to be accessed in the inner class
  2. block the thread and wait for the result,
    so invokeLater() is not good here
  3. it becomes more tricky if methods throws checked exceptions,
    in this case we need to catch them in the inner class and rethrow them properly

The guru of Swing threading and one of the creators of SwingWorker,
Igor Kushnirskiy offers the following scheme to convert the body of EventDispatchThread only method to a thread safe one

        if (!SwingUtilities.isEventDispatchThread()) {
            store all the method arguments in final variables
            Callable callable =
                new Callable() {
                    public T call() throws Exception {
                        return invoke this method;
                    }
                };
            RunnableFuture future = new FutureTask(callable);
            SwingUtilities.invokeLater(future);
            try {
                return future.get();
            } catch (InterruptedException e) {
                //unlikely to happen
              throw new RuntimeException(e);
            } catch (ExecutionException e) {
                //rethrow all exceptions properly
                Throwable cause = e.getCause();
                if (cause instanceof RuntimeException) {
                    throw (RuntimeException) cause;
                } else if (cause instanceof Error) {
                    throw (Error) cause;
                } else if(cause instaceof