java component focus lost event and tabbedPane design problem

K

Krick

I'm working on a large application that uses a tabbedPane design.

Certain components on each "page" of our application have events that
are triggered when focus is lost. For example, there is a field that
asks for a number between 1 and 27. If the user enters 27 and then
leaves the field, a dialog is popped up asking for an additional piece
of information that is specifically related to choice 27.

The problem is that the user can type 27 in the field and then click
on the tab control to switch to a different "page". The JVM gladly
switches to the other page first, and then pops up the dialog. The
user is seeing a dialog pop up asking them a question without the
context of the original text field and is subsuquently confused.

I would really like to have the focus lost event triggered first, and
then the focus gained event on the tab control. I've tried every
thing I can think of and it doesn't seem possible. Have I found
another Java bug?

I can provide a small example if necessary to illustrate the problem.

any thoughts would be appreciated
....
Krick
 
S

Sajjad Lateef

Certain components on each "page" of our application have events that
are triggered when focus is lost. For example, there is a field that
asks for a number between 1 and 27. If the user enters 27 and then
leaves the field, a dialog is popped up asking for an additional piece
of information that is specifically related to choice 27.

The problem is that the user can type 27 in the field and then click
on the tab control to switch to a different "page". The JVM gladly
switches to the other page first, and then pops up the dialog. The
user is seeing a dialog pop up asking them a question without the
context of the original text field and is subsuquently confused.

Have you tried to make that dialog modal?

From Dialog.java comments:
* A dialog can be either modeless (the default) or modal. A modal
* dialog is one which blocks input to all other toplevel windows
* in the application, except for any windows created with the dialog
* as their owner.
 
K

Krick

Phil... said:
I use tabs too, can you post your code it might help me too.


Ok, here's a really lame example that demonstrates the problem.
Compile and run it. Type something in the text field, and then
click on the second tab. You'll see that the tabs switch before
the focus lost event is triggered on the text area and the mesage box
is popped up over the second tab...



import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Application1 extends JFrame {

JPanel contentPane;
BorderLayout borderLayout1 = new BorderLayout();
JTabbedPane jTabbedPane1 = new JTabbedPane();
JPanel jPanel1 = new JPanel();
JPanel jPanel2 = new JPanel();
JTextField jTextField1 = new JTextField();
JLabel jLabel1 = new JLabel();

public Application1() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try {
jbInit();
}
catch(Exception e) {
e.printStackTrace();
}
}

private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(borderLayout1);
this.setSize(new Dimension(400, 300));
jLabel1.setText("Type something here and then click Tab2");
jTextField1.setPreferredSize(new Dimension(50, 21));
jTextField1.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(FocusEvent e) {
jTextField1_focusLost(e);
}
});

contentPane.add(jTabbedPane1, BorderLayout.CENTER);
jPanel1.add(jLabel1, null);
jPanel1.add(jTextField1, null);
jTabbedPane1.add(jPanel1, "Tab1");
jTabbedPane1.add(jPanel2, "Tab2");
}

protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID() == WindowEvent.WINDOW_CLOSING) {
System.exit(0);
}
}

void jTextField1_focusLost(FocusEvent e) {
JOptionPane.showConfirmDialog(this, "Are you sure about what you entered?",
"", JOptionPane.YES_NO_OPTION);
}

public static void main(String[] args) {
JFrame frame = new Application1();
frame.setVisible(true);
}

}
 
K

Kleopatra

Krick said:
I'm working on a large application that uses a tabbedPane design.

Certain components on each "page" of our application have events that
are triggered when focus is lost. For example, there is a field that
asks for a number between 1 and 27. If the user enters 27 and then
leaves the field, a dialog is popped up asking for an additional piece
of information that is specifically related to choice 27.

The problem is that the user can type 27 in the field and then click
on the tab control to switch to a different "page". The JVM gladly
switches to the other page first, and then pops up the dialog. The
user is seeing a dialog pop up asking them a question without the
context of the original text field and is subsuquently confused.

The solution is a two-step process:

