|
|
||
Alexander Potochkin's BlogA well-behaved GlassPanePosted by alexfromsun on September 20, 2006 at 12:02 PM | 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
Bookmark blog post:
del.icio.us
Digg
DZone
Furl
Reddit
Thanks for the contains() trick! I always wondered why the mouse cursor were screwed up when showing a "transparent" glass pane.
Posted by: gfx on September 20, 2006 at 12:42 PM
great to here someone took the time to figure out a better glasspane solution. I tested it with a JComboBox (which has been a big problem in the past), and it worked like a charm.
Posted by: gmoniey on September 20, 2006 at 01:58 PM
Why not simply put stuff higher in layers using JLayeredPane?
See this example which doesn't use the glass pane at all.
Posted by: twalljava on September 20, 2006 at 10:58 PM
To Romain
You are welcome, I know you like GlassPane tricks
;-)
alexp
Posted by: alexfromsun on September 21, 2006 at 04:43 AM
Hello twalljava
Thanks for the interesting link !
Is it you the author of the DecoratorDemo ?
JLayeredPane definitely can be used as well,
but a lot of people love to use GlassPane for custom paintings
Thanks
alexp
Posted by: alexfromsun on September 21, 2006 at 05:25 AM
I have had to deal with these issues, and ended up redispatching mouse events. Ultimately what I wanted to do was determine where in my application frame the user's mouse was, but there was no easy way to just inspect all mouse events, so I used the GlassPane. I've come to the conclusion that the GlassPane is not the greatest design decision. I think what is needed is a better ability to capture events before they are dispatched to the components. I think it would also be useful to have the ability to show an object without it being receptive to events; why is the visibility of the GlassPane coupled with it's reception of mouse events?
Posted by: davetron5000 on September 21, 2006 at 06:57 AM
Hello davetron5000
Toolkit.getDefaultToolkit().addAWTEventListener(al,
AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK);
is quite easy way to inspect all mouseEvents
and you don't need any visible GlassPanes for it
alexp
Posted by: alexfromsun on September 21, 2006 at 07:05 AM
Alexander,
I don't know why, but I've clicked on the final glass pane solution and I think it is not selecting the internal frame all the time. To recreate:
1.) move mouse to edge of title bar (upper right of JIF - first frame)
2.) move mouse to edge of title bar (upper left of JIF - second frame)
3.) Without touching any other components repeat one and two a few times. Just move back and forth on the title bar area.
4.) Observe that the mouse is over a title bar and it is not selected where it should be selected.
-Carl
Posted by: carldea on September 23, 2006 at 07:31 PM
Hello Carl
Thanks for the comment, It's an interesting fact
This effect is reproducible with no any visible glassPane
It looks like JIF sometimes doesn't recognize mouseEntered event
when you move mouse to its the title bar quick enough
Note that it is not related to the final glassPane implementation
I'll test it a bit more and most likely file a bug for JInternalFrame
Thanks
alexp
Posted by: alexfromsun on September 24, 2006 at 10:47 AM
Alex,
I hope this is a simple fix and possibly able to be back ported. If not I hope there is a workaround. Very nice demo!
-Carl
Posted by: carldea on September 24, 2006 at 05:26 PM
Great! I just found this glasspane solution today and it works just fine. Thanks a lot for your blog.
erik
Posted by: erikriedel on October 24, 2006 at 02:31 PM
Hi Alex!
This is really good, but I'm having a different problem which I think it
might not be possible to solve with a glassPane, I want to do some effects on
objects when they get Focus.
I already thought about changing UI delegates, but I get into a problem as described at:
http://blogs.oracle.com/duffblog/2006/08/21
I already tried the approach described there but it won't work.
Essencially I have objects with images that contains transparent areas to
define its shape. This forces me to subclass every single standard object I
use just to redefine contains(int x,int y) in a check for invisible pixels, which
is the same code! I wanted to do this in another way...
Any hints on how will I solve these situations? I'm out of ideas...
Cheers,
Paulo Matos
Posted by: paulo_matos on November 06, 2006 at 08:57 AM
Hello Paulo
I have no ideas how to make it without overriding contains() so far
Will let you know if I find something
Thanks
alexp
Posted by: alexfromsun on November 15, 2006 at 04:08 AM
Thanks everybody who left a comment here !
I really appreciate it
feel free to add more ones
alexp
Posted by: alexfromsun on November 15, 2006 at 04:10 AM
Greetings alex
I am trying to do something with applet where it will be really important for me to use something like the glass pane.I think. What i have is a propriatary applet that display some images. What i would like to do is in some way create maybe a second applet that would be placed transparently on and off over the first one. Pretty much the concept i like to achive is like having the real life transparencies. You can put them over a paper and draw on them without afecting the source and then just erase them.
just a more detailed explanation for the previous entry. when the glass pane is visible probable will have to create some kind of controlls that allow painitng of lines,writing text and so forth. Or is there any other way instead of glass pane? thank you
I dont have the source code for the proprietary applet that displays the image in the web page. That is why i have the problems.
Any advice
Cheers
Dan
Posted by: dan_dan on November 21, 2006 at 01:45 PM
Hello Dan
I though a bit about your case and didn't come up with any clear solutions,I would try to subclass the applet (hope it's not final)
and in your sublcass change the glasspane, make it visible and play with that
Wrap the applet with another applet doesn't sound feasible to me
Good luck !
alexp
Posted by: alexfromsun on November 23, 2006 at 11:31 AM
Alex's approach to dealing with glass panes has solved many glass pane issues that I had using far more elegant and succinct code than the mess I had! I thought I might add some additional thoughts here to take it up on notch on the elegance scale.
It rubs me wrong when classes implement *Listeners. Rarely are the added public listener methods intended to actually be called by a client of the API.
Also, the developer using the glass pane has to do a dance to maintain the addition and removal of the AWTEventListener to and from the default toolkit.
To attend to these two concerns, this update listens into hierarchal changes (with the way the glass pane is set into the root pane, can't use ComponentListener!) to add and remove a private field AWTEventListener on behalf of the developer. With these additions, the developer only need set the glass pane into the JRootPane and set it visible when needed; steps he is already familiar.
The class in its entirety can be found here, but the salient addition is this:
private final AWTEventListener awtEventListener = new AWTEventListener() {
public final void eventDispatched(AWTEvent e) {
if (e instanceof MouseEvent)
PotochkinGlassPane.this.processGlassPaneMouseEvent((MouseEvent)e);
}
};
private final long forTheseEvents = AWTEvent.MOUSE_EVENT_MASK
| AWTEvent.MOUSE_MOTION_EVENT_MASK;
public PotochkinGlassPane(RootPaneContainer rootPaneContainer) {
assert rootPaneContainer != null;
this.rootPaneContainer = rootPaneContainer;
setName("Potochkin Glass Pane");
setVisible(false);
setOpaque(false);
addHierarchyListener(new GlassPaneHierarchyListener());
}
private void processGlassPaneMouseEvent(MouseEvent me) {
final Component mousedComp = me.getComponent();
if (!SwingUtilities.isDescendingFrom(mousedComp, (Component)rootPaneContainer))
return;
point = me.getID() == MouseEvent.MOUSE_EXITED && mousedComp == rootPaneContainer ?
null :
SwingUtilities.convertPoint(mousedComp, me.getPoint(),
rootPaneContainer.getGlassPane());
repaint();
}
...
private final class GlassPaneHierarchyListener implements HierarchyListener {
public final void hierarchyChanged(HierarchyEvent e) {
final Component changedComp = e.getChanged();
if (PotochkinGlassPane.class.isInstance(changedComp)) {
// If the event is happening to the glass pane, let's find out
// what's going' on with it...
final long flags = e.getChangeFlags();
final boolean displayabilityChanged =
(flags & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0;
if (displayabilityChanged && !changedComp.isDisplayable())
// Glass pane is no longer displayable...
Toolkit.getDefaultToolkit().removeAWTEventListener(awtEventListener);
else {
final boolean showingChanged =
(flags & HierarchyEvent.SHOWING_CHANGED) != 0;
if (showingChanged) {
// Remove listener if isShowing or not. It should be
// redundant to remove for isShowing, but I prefer to
// be redundant to prevent multiple listeners from being
// registered due to some unforeseen code path that
// doesn't remove were needed...
Toolkit.getDefaultToolkit().removeAWTEventListener(awtEventListener);
if (changedComp.isShowing())
Toolkit.getDefaultToolkit().addAWTEventListener(awtEventListener,
forTheseEvents);
}
}
}
}
}
Apologies for being verbose here; hope this helps!
Posted by: gkedge on August 29, 2007 at 08:15 PM
Hello gkedge
It's nice to hear that my blog was useful for you
for some reason the link you gave doen't open in my browser
(not sure where is the problem)
but PotochkinGlassPane sounds tremendous anyway :-)
Please check out the follow up blog it might also be interesting for you
Thanks
alexp
Posted by: alexfromsun on August 30, 2007 at 05:17 AM
Alex: Very sorry for my link not working right now. Something going on with my server access that allows for local access but not so from outside the confines of my office. :-P
If you are still curious as to what I came up with, I believe you have my email address (can't find yours). Email me and I will reply with the complete PotochkingGlassPane class. You can judge whether to post it somewhere accessible and change my links to point to there.
Glasspaning is way fun. I am collecting and creating a stable of debugging panes, all of which I can run simultaneously (for the most part) without interfering with the debugged application's regular GlassPane. Not sure if that has been done before. If it has, I would be interested in seeing how that prior art was done for comparison. If not, and you are interesting that sort of thing, let me know...
Posted by: gkedge on September 15, 2007 at 09:01 AM
Hello gkedge
My email address is login@dev.java.net where login is alexfromsun
I am personally not keen on using glassPanes and that's was the reson to start the JXLayer project
but for debugging GlassPanes might be really useful, it would be interesting to hear about your solutions
Thanks
alexp
Posted by: alexfromsun on September 17, 2007 at 08:44 AM
|
|