Working around Component.setEnabled().
I'm having a lot of issues arising from events generated from setEnabled() due to the fact that they get added to the end of the queue. For some reason even running code using SwingUtilities.invokeLater() after the setEnabled() doesn't work, the offending code still ends up executed after what was in invokeLater(). I believe it's part of the UI's reaction to the property change but I haven't pin pointed it yet.
For example:
myJTextField.setEnabled(false);
SwingUtilities.invokeLater(new Runnable(){
publicvoid run(){
myFocusCycleRoot.transferFocusDownCycle();
}
});
Results in focus being transferred to myFocusCycleRoot's default component, then transferred to the component after myJTextField. This leads me to believe that code is run at the end of the queue and then THAT code runs code that gets added to the end of the queue which causes the focus to transfer. The code in invokeLater() would then end up executed between the two. Any ideas for a solution?
[1266 byte] By [
kablaira] at [2007-10-2 10:24:16]

I'd like to have a small runnable code ...
Why not:
SwingUtilities.invokeLater(new Runnable() {
public void run() {
myJTextField.setEnabled(false);
myFocusCycleRoot.transferFocusDownCycle();
}
});
hiwaa at 2007-7-13 1:58:30 >

Well if setEnabled() is causing code to put on the end of the event queue and I'm trying to ensure my code executes after that the sooner the setEnabled() executes the better. I'll try it just becuase I try everything in case my assumptions, or perhaps I should say presumptions, are wrong.
I'm thinking I may be able to use a VetoableChangeListener to "reject" the focus change it causes. Either that or maybe I can clear the focus owner before I change the enabled status so that it won't try and transfer automatically.
Basically what's happening is when a user performs an action (moving to the next bill) it queries the model and based upon some information it will enable or disable certain fields.The "fields" I refer to are of course JTextField, JComboBox and JCheckBox fields. It will then use transferFocusDownCycle() to move focus to the default field, which based upon the installed FocusTraversalPolicy is the first enabled field found. So when this all happens of the field that had focus when the user performed the action becomes disabled, it automatically initiates a transfer of focus which apparently executes after transferFocusDownCycle() causing the focus to jump to the default component then immediately jump to the component after the original field which became disabled.
Any particular reason you can't reverse the order?
> Any particular reason you can't reverse the order?Reverse what order? The focus order? That wouldn't do any good.
> > Any particular reason you can't reverse the order?Or perhaps you meant the order the code is executed. Well, that's the problem, I'm trying to. This is more of a multithreading issue with the EDT at this point.
I'm going to try executing this from a separate thread and then use invokeAndWait() for the disabling before changing focus.
Actually, I did mean reversing the order the code is executed. E.g,
myFocusCycleRoot.transferFocusDownCycle();
SwingUtilities.invokeLater(new Runnable() {
public void run() { myJTextField.setEnabled(false); }
});
Your problem description makes it sound like disabling the field that currently
has focus is what is causing the problem. Hence my suggestion to reverse the
order: get focus off that field, then disable it.
: jay
No. Changing the focus using transferFocusDownCycle() will change focus based on what fields are enabled or disabled at the time. If the fields I need disabled aren't disabled first then it can't select an appropriate field. Calling it first, then disabling, then calling it again may fix it under some circumstances but it's just going happen under a different set.
Perhaps I am misunderstanding your problem description.
As I understand your description, if you disable the component with focus, focus is transferred to the next enabled component.
So, if you invoke transferFocusDownCycle(), focus will be set to the default component. If this component is then made disabled, won't focus go to the next enabled component, which should be the component you wanted to end up with focus anyways?
Perhaps you can post a [url http://homepage1.nifty.com/algafield/sscce.html]Short, Self Contained, Compilable and Executable Example Program[/url] that demonstrates the problem?
: jay
> Perhaps I am misunderstanding your problem
> description.
>
> As I understand your description, if you disable the
> component with focus, focus is transferred to the
> next enabled component.
>
> So, if you invoke transferFocusDownCycle(), focus
> will be set to the default component. If this
> component is then made disabled, won't focus go to
> the next enabled component, which should be the
> component you wanted to end up with focus anyways?
So what happens if you disable the component and then do transferFocusDownCycle()? You might expect that the component is disabled, transferring focus to the next enabled component, and then after that transferFocusDownCycle() is executed putting focus on the first enabled component. It doesn't. The change in focus from disabling happens AFTER the execution of transferFocusDownCycle() .
The problem here is that the next enabled component after the component being disabled is not the component we want to have focus. It is possible for a component prior to the component that has focus to have become enabled in this process, hence we want focus to flow to it.
if I'm reading the snippet in Manning's book correct, transferFocusDownCycle()
takes you to the next focus cycle, not the next component (again, if I'm reading
correctly, is what you're trying to do)
here's the book's sample code of several focus cycles
tabbing from the first component (cycle A) takes you all the way down to the last
component (cycle D). Continued tabbing just takes you to the top of cycle D,
ie you are stuck in cycle D.
note: this does NOT work in 1.5, works OK in 1.4.2_08
/**
* Copyright 1999-2002 Matthew Robinson and Pavel Vorobiev.
* All Rights Reserved.
*
* ===================================================
* This program contains code from the book "Swing"
* 2nd Edition by Matthew Robinson and Pavel Vorobiev
* http://www.spindoczine.com/sbe
* ===================================================
*
* The above paragraph must be included in full, unmodified
* and completely intact in the beginning of any source code
* file that references, copies or uses (in any way, shape
* or form) code contained in this file.
*/
import java.awt.*;
import javax.swing.*;
import javax.swing.border.*;
public class FocusDemo extends JFrame {
public FocusDemo() {
super("Focus Demo");
JPanel contentPane = (JPanel) getContentPane();
contentPane.setBorder(new TitledBorder("Focus Cycle A"));
contentPane.add(createComponentPanel(), BorderLayout.NORTH);
JDesktopPane desktop1 = new JDesktopPane();
contentPane.add(desktop1, BorderLayout.CENTER);
JInternalFrame internalFrame1 = new JInternalFrame("Focus Cycle B", true, true, true, true);
contentPane = (JPanel) internalFrame1.getContentPane();
contentPane.add(createComponentPanel(), BorderLayout.NORTH);
JDesktopPane desktop2 = new JDesktopPane();
contentPane.add(desktop2, BorderLayout.CENTER);
desktop1.add(internalFrame1);
internalFrame1.setBounds(20,20,500,300);
internalFrame1.show();
JInternalFrame internalFrame2 = new JInternalFrame("Focus Cycle C", true, true, true, true);
contentPane = (JPanel) internalFrame2.getContentPane();
contentPane.add(createComponentPanel(), BorderLayout.NORTH);
JDesktopPane desktop3 = new JDesktopPane();
contentPane.add(desktop3, BorderLayout.CENTER);
desktop2.add(internalFrame2);
internalFrame2.setBounds(20,20,400,200);
internalFrame2.show();
JInternalFrame internalFrame3 = new JInternalFrame("Focus Cycle D", false, true, true, true);
contentPane = (JPanel) internalFrame3.getContentPane();
contentPane.add(createComponentPanel(), BorderLayout.NORTH);
desktop3.add(internalFrame3);
internalFrame3.setBounds(20,20,300,100);
internalFrame3.show();
}
public static void main(String[] args) {
FocusDemo frame = new FocusDemo();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 1.3
frame.setBounds(0,0,600,450);
frame.setVisible(true);
}
protected JPanel createComponentPanel() {
JPanel panel = new JPanel();
panel.add(new JButton("Button 1"));
panel.add(new JButton("Button 2"));
panel.add(new JTextField(10));
return panel;
}
}
> if I'm reading the snippet in Manning's book correct,
> transferFocusDownCycle()
> takes you to the next focus cycle,
Either you are reading wrong or Manning is wrong.
Transfers the focus down one focus traversal cycle. If this Container is a focus cycle root, then the focus owner is set to this Container's default Component to focus, and the current focus cycle root is set to this Container. If this Container is not a focus cycle root, then no focus traversal operation occurs.
We're calling it on a focus cycle root and consequently the default component will have focus. According to the focus traversal policy installed the default component will be the first component found that is enabled, or null if none are enabled.
>not the next component (again, if I'm reading
> correctly, is what you're trying to do)
Nope. Not what I want at all. That's the problem, setEnabled(false) indirectly results in focus transferring to the next component after focus has already been changed to the default component.
Let's say we have 10 components and we'll call them component 1 through component 10. Component 1 is the first component in the policy, component 10 is the last. The default component is the first enabled component found when iterating from 1 to 10. Now, a user may be viewing these components and may take some action. This action will result in those 10 components having their fields repopulated and some of them disabled based upon information from a model. Likewise, some of the components that may have been disabled prior to the user action may become enabled. Once this happens we want the focus to move to the default component, which may be before, or after, or even the same as the current component depending on what all was enabled/disabled. Unfortunately, if the current component happens to be disabled in this process then that disabling will cause focus to transfer to the next component after we've changed focus to the default component. So let's say we have thus:
Components 1-5 disabled component 6-10 enabled. The user is on component 6. The user takes said action. Now components 3, 4, 7, 9, 10 become enabled. Likewise components 1, 2, 5, 6, 8 become disabled. The default component will be the first enabled, 3, and using transferFocusDownCycle() the default component receives focus. So now the focus is on 3. However, 6 had focus before and was disabled, and somehow it's code only now results in transfering focus to 7, so it jumps to 7.
Now consider what happens if 7 became disabled. It happens before it has focus, so the focus transfer from 7 isn't triggered, but 7 still receives focus despite being disabled because the trigger in 6 happened before it was disabled. Now 7, a disabled component, receives focus but is disabled so focus ends up in limbo and nothing has it anymore.
Yes, it's a freaking nightmare and I'm about ready to file a bug because the more I think about it the dumber it sounds. Why should setEnabled() transfer focus? That should be left to the client to decide whether or not focus should even happen. Worst part of it is because of the way it's written if the next component becomes disabled between it's checking for focus owner and actually transferring focus then focus ends up in limbo and the next component gets a focus gained but never a focus lost.Plus, I don't see it documented in the API at all.
Oh, and I will try to put together a compilable example though it most certainly won't be short.
I discovered the problem.
Can you share what it was?
Unfortunately, I don't think so. It involves how some COBOL code was being accessed which was inadvertently happening on the EDT . It wasn't a problem with the Swing code, that was just a symptom of it.