1) don't use focusListeners to popup the dialog but InputVerifiers, do
it in shouldYieldFocus (check the bug parade there's a workaround for a
little problem when opening dialogs) - they are called internally
whenever the focus is move from a component _before_ the next component
get's it.

2) unfortunately that does not help with JTabbedPane because
setSelectedIndex does not care about InputVerifiers ... but you can
easily make it so by setting a custom SingleSelectionModel that simply
does not change the selectedIndex if the current focus owner is a child
of the currently selected tab, has an InputVerifier and objects to the
focus yield.

Greetings
Jeanette
 
K

Krick

Kleopatra said:
The solution is a two-step process:

1) don't use focusListeners to popup the dialog but InputVerifiers, do
it in shouldYieldFocus (check the bug parade there's a workaround for a
little problem when opening dialogs) - they are called internally
whenever the focus is move from a component _before_ the next component
get's it.

2) unfortunately that does not help with JTabbedPane because
setSelectedIndex does not care about InputVerifiers ... but you can
easily make it so by setting a custom SingleSelectionModel that simply
does not change the selectedIndex if the current focus owner is a child
of the currently selected tab, has an InputVerifier and objects to the
focus yield.

Greetings
Jeanette


Thanks so much for your help. I have it almost working, however, I'm
having trouble with the custom SingleSelectionModel. I've copied the
source for javax.swing.DefaultSingleSelectionModel and renamed it
CustomSingleSelectionModel, now I'm trying to implement the additional
focus yield checks in the setSelectedIndex method. The problem I'm
having is that I can't figure out how to get access to the current
focus owner, the JTabbedPane, or any of the children from within that
method.

Is there something subtle that I'm missing?

....
Krick
 
K

Kleopatra

Krick said:
Thanks so much for your help. I have it almost working, however, I'm
having trouble with the custom SingleSelectionModel. I've copied the
source for javax.swing.DefaultSingleSelectionModel and renamed it
CustomSingleSelectionModel, now I'm trying to implement the additional

why copy? subclass...
focus yield checks in the setSelectedIndex method. The problem I'm
having is that I can't figure out how to get access to the current
focus owner, the JTabbedPane, or any of the children from within that
method.

1) keyboardFocusManager.get/*Permanent*/FocusOwner()
2) your model will need a reference to the tabbedPane

Greetings
Jeanette
 
K

Krick

Kleopatra said:
why copy? subclass...


1) keyboardFocusManager.get/*Permanent*/FocusOwner()
2) your model will need a reference to the tabbedPane

Greetings
Jeanette


Ok, I've revised the example that I posted earlier to incorporate your
suggestions (code is at the end of this post). Though notice that I
was able to implement a new setSelectedIndex() method without
subclassing SingleSelectionModel.

The problem that I have now is that I need my shouldYieldFocus method
to pop up an input OptionPane. To gather additional information for a
given input (in this case the number 27).

I suppose I could limit the shouldYieldFocus method to just returning
true or false and have the OptionPane popped up inside the
setSelectedIndex
method of the Custom Tabbed Pane Model, but what happens if I have
multiple
controls on a tab that each have their own specific "additional
information"
pop-ups?

If you run the program, you'll notice that the events happen in this
order:

1. jTabbedPane1's setSelectedIndex() is called
2. the tab is switched
3. jTextField1's InputVerifier's shouldYieldFocus() is called
4. jTextField1's loses focus


It seems to me, the way it should really happen is this:

1. a focus shift from jTextField1 is triggered which should
2. trigger shouldYieldFocus(). Then if jTextField1 yields focus,
3. the tabbedPane control should gain focus and _THEN_
4. the setSelectedIndex method should be called.


....
Krick


//*******************************************************************
// BEGIN CODE
//*******************************************************************

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Application1 extends JFrame {

JPanel contentPane;
BorderLayout borderLayout1 = new BorderLayout();
JTabbedPane jTabbedPane1 = new JTabbedPane();
JPanel jPanel1 = new JPanel();
JPanel jPanel2 = new JPanel();
JTextField jTextField1 = new JTextField();
JTextField jTextField2 = new JTextField();
JLabel jLabel1 = new JLabel();

public Application1() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try { jbInit(); } catch(Exception e) { e.printStackTrace(); }
}

