If you are not redirected to JXLayer 4.0 demonstration by Piet Blok please press the link.
Note: per 2008-08-26 the sources are slightly modified to comply with the latest version of JXLayer (2008-08-25).
A second blog on JXLayer, describing a generic ZoomUI, can be found here: JXLayer and a generic ZoomUI
Some time ago I noticed the new JXLayer forum on java.net that draw my attention. Mostly because it is being maintained by Alexander Potochkin , also known as alexp or alexfromsun , of whom I've read many very good articles. The forum is dedicated to his new java.net project JXLayer.
What is this project all about? Well, you should read the project page to get the correct answer to this question. But in general it is about a lot of things that you can apply to ordinary JComponents.
For a private project I was playing with images. I implemented a pseudo full screen mode that can be toggled to and from. I decided that I wanted a means to hide the cursor when viewing the image. Well, there was my opportunity to try JXLayer. And indeed, it proved to be very simple to create a LayerUI that, when enabled, let the cursor disappear and reappear for a short while on mouse activity.
So far, so good. However, there was one problem: I had implemented the Scrollable interface in my component to get some choosable behavior like resize to fit in a smaller area, and or resize to fit in a larger area or to have a JScrollPane do its work. This didn't work any more. When I asked the forum for a solution, within hours Alex updated the JXLayer software to also implement Scrollable. My problem solved.
Because of this first succes, I got really curious. But first, to understand the following, a reader must have a basic idea how JXLayer works, or rather, how you work with it. In general there are two mayor classes that you have to deal with:
JXLayer
class. It extends from JComponent, so you can use it
as a JComponent. You can wrap a component hierarchy
in one JXLayer. JXLayer is a final class, so you
have no worries how to extend it.
LayerUI
class. You can set a LayerUI to a JXLayer to get the
desired effect. Also, a LayerUI can be enabled or
disabled. It is the LayerUI that needs to be
extended to implement the desired effects.
One of the first things that came to my mind was: what if I write two LayerUIs, each one with its own specific behavior, and I want to apply both effects on one component hierarchy? Must I write a new LayerUI that combines the functionality of the two? Or, is there a way to apply two different LayerUIs to one JXLayer?
Since a LayerUI can only be set to a JXLayer, not added, I shortly contemplated writing a compound LayerUI. A sort of MultiLayerUI to which other LayerUIs can be added and that delegates its work to these added LayerUIs.
I thought about it, and even started to work on it, but soon discovered that this was way to difficult for me. What methods should delegate and what methods should act on their own? Before I could ever accomplish this task, I needed a much deeper insight in the internals of JXLayer.
The solution proved to be much easier. What about wrapping one JXLayer into another JXLayer, each of them with its own LayerUI? After all, JXLayer is an ordinary JComponent.
Meanwhile I had implemented three different LayerUIs:
Now I created a component that should have all three layerUIs applied and wrapped this component into three JXLayers as follows (this code is a bit simplified but works):
private JComponent wrap(JComponent component, LayerUI... uis) {
JComponent wrappingTarget = component;
for (LayerUI layerUI : uis) {
wrappingTarget = new JXLayer(wrappingTarget, layerUI);
}
return wrappingTarget;
}
And yes, all three effects work perfectly on my component!
Please note: when trying to find a way to let webstart use jars with different certificates (my jar with my certificate and the JXLayer jar with Alexander's certficate), I obviously did something wrong that messed up the webstart cache. If you get a SecurityException when running the demos, you may try to clear the webstart cache. On Windows XP (that is what I am working on) this is done as follows:
Somewhere in the documentation I read that one LayerUI instance may be shared by more than one JXLayer instance. I could not directly think of a good use for it, because if the LayerUI is disabled, it will be disabled for all JXLayers that use it (and vice versa). But nevertheless, I wanted to give it a try, just out of curiousity.
The first thing that I considered was that a LayerUI that is shared by multiple JXLayers may not maintain state information in its own instance. So I created a new class GeneralLayerUI, extending from AbstractLayerUI, that offers the framework of storing StateObjects as client properties in the affected JXLayer. All my LayerUIs extend from this GeneralLayerUI.
Having done this, it was not difficult to prepare a new test application that is almost the same as the wrapping demo, but this time I create two wrapped components, both with the same LayerUIs.
Note: Alex informed me that sharing is only possible with LayerUIs that do not descent from AbstractBufferedLayerUI. That is quite understandable I guess, because the AbstractBufferedLayerUI maintains a BufferedImage that is of course not sharable by multiple JXLayer instances.
The sources are available here:
| SharedTest.html | The text in the shared test demo |
| WrapTest.html | The text in the wrap test demo |
| FlipFlop.java | An interface for anything that has a true or false state |
| FlipFlopAction.java | An AbstractAction implementing FlipFlop |
| GeneralLayerUI.java | The base class for my LayerUIs. Implements a StateObject and some other stuff I found convenient. |
| HideCursorUI.java | The LayerUI that lets the cursor disappear |
| MagnifierUI.java | The LayerUI that shows a magnifying glass over its component |
| MouseDrawingUI.java | The LayerUI that let one draw lines on the surface of a component |
| TestShared.java | Tests the use of shared LayerUIs |
| TestWrapped.java | Tests the use of wrapped LayerUIs |
Update: 2008-08-12.
I read the thread Generic ZoomPanel? on the java.net forums. This blog is referred to in this thread,
Curious what would happen with a magnification that zooms out, I added some factors less than one to the Magnifier. To prevent the original content to shine through, I also gave the glass some color.
I also changed to the newest version of JXLayer and
implemented the new methods
protected boolean isAWTEventListenerEnabled()
and
protected long getAWTEventListenerEventMask()
in GeneralLayerUI.
Update: 2008-09-06.
The above line in red is a typo.
It should read:
removed
protected long getAWTEventListenerEventMask()
in GeneralLayerUI.
In my examples I use the default mask.
If I wanted to change that mask, for example to exclude the MouseWheelEvent,
I should now use the new method
setLayerEventMask().
Normally, one should only ask for events that one really needs to process.
In general one will make a selection in the constructor for a LayerUI
something like this:
public MyLayerY() {
super();
// set the needed mask
setLayerEventMask(
AWTEvent.MOUSE_EVENT_MASK
| AWTEvent.MOUSE_MOTION_EVENT_MASK
// | AWTEvent.MOUSE_WHEEL_EVENT_MASK // This will make JXLayer transparent for the mouseWheel events
| AWTEvent.KEY_EVENT_MASK
| AWTEvent.FOCUS_EVENT_MASK);
}
}
However, as a test I wanted to see what happens if I dispatch MouseWheelEvents unconditionally to
JXLayer's parent. You will find the override of
processMouseWheelEvent in GeneralLayerUI.
My implementations of LayerUIs have no constructor.
Today I updated the jxlayer.jar file with the version that Alex produced on 2008-08-29.
A second blog on JXLayer, describing a generic ZoomUI, can be found here: JXLayer and a generic ZoomUI
Author:
Piet Blok.
If you wish to contact me, please send email to