|
|
||
Alexander Potochkin's BlogSwing ArchivesJXLayer 3.0 - MouseScrollableUIPosted 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:
To enable auto-scrolling you just need to wrap a JScrollPane with JXLayer and set the MouseScrollableUI to it:
After that you can you can activate auto-scrolling by clicking the mouse wheel:JScrollPane sp = JScrollPane(createMyTable()); JXLayer<JScrollPane> l = new JXLayer<JScrollPane>(sp, new MouseScrollableUI()); add(l);
ImplementationThings like MouseScrollableUI are very easily implemented with JXLayer, because it provides a way to catch all mouseEvents for all components inside a JXLayer.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.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()); } } (the runnable link is above the screenshot) See you on the JXLayer forum Thanks alexp JXLayer 3.0 - Getting startedPosted 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:
// 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. See you one the JXLayer forum With best wishes alexp RepaintManager's side effectPosted 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 updatePosted 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,
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,
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,
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.
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 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 the same code is run under JDK 6 update 10 or JDK 7 The red quarter circle icon is owned by the JXTrayIcon's test. I encourage you to download the JDK 6u10 early bits and try out the JXTrayIcon demo
I particularly interested how it works in different environments,
Thank you and sorry this took so long.I hope it was worth the wait. Making Rainbow: Spotlight effect and soft clippingPosted 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)
ForegroundPainterSpotlightPainter 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:
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:
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 clippingWhen 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: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! :-) 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. 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:
MouseEvents filteringComponents which are outside the spotlights shouldn't react on the mouse events and this was the simplest task for the SpotLightPainter
ConclusionTo add this functionality to your application you just need to wrap you component with JXLayer:
All source and binary files you can find on the SwingHelper project site
Debug Swing repaintingPosted 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.
A flash presentation is available, press the green button below to start: 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 overlaysFirst of all I want to mention my requirements to the validation overlays implementation:
RepaintManagerWhen 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 GlassPaneFor 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:
JLayeredPaneIt 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...
- 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
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 JXLayerIn 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:
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 ConclusionSometimes 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 welcomeIf 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 ConclusionI 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 followupPosted 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.
That why I mentioned Keep It Simple principle
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 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
Your comments are welcome ! Crazy JButton paintersPosted 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
Custom componentAs 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:
It is the clue to the first trick, you can add a translusent component to the button to make it looks different
Unusual border
Borders in Swing are used to paint decorations around a Component,
The only problem with this approach is that a component can have only one border, and new border will replace the old one.
Misuse of IconThe 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.
DemoThere is a demo with all this crazy stuff implemented I have finally found the modern and fresh look for my buttons !
ConclusionDespite the fact the provided tricks work A well-behaved GlassPanePosted 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,
I am going to examine special cases when you might want to show a GlassPane to provide a constant visual effects,
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.
This application contains two JInternalFrames,
components on the first frame don't have any special features,
only JTextField has a popupMenu, 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. 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. 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:
There is one more problem which is not so visible:
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:
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 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 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 GlassPaneWe 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. 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):
You can see the same problem if you move the cursor to the internal frame border - 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 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, 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:
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
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
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)
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
Please welcome JXButtonGroupPanel from SwingHelper project
You just add your buttons to JXButtonGroupPanel and it will do the rest of the work
Please try it and share you opinion !
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
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:
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=]
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:
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
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 !
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
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
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
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:
Make the Swing two times faster
This one is in progress, AWT team doubts that we really need it
NONE value is not completely tested yet,
so I don't recommend trying it out
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 !
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:
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
Please take the latest version of the ThreadCheckingRepaintManager from the SwingHelper project
This solution has the following advantages:
But from the other side I thought about the following problems
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:
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
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
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?
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
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.
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:
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
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:
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