private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(borderLayout1);
this.setSize(new Dimension(400, 300));
jLabel1.setText("Type '27' here and then click Tab2");
jTextField1.setPreferredSize(new Dimension(50, 21));

// set a new model with a custom setSelectedIndex method
jTabbedPane1.setModel(new DefaultSingleSelectionModel() {
public void setSelectedIndex(int index) {
Component compWithFocus = KeyboardFocusManager.
getCurrentKeyboardFocusManager().
getFocusOwner();


if(compWithFocus != null) {
System.out.println("in jTabbedPane1_setSelectedIndex, "+
"currentFocusOwner: " +
compWithFocus.getClass().getName());
}
super.setSelectedIndex(index);
}
});

// add an input verifier
jTextField1.setInputVerifier(new InputVerifier() {
public boolean verify(JComponent comp) {
return (!"27".equals(((JTextField)comp).getText()));
}
public boolean shouldYieldFocus(JComponent input) {
System.out.println("jTextField1_InputVerifier_shouldYieldFocus");
if(!verify(input)) {
// According to the documentation should yield focus is
allowed to
// cause side effects. So temporarily remove the input
verifier on
// the text field.
input.setInputVerifier(null);

// Pop up the message dialog.
Frame frm = (Frame)
SwingUtilities.windowForComponent(input);
String title = "Additional information is needed for 27";
JOptionPane.showConfirmDialog(frm, jTextField2, title,
JOptionPane.OK_CANCEL_OPTION,
JOptionPane.PLAIN_MESSAGE,
null);

// Reinstall the input verifier.
input.setInputVerifier(this);
}
return true;
}
});

// this focus listener is just here for diagnostic purposes
jTextField1.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(FocusEvent e) {
System.out.println("jTextField1_focusLost");
}
});

contentPane.add(jTabbedPane1, BorderLayout.CENTER);
jPanel1.add(jLabel1, null);
jPanel1.add(jTextField1, null);
jTabbedPane1.add(jPanel1, "Tab1");
jTabbedPane1.add(jPanel2, "Tab2");
}

protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID()==WindowEvent.WINDOW_CLOSING) { System.exit(0); }
}

public static void main(String[] args) {
JFrame frame = new Application1();
frame.setVisible(true);
}

}
 
K

Kleopatra

Krick said:
Ok, I've revised the example that I posted earlier to incorporate your
suggestions (code is at the end of this post). Though notice that I
was able to implement a new setSelectedIndex() method without
subclassing SingleSelectionModel.

anonymous or not - of course you are subclassing
(DefaultSingleSelectionModel).

The problem that I have now is that I need my shouldYieldFocus method
to pop up an input OptionPane. To gather additional information for a
given input (in this case the number 27).

the problem with your code is, that you don't follow the solution (issue
2 in my original answer) I outlined. In particular, your custom model
does call super.setSelectedIndex(..) unconditionally instead of doing so
only if no inputVerifier objected.

Greetings
Jeanette
 
K

Krick

Kleopatra said:
the problem with your code is, that you don't follow the solution (issue
2 in my original answer) I outlined. In particular, your custom model
does call super.setSelectedIndex(..) unconditionally instead of doing so
only if no inputVerifier objected.

Actually, I am aware of that. I tried it the way you originally
suggested but found that I could ONLY leave the field by clicking on a
tab if the control has "27" entered in it. Since the shouldYieldFocus
method returns false in that case, I am unable to move from one
control to another on the same tab.

My test application, using your original suggestions, that illustrates
this problem is at the end of this posting.

thanks for your help
....
Krick



