 |
Enabling/Disabling Swing Containers
Posted by alexfromsun on June 26, 2007 at 08:58 AM | Comments (23)
The fact that disabling a Swing container doesn't mean disabling its child component has always been surprising for Swing beginners
for example see this thread
moreover sometimes it is surprising for me as well :-)
Consider the following code:
JPanel panel = new JPanel();
panel.add(new JButton("Hello"));
frame.add(panel);
panel.setEnabled(false);
When the panel is disabled its child button remains perfectly accessible.
In this blog we'll examine how to disable a container with all its children
and let's start with the most well-known and, at the same time, the most limited technique:
Blocking GlassPane
The GlassPane technique can hardly be used to disable a particular container,
it's always used to disable a whole frame, which can be desirable during a time consuming operation
Amost a year ago I wrote
A well-behaved GlassPane blog, where I described how to properly create "dynamic" GlassPanes,
which respond to MouseEvents and don't break the "mouseEvents transparency"
However in some cases GlassPane can deliberately consume all mouseEvents to make the frame looks "frozen"
I didn't cover this case in the previous blog because it looked quite trivial
you just set you own GlassPane with fancy animation, add a mouseListener to it to break the "mouseEvents transparency" and that's it
public class InitialGlassPane extends JPanel {
public InitialGlassPane() {
setOpaque(false);
// This is breaking the "mouseEvents transparency"
// see also
// http://weblogs.java.net/blog/alexfromsun/archive/2006/09/index.html
// http://weblogs.java.net/blog/alexfromsun/archive/2005/10/index.html
addMouseListener(new MouseAdapter() {
});
}
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
// fill the component with the translucent color
g2.setColor(new Color(0, 0, 128, 128));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
}
at least this component looks like a good candidate to be a blocking GlassPane
Check it out:
For this example I created a frame to test the InitialGlassPane
- Select the "Show GlassPane" menuItem from the Option menu or press Ctrl-D for the same result
(letter "D" means that we are gonna "disable" the frame)
- Click to the frame's components with the mouse
- They don't react - wow, that's what we need !
But... let' try to use keyboard
- If the first checkbox is focused, press Space
- The checkbox gets selected !
- Press Tab and the focus will move to the next component
- You can even type in the textFields
- This is hardly how disabled components should behave
InitialGlassPane blocks mouseEvents but doesn't block events from the keyboard
I've seen several blocking glassPanes which can show very nice animation but failed to block the keyboard
so it was possible to change the state of the components behind them
To solve this problem we should request focus to our GlassPane
and make it to keep the focus until the GlassPane is visible
The InputVerifier class will help us to do that
public class BetterGlassPane extends JPanel {
public BetterGlassPane() {
setOpaque(false);
// This is breaking the "mouseEvents transparency"
// see also
// http://weblogs.java.net/blog/alexfromsun/archive/2006/09/index.html
// http://weblogs.java.net/blog/alexfromsun/archive/2005/10/index.html
addMouseListener(new MouseAdapter() {
});
// This component keeps the focus until is made hidden
setInputVerifier(new InputVerifier() {
public boolean verify(JComponent input) {
return !isVisible();
}
});
}
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g.create();
// fill the component with another translucent color
g2.setColor(new Color(0, 128, 128, 128));
g2.fillRect(0, 0, getWidth(), getHeight());
g2.dispose();
}
}
- Restart the GlassPane test from the previous link
- Select the "BetterGlassPane" from the option menu
- Press Ctrl-D
- Now it is impossible to change any component's state with the keyboard as well as with the mouse
- The frame is truly disabled now
Here is the source code for the GlassPane example
Why blocking GlassPane is *not* a good solution ?
The BetterGlassPane solves the keyboard issue and nevertheless it still has a big problem:
All good applications never block the GUI,
so a user can always interact with it
A GlassPane blocks all components in the frame, including the MenuBar, so if you use it during a time-consuming operation,
user simply has no way to cancel this operation
GlassPanes don't solve the main problem -
How to disable a particular container with all its children ?
=== The way to go ===
The JXLayer is a great choice if you want to enrich existing components and it also solves our main problem.
Wrap your container with the JXLayer and call JXLayer.setLocked(true) after that - all components inside will be disabled
Note: Initialy JXLayer overrode setEnabled() method to provide the same functionality, but this led to big problems with mouse cursor support, so the old setEnabled() method was rewritten as setLocked(boolean) and LockedLayerDemo was accordingly updated.
Seeing is believing:
- Select the checkbox at the righ hand side of the frame
- The container will become disabled
- Try to click on it with the mouse and see how the focus is traversed when you press the Tab key
JXLayer is even more capable, it remembers its last focused child component:
- Enable the container, focus a textField and start typing
- Press Alt-D to disable a container
- Don't move focus with the mouse or the Tab key to another component
- press Alt-D one more time to enable the layer again
- Notice that your textField is still focused and you can continue typing
The powerful painters' support makes it easy to modify the visual appearance of a container when it is disabled:
- Disable a container
- Switch between three painters with help of the menu or the radioButton from the right hand side
- Notice how the disabled component can be modified
- Change the Look and Feel with the menu, resize the frame - the disabled container will be accordingly repainted
JXLayer is extremely easy to use, check out the source code of this demo.
By the way, two of my painters use the beautiful open-sourced image filters from www.jhlabs.com,
don't miss this very useful resource
The JXLayer is an open source project,
all files with the demos included you can find on the SwingHelper project
I am happy to have so much feedback from everyone who uses JXLayer in their projects
more blogs are coming
Note: Check out the Disabling Swing Containers, the final solution blog
Thanks
alexp
Bookmark blog post: del.icio.us Digg DZone Furl Reddit
Comments
Comments are listed in date ascending order (oldest first) | Post Comment
-
Hi Alex,
JXLayer is simple and good thing. I suppose that such component
has to be included into JDK. Some time ago I also did
same or similar wheel invention :) and probably many other
developers too.
BTW how it will work if I set a hourglass cursor to JXLayer in
disabled state? Will the cursor appear only over JXLayer?
As I remember there were problems with setting cursors to
disabled components.
Maxim
Posted by: maxz1 on June 26, 2007 at 11:47 PM
-
Here's a link to a discussion (in SwingLabs forum) trying to shine light on the darker issues with container-wide enable/disable:
http://forums.java.net/jive/thread.jspa?threadID=13758
Congrats if JXLayer can cope with them ;-)
Jeanette
Posted by: kleopatra on June 27, 2007 at 06:26 AM
-
Hello Maxim
Initially disabled JXLayer supported only default mouse cursor
as well as any other disabled Swing components do
But when I read your comment I realized that being able to
set a custom cursor for disabled layer is a useful feature
With help of some AWT magic the request is completed
Check out the updated demo from this blog
I added a new checkbox and you can set the wait cursor for disabled layer
I set the cursor to the layer's GlassPane
since it covers the whole layer and all its child components
layer.getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
it does the trick
Thank you for your valuable comment !
alexp
Posted by: alexfromsun on June 27, 2007 at 06:44 AM
-
Hello Jeanette
Thanks for the link
By the way, JXLayer doesn't use all that spooky techniques like "disable everything recursively"
:-)
The solution is clear and elegant -
I use the layer's GlassPane to block the mouseEvents
and use a custom FocusTraversalPolicy to prevent child components from receiving focus
together with keeping the recent focus owner, mouse cursors
and complete painters' support it makes a really good solution
I extensively tested this functionlaity and now
I am sure that puzzle is solved
Your feedback is very welcome
Thanks
alexp
Posted by: alexfromsun on June 27, 2007 at 07:02 AM
-
This is very cool. One thing though, if you call isEnabled() on any of the child components they will return true right? So while this is just as effective as disabling, it's not really disabling the children. It's more like blocking or restricting them. I built something similar for our application. Ours is used for long calls to a server and optionally displays a message, icon, and infinite progress bar. We don't have the nifty blur or emboss effects.
Posted by: aberrant on June 27, 2007 at 08:15 AM
-
Alex, somehow I knew I should have looked at your code before posting Will do soon, thanks!
Jeanette
Posted by: kleopatra on June 27, 2007 at 08:22 AM
-
BTW, could you all please not use those funky scrollpanes in the messages? It makes hard reading because a) I have to left scroll and b) the font size inside is increased differently from the font size outside it (yeah, my eyes are not the youngest, so they prefer a bigger font :-) Thanks!
Posted by: kleopatra on June 27, 2007 at 08:28 AM
-
Hello Aberrant
That's correct - I don't use recursive disabling
because I don't think it's a good solution
It's nice to hear that you've used similar approach
I hope you'll try JXLayer in your next project
please check the other JXLayer demos from SwingHelper.dev.java.net
you may find them useful
Thanks
alexp
Posted by: alexfromsun on June 27, 2007 at 08:51 AM
-
Hello Jeanette
I reformatted my comments
Hope it looks better now
Thanks
alexp
Posted by: alexfromsun on June 27, 2007 at 08:53 AM
-
Hi Alex and again thanks a lot for the elegant JXLayer solution !
I have one question/problem that I hope you can help with.
I use the JXLayer for my Hourglass disable/enable solution. The only problem is that when I disable/enable the JXLayer from for example a click of a button, the focus of the button is not retained after the JXLayer is enabled again.
Do you have any idea what could be the matter, or of a possible workaround?
You can see this by changing your demo just a little bit.
To save you a minute, here's a code snip:
private JComponent createLayerPanel()
{
JComponent panel = new JPanel();
panel.add(new JCheckBox("JCheckBox"));
panel.add(new JRadioButton("JRadioButton"));
panel.add(new JTextField(15));
JButton btn = new JButton("Have a nice day");
btn.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent ae)
{
disableEnable();
disableEnable();
}
});
panel.add(btn);
panel.add(new JTextField(15));
panel.add(new JCheckBox("JCheckBox"));
panel.add(new JRadioButton("JRadioButton"));
panel.add(new JTextField(15));
panel.add(new JCheckBox("JCheckBox"));
panel.add(new JRadioButton("JRadioButton"));
panel.setBorder(BorderFactory.createEtchedBorder());
return panel;
}
private void disableEnable()
{
layer.setEnabled(!layer.isEnabled());
if (layer.isEnabled())
{
// Reset cursor for enabled layer
layer.getGlassPane().setCursor(null);
}
else if (waitCursorItem.isSelected())
{
layer.getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
}
waitCursorItem.setEnabled(!layer.isEnabled());
}
Best regards, Gath
Posted by: gath55 on August 01, 2007 at 05:37 AM
-
Hello Gath
I just fixed this problem,
please test the latest version
and let me know if everything is ok
Thanks for using JXLayer
alexp
Posted by: alexfromsun on August 01, 2007 at 11:50 AM
-
Hi Alex, thank you for the quick response.
The problem in the demo is fixed. I still have a slight problem, for example if I make a button open a dialog (modal), when I close the dialog, the focus still disappears. That is if I'm doing it like this:
disableLayer()
showModalDialog()
enableLayer()
But now with your fix I can work around that. Please let me know if my current problem is something you could fix as well, because otherwise I have to go through a lot of code :-/ :-)
Best regards, Gath
Posted by: gath55 on August 01, 2007 at 02:04 PM
-
Hello Gath
I see no reason why to disable a layer when modal dialog is shown
because in this case you can't do anything with any component from a blocked window.
Could you give more details, why you need it ?
Thanks
alexp
Posted by: alexfromsun on August 01, 2007 at 03:07 PM
-
Hello Alex,
I agree with you and I can fix this now, the reason I asked was just that I have a lot of code which is like this:
try
{
showHourglassAndDisableLayer();
openModalDialog();
if(dialogNotCanceled())
{
doStuffBasedOnInput();
}
}
finally
{
hideHourglassAndEnableLayer();
}
Before when I used a not as good disabling technique it worked fine because when the dialog was closed the Focus would return to, for example,
the button which was clicked to generate the action that opens the dialog.
I don't know the details of how the Java Focus system works and why it doesn't work the same way with JXLayer, but if this is not
a reasonable thing for you to fix, don't worry, I will just go through the code and make the changes.
Thanks for the help,
Gath
Posted by: gath55 on August 02, 2007 at 03:58 AM
-
Hello Gath
It doesn't work the same way for JXLayer,
because it is a special component and I made some efforts
to change the way how a disabled layer works with focus
When you disable a layer and the focus inside it,
layer requests the focus to its glassPane
and that's why it allows it to efficiently disable all its children
so the solution is - if you want JXLayer to work with focus exaclty like any other container don't disable it
I am not sure that you really need to disable it for your case
modal dialog blocks all components from layer's window
and the only thing you need for your case is to show Hourglass cursor you don't need to disable your layer to do that
layer.getGlassPane().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
I would also set the cursor back before doStuffBasedOnInput and skip the finally clause
Thanks
alexp
Posted by: alexfromsun on August 02, 2007 at 05:10 AM
-
Hi Alex, yes I agree, I just need to clean up the code a little bit. Thanks again !! Gath
Posted by: gath55 on August 02, 2007 at 12:47 PM
-
Hello Everybody
It turned out that it was impossible to correctly support mouse cursor
for disabled layer,
so the JXLayer.setEnabled() method was rewritten as JXLayer.setLocked(boolean)
I am sorry for any inconvenience it may cause
the LockedLayerDemo was accordingly updated,
now everything works just perfect
Thanks
alexp
Posted by: alexfromsun on August 09, 2007 at 08:05 AM
-
I am going to check out JXLayer, but the InputVerifier approach caught my eye because it is far more elegantly succinct than my convoluted prior approach to add/remove KeyEventDispatcher listeners on the global KeyboardFocusManager based on the glass pane's visibility. Alex clearly describes the InputVerifier technique: To solve this problem we should request focus to our GlassPane and make it to keep the focus until the GlassPane is visible... The code snip above shows how to set the InputVerifier in the ctor, but it took me a bit of a while to see that he was requesting focus of the glass pane in his MainFrame.java test code. Simply overriding the glass pane's setVisible() method would automate that step: @Override
public void setVisible(boolean aFlag) {
super.setVisible(aFlag);
if (aFlag) {
requestFocusInWindow();
}
}
Posted by: gkedge on August 27, 2007 at 01:16 PM
-
Hello everybody
In the previous example: is it possible to lock the JXLayer (i.e. disable container) and add some JButton on top of the JXLayer (eg. with name 'Disable container') that re-enables container when is clicked? Of course, JButton is invisible when JXLayer isn't locked.
Thanks,
Danijel
Posted by: danijelbasic on December 20, 2007 at 02:26 AM
-
Hello Danijel
JXLayer has its own GlassPane, see JXLayer.getGlassPane() method you can set any layout and add any components to it
so it is quite easy to implement what you described
Thanks>
alexp
Posted by: alexfromsun on December 20, 2007 at 05:00 AM
-
Hello everybody
There seem to be a minor bug in the blocking in the "Locked/Unlocked layer demo". It do not block for mnemonic keystrokes. I added a mnemonic to the "Have a nice day" button and it sadly get activated despite the locking. A slightly more complicated variant: I moved the JMenuBar to inside the locked area and added a M mnemonic to 'Menu' Now the menu bar looks locked when I lock it, but the menu is still working, I can make it drop down and change look and feel despite it was intended to be locked. In my application I have one critical operation, and while it is running I will not like my user to be able to do anything else How do I accomplish that? Lars Worsaae
Posted by: worsaae on January 04, 2008 at 04:47 AM
-
Hello Lars
Mnemics are not blocked inside the locked layer,
it is sad but true
I didn't find an acceptable solution for this problem yet
will let you know when I have any news
As for menuBar, it is not designed to be added to arbitrary components,
frame.setJMenuBar() is the only recommeded usage
if you need to disable it, try menuBar.setEnabled(false)
Thanks
alexp
Posted by: alexfromsun on January 11, 2008 at 07:43 AM
-
Hello. I found your blog looking for good solution for completly broken InputVerifier functionality.
In short InputVerifier works only in very limited way. It does not prevent from:
-switching JTabbedPanePane
-selecting combobox value with mouse
-selecting value in JTree
-firing JButton actionPerformed
Case is described here
There are many attempts to make it work but most promising is using GlassPane as described here
Unfortunetly this is not well behaved glassPane. Maybe it would be good to construct it with your approach to make it finally working.
Best regards,
Marek Mosiewicz
http://www.jotel.com.pl
Posted by: marekmosiewicz on April 02, 2008 at 04:48 AM
|