import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Application1 extends JFrame {

JPanel contentPane;
BorderLayout borderLayout1 = new BorderLayout();
JTabbedPane jTabbedPane1 = new JTabbedPane();
JPanel jPanelTab1 = new JPanel();
JPanel jPanel1 = new JPanel();
JLabel jLabel1 = new JLabel();
JTextField jTextField1 = new JTextField();
JPanel jPanelTab2 = new JPanel();
JPanel jPanel2 = new JPanel();
JLabel jLabel2 = new JLabel();
JTextField jTextField2 = new JTextField();
JTextField jTextFieldPopup = new JTextField();

public Application1() {
try { jbInit(); } catch(Exception e) { e.printStackTrace(); }
}

private void jbInit() throws Exception {
this.setSize(new Dimension(400, 300));
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
contentPane = (JPanel) this.getContentPane();
contentPane.setLayout(borderLayout1);
jLabel1.setText("Type '27' here and then click Tab2");
jTextField1.setPreferredSize(new Dimension(50, 21));
jLabel2.setText("Type 27 here and then click in the textField
above");
jTextField2.setPreferredSize(new Dimension(50, 21));

// set a new model with a custom setSelectedIndexmethod
jTabbedPane1.setModel(new DefaultSingleSelectionModel() {
public void setSelectedIndex(int index) {

Frame frm = (Frame)SwingUtilities.windowForComponent(jTabbedPane1);
Component compWithFocus = KeyboardFocusManager.
getCurrentKeyboardFocusManager().
getFocusOwner();

if(compWithFocus != null) {
System.out.println("jTabbedPane1_setSelectedIndex, "+
"currentFocusOwner: " +
compWithFocus.getClass().getName());
}

int currSelectedTabIndex = getSelectedIndex();
if(currSelectedTabIndex >=0) {
JPanel currSelectedTabPanel = (JPanel)jTabbedPane1.

getComponentAt(currSelectedTabIndex);
if(currSelectedTabPanel != null) {
if(compWithFocus instanceof JComponent) {
JComponent comp = (JComponent)compWithFocus;
boolean isChild = isChild(currSelectedTabPanel, comp);
InputVerifier iv = comp.getInputVerifier();
if (isChild && iv!=null && !iv.shouldYieldFocus(comp)) {
// Pop up the message dialog.
String title = "Additional information is needed for
27";
JOptionPane.showConfirmDialog(frm, jTextFieldPopup,
title,

JOptionPane.OK_CANCEL_OPTION,

JOptionPane.PLAIN_MESSAGE, null);
}
}
}
}
super.setSelectedIndex(index);
}
});

// add an input verifier
jTextField1.setInputVerifier(new InputVerifier() {
public boolean verify(JComponent comp) {
return (!"27".equals(((JTextField)comp).getText()));
}
public boolean shouldYieldFocus(JComponent input) {
System.out.println("jTextField1_InputVerifier_shouldYieldFocus");
return (verify(input));
}
});

// add an input verifier
jTextField2.setInputVerifier(new InputVerifier() {
public boolean verify(JComponent comp) {
return (!"27".equals(((JTextField)comp).getText()));
}
public boolean shouldYieldFocus(JComponent input) {
System.out.println("jTextField2_InputVerifier_shouldYieldFocus");
return (verify(input));
}
});

// this focus listener is just here for diagnostic purposes
jTextField1.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(FocusEvent e) {
System.out.println("jTextField1_focusLost");
}
});

// this focus listener is just here for diagnostic purposes
jTextField2.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusLost(FocusEvent e) {
System.out.println("jTextField2_focusLost");
}
});

contentPane.add(jTabbedPane1, BorderLayout.CENTER);
jPanelTab1.add(jPanel1, null);
jPanel1.add(jLabel1, null);
jPanel1.add(jTextField1, null);
jPanelTab1.add(jPanel2, null);
jPanel2.add(jLabel2, null);
jPanel2.add(jTextField2, null);
jTabbedPane1.add(jPanelTab1, "Tab1");
jTabbedPane1.add(jPanelTab2, "Tab2");
}

public static boolean isChild(Container cont, Component comp) {
Component p = null; // parent
for (p=comp.getParent(); p!=null && p!=cont; p=p.getParent())
continue;
return (p != null);
}

public static void main(String[] args) {
new Application1().setVisible(true);
}

}
 
K

Krick

I think I found my own work-around (see code below).

I had to extend JTabbedPane to make it behave like other components
with regard to focus. It seems to work correctly now.

Check it out and let me know what you think.

....
Krick



import javax.swing.*;

public class WorkingJTabbedPane extends JTabbedPane {

public WorkingJTabbedPane() {
this(TOP, WRAP_TAB_LAYOUT);
}

public WorkingJTabbedPane(int tabPlacement) {
this(tabPlacement, WRAP_TAB_LAYOUT);
}

public WorkingJTabbedPane(int tabPlacement, int tabLayoutPolicy) {
super(tabPlacement, tabLayoutPolicy);

setModel(new DefaultSingleSelectionModel() {
public void setSelectedIndex(int index) {
requestFocus();
super.setSelectedIndex(index);
}
});
}

public void requestFocus() {
super.requestFocus();
}

}
 
K

Krick

I think I spoke too soon. The solution (see below) is even simpler
than I just posted.

However, I believe the real problem is in BasicTabbedPaneUI.

BasicTabbedPaneUI.MouseHandler.mousePressed should call
tabPane.requestFocus()
before tabPane.setSelectedIndex(tabIndex).



import javax.swing.*;

public class WorkingJTabbedPane extends JTabbedPane {
public void setSelectedIndex(int index) {
requestFocus();
super.setSelectedIndex(index);
}
}
 
K

Krick

Ok, I discovered a few limitations of my previous solution.
In my previous version, if the focus owner prior to clicking on the
tab had an InputVerifier that returned FALSE, the tab switch would
still happen.
This version (code below) works correctly in that situation now.

Any comments and/or criticisms will be appreciated.

....
Krick


import java.awt.*;
import javax.swing.*;

public class WorkingJTabbedPane extends JTabbedPane {

public void setSelectedIndex(int index) {
Component comp = KeyboardFocusManager.
getCurrentKeyboardFocusManager().getFocusOwner();

// if no tabs are selected
// -OR- the current focus owner is me
// -OR- I request focus from another component and get it
// then proceed with the tab switch

if(getSelectedIndex()==-1 || comp==this || requestFocus(false)) {
super.setSelectedIndex(index);
}
}

}
 
K

Kleopatra

import java.awt.*;
import javax.swing.*;

public class WorkingJTabbedPane extends JTabbedPane {

public void setSelectedIndex(int index) {
Component comp = KeyboardFocusManager.
getCurrentKeyboardFocusManager().getFocusOwner();

// if no tabs are selected
// -OR- the current focus owner is me
// -OR- I request focus from another component and get it
// then proceed with the tab switch

if(getSelectedIndex()==-1 || comp==this || requestFocus(false)) {
super.setSelectedIndex(index);
}
}

}

yeah I saw this before - but I have no time to follow you into your
experiments when I have it working reliably doing it differently.

And I'm not convinced: hooking into tabbedPane.setSelectedIndex and
doing whatever will not prevent the change of tab if the selection is
changed via the selectionModel.

Greetings
Jeanette
 
K

Krick

yeah I saw this before - but I have no time to follow you into your
experiments when I have it working reliably doing it differently.

And I'm not convinced: hooking into tabbedPane.setSelectedIndex and
doing whatever will not prevent the change of tab if the selection is
changed via the selectionModel.


I did try it your way. I didn't work for what I'm trying to do. Your
solution seems to work well if you only have input verifiers that
simply return true or false and you use that as an indication of
whether you can switch tabs.

In my case, I have an input verifier that needs to prompt for more
information if the value is a certain value (like "27"). The
validator never actually returns false. This is basically what my
input verifier looks like...

jTextField1.setInputVerifier(new InputVerifier() {
public boolean verify(JComponent comp) {
return (!"27".equals(((JTextField)comp).getText()));
}

public boolean shouldYieldFocus(JComponent input) {
if(!verify(input)) {
// Remove the input verifier (Workaround for bug #4532517)
input.setInputVerifier(null);

// Pop up the message dialog.
Frame frm = (Frame) SwingUtilities.windowForComponent(input);
String title = "Enter addional information";
JOptionPane.showMessageDialog(frm, jTextField2, title,
JOptionPane.PLAIN_MESSAGE, null);

// Reinstall the input verifier
input.setInputVerifier(this);
}
return true;
}
});


I don't understand why you think my method will not work.
It's not really experimentation. I set a lot of break points and
walked through the java code to determine exactly where and why the
problem is occuring and where I might be able to intercept the problem
and fix it with the least amount of trouble.

If you look at the source for JTabbedPane.setSelectedIndex(),
you'll see that it calls model.setSelectedIndex() for you.

I suppose that someone could circumvent my method by calling
JTabbedPane.getModel() and then calling setSelectedIndex() directly
through the model. Though, I can't think for the life of me why
someone would to that.

If that's the case, I could very easily extend setSelectedIndex in a
model instead of in the JTabbedPane itself. Regardless, my way seems
a lot simpler.

Ultimately, the problem boils down to one simple issue...


When someone tries to switch tabs (either through a mouse click or
programmatically), the tab control needs to request focus so that
any
input validators are consulted before the switch is allowed to
happen.


My solution is to call JTabbedPane.requestFocus(false) before allowing
the switch. The additional cases in the condition handle when the
JTabbedPane is created and no tabs have been selected, and the case
where the JTabbedPane already has focus.

Why this the java code isn't doing this already points to a much
larger design problem with Swing and component focus. Tons of issues
remain in spite of the new focus system. Don't even get me started on
the problems I'm having with JTable and DefaultCellEditors. It's not
pretty.

I'm definitely not trying to say that what you are doing is wrong or
for that matter that I am right. Lord knows I've still got a lot of
learning to do. I just want to understand why the method you use
(which you must admit is much more involved) is better. What
situations can your solution handle that I'm overlooking? Is there
something about this situation that I'm not considering?

....
Krick
 
K

Kleopatra

Krick said:
I did try it your way. I didn't work for what I'm trying to do.

Then you didn't follow my outline:

"2) unfortunately that does not help with JTabbedPane because
setSelectedIndex does not care about InputVerifiers ... but you can
easily make it so by setting a custom SingleSelectionModel that simply
does not change the selectedIndex if the current focus owner is a child
of the currently selected tab, has an InputVerifier and objects to the
focus yield."

I never saw any code from you that a) checked if the focusOwner is a
child of the tab and b) if so ask its InputVerifier if it's okay to
leave.

Your
solution seems to work well if you only have input verifiers that
simply return true or false and you use that as an indication of
whether you can switch tabs.

no, it doesn't make a difference.
I don't understand why you think my method will not work.
It's not really experimentation.

because I tested it. It's misbehaving in (at least) 2 situations:

1) when the focus is on an objecting child of the selected tab and the
selection is changed via model.setSelectedIndex() the tab is changed
before popping up the dialog - that's the very problem you wanted to
solve.

2) when the focus is on widget _outside_ of the tabbed pane then
selecting another tab does invoke the inputVerifier of that widget.
I suppose that someone could circumvent my method by calling
JTabbedPane.getModel() and then calling setSelectedIndex() directly
through the model. Though, I can't think for the life of me why
someone would to that.

that's no "circumvention" - it's the way swing is supposed to work: the
model rules! So whenever you find something's wrong, first go for the
model to fix it. In fact, tabbedPane.setSelectedIndex is doing too much
internal state change - a clean implementation would set the model's
index and do any state change in a method listening to the model.
If that's the case, I could very easily extend setSelectedIndex in a
model instead of in the JTabbedPane itself. Regardless, my way seems
a lot simpler.

Unfortunately simple solutions often don't solve the problem completely.
It's up you to decide if you can live with the incompleteness.
Ultimately, the problem boils down to one simple issue...

When someone tries to switch tabs (either through a mouse click or
programmatically), the tab control needs to request focus so that
any
input validators are consulted before the switch is allowed to
happen.

sorry - but that's incorrect in 2 (again: at least) details:

a) you included a wrong assumption about the solution into your problem
analysis: focus of the tab is a side-effect of your approach but not
part of the problem. In fact, what to do with focus on selection changes
depends on context.

b) you don't (or at least should not) want to invoke _any_ validators
but only those attached to the focused child of the currently selected
tab
Why this the java code isn't doing this already points to a much
larger design problem with Swing and component focus.

no, I disagree: focus should not be forced unconditionally onto the tab
with every selection, f.i. if the focus is somewhere outside of the tab
then selection changes should do nothing to change the focus, at least
not if the selection change is done programmatically. What to do if the
change is the result of a mouse-click is arguable and depends on
requirements.

What it should do though is to handle the case when the focus has been
on the tab or any of its children consistently, the current
implementation is definitely buggy.
Tons of issues
remain in spite of the new focus system. Don't even get me started on
the problems I'm having with JTable and DefaultCellEditors. It's not
pretty.

I agree. But things improved considerably over versions. Some of the
deeper lying problems with focus are announced to be fixed in 1.5. Let's
hope it's true.

Greetings
Jeanette
 
K

Krick

Kleopatra said:
I never saw any code from you that a) checked if the focusOwner is a
child of the tab and b) if so ask its InputVerifier if it's okay to
leave.

I did post that code, I couldn't get it to work in my situation...

http://groups.google.com/[email protected]&rnum=10
because I tested it. It's misbehaving in (at least) 2 situations:

1) when the focus is on an objecting child of the selected tab and the
selection is changed via model.setSelectedIndex() the tab is changed
before popping up the dialog - that's the very problem you wanted to
solve.

You are correct, that can happen as you say. However, I'm unclear how
or when model.setSelectedIndex() would be called directly. In my code
(even programmatically) I always call JTabbedPane.setSelectedIndex().
2) when the focus is on widget _outside_ of the tabbed pane then
selecting another tab does invoke the inputVerifier of that widget.

I'm confused. Isn't that what an input verifer is supposed to do? I
thought that point of an InputVerifier is to do something when a
person tries to leave the control that "owns" it. Clicking anywhere
outside the control is "leaving" and should trigger the InputVerifier.
sorry - but that's incorrect in 2 (again: at least) details:

a) you included a wrong assumption about the solution into your problem
analysis: focus of the tab is a side-effect of your approach but not
part of the problem. In fact, what to do with focus on selection changes
depends on context.

b) you don't (or at least should not) want to invoke _any_ validators
but only those attached to the focused child of the currently selected
tab


no, I disagree: focus should not be forced unconditionally onto the tab
with every selection, f.i. if the focus is somewhere outside of the tab
then selection changes should do nothing to change the focus, at least
not if the selection change is done programmatically. What to do if the
change is the result of a mouse-click is arguable and depends on
requirements.

What it should do though is to handle the case when the focus has been
on the tab or any of its children consistently, the current
implementation is definitely buggy.

Ok, I think I see what you are saying about focus, children of the
tab, and outside widgets but I'm not sure I can see a situation where
this would apply in an acutal GUI.

I've looked around at examples of tabbed controls used in the Windows
OS and in other Windows applications (which is all I have access to at
the moment). It appears that selecting a new tab always sets focus to
one of the components within that new tab. Which would have the side
effect of triggering a focus lost with a widget outside of the tabbed
pane.

I'll continue to experiment with your original suggested solution to
see if I can get it to work in my situation. Any comments you have
that might help will be appreciated (see link to my posted code
above).


In my testing, I uncovered another bug with the focus system that's
probably related somehow. (sample code at end of post)

Here's how to produce the bug...

Open an application (Internet Explorer for example).
Compile and start my sample Java application.
Resize and position the two applications so they are both fully
visible on the screen at the same time.
Click in control #1 and input "27" but don't leave the field.
Switch to the other application (giving it focus).
Switch back to the Java application by clicking in control #2.
The focus will now be on control #2 and the control #1's input
verifier will never have been invoked.


Thank you for your insight.
....
Krick



//*******************************************************************
// BEGIN CODE
//*******************************************************************

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Application1 extends JFrame {
JPanel contentPane;
JPanel jPanel1 = new JPanel();
JPanel jPanel2 = new JPanel();
JLabel jLabel1 = new JLabel();
JLabel jLabel2 = new JLabel();
JTextField jTextField1 = new JTextField();
JTextField jTextField2 = new JTextField();
JTextField jTextField3 = new JTextField();

public Application1() {
enableEvents(AWTEvent.WINDOW_EVENT_MASK);
try { jbInit(); } catch(Exception e) { e.printStackTrace(); }
}

private void jbInit() throws Exception {
contentPane = (JPanel) this.getContentPane();
this.setSize(new Dimension(300, 200));
jLabel1.setText("Control #1");
jLabel2.setText("Control #2");
jTextField1.setPreferredSize(new Dimension(50, 21));
jTextField2.setPreferredSize(new Dimension(50, 21));
jTextField1.setInputVerifier(new InputVerifier() {
public boolean verify(JComponent comp) {
return (!"27".equals( ( (JTextField) comp).getText()));
}
public boolean shouldYieldFocus(JComponent input) {
if (!verify(input)) {
// Remove the input verifier (Workaround for bug #4532517)
input.setInputVerifier(null);
// Pop up the message dialog.
Window wind = SwingUtilities.windowForComponent(input);
String title = "Enter addional information";
JOptionPane.showMessageDialog(wind, jTextField3, title,
JOptionPane.PLAIN_MESSAGE, null);
// Reinstall the input verifier
input.setInputVerifier(this);
}
return true;
}
});
contentPane.add(jPanel1, BorderLayout.NORTH);
jPanel1.add(jLabel1, null);
jPanel1.add(jTextField1, null);
contentPane.add(jPanel2, BorderLayout.CENTER);
jPanel2.add(jLabel2, null);
jPanel2.add(jTextField2, null);
}

protected void processWindowEvent(WindowEvent e) {
super.processWindowEvent(e);
if (e.getID()==WindowEvent.WINDOW_CLOSING) { System.exit(0); }
}

public static void main(String[] args) {
JFrame frame = new Application1();
frame.setVisible(true);
}
}
 
K

Kleopatra

Krick said:
I did post that code, I couldn't get it to work in my situation...

http://groups.google.com/[email protected]&rnum=10

you are right - I forgot that one. And was too lazy to wade into your
very nested logical structure I have to admit...
You are correct, that can happen as you say. However, I'm unclear how
or when model.setSelectedIndex() would be called directly. In my code
(even programmatically) I always call JTabbedPane.setSelectedIndex().

Because that's the swing way of life :) In a well MVC designed
application there's probably some controller that does know nothing
about a view but only about selection.
I'm confused. Isn't that what an input verifer is supposed to do? I
thought that point of an InputVerifier is to do something when a
person tries to leave the control that "owns" it. Clicking anywhere
outside the control is "leaving" and should trigger the InputVerifier.

not necessarily - again you mix concerns: yes - the inputVerifier is
called automatically before ("leaving" a component) == (component has
focus AND focus will be moved elsewhere). Clicking somewhere need not
transfer the focus. In fact the system will not even try if the target
has requestFocusEnabled on false.
Ok, I think I see what you are saying about focus, children of the
tab, and outside widgets but I'm not sure I can see a situation where
this would apply in an acutal GUI.

you probably mean that you can't see it in _your_ GUI? Anyway, IMO you
have to go for correct and complete solutions - otherwise you are asking
for trouble in the future.
I've looked around at examples of tabbed controls used in the Windows
OS and in other Windows applications (which is all I have access to at
the moment). It appears that selecting a new tab always sets focus to
one of the components within that new tab. Which would have the side
effect of triggering a focus lost with a widget outside of the tabbed
pane.

Be careful when jumping to conclusions: by experimenting with the
behaviour of a native app you can only see the behaviour when the
selection is done by mouseClick or key shortcuts, not what happens if
the selection is done programmatically.

You are right: in win many of the apps seem to move the focus to either
the tab or one of its children when selecting by a user gesture. An
example where focus does not follow the click but remains on a widget
outside the tab is Eclipse (f.i. the preferences page for code
formatting).

I'll continue to experiment with your original suggested solution to
see if I can get it to work in my situation. Any comments you have
that might help will be appreciated (see link to my posted code
above).

only general advice: refactor to detangle your logic and put the
responsibilities where they belong.
Here's how to produce the bug...

you might want to report it (if it's not yet in the bug parade).

Greetings and good luck
Jeanette
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top