I have a problem with the focus traversal system in Java. When I tab between components in a pane in my application everything works fine Tab moves the focus to the next component.
Some of my components perform validation on loss of focus, if the validation returns errors then the screens save button is disabled.
My problem occurs when the validated component is followed by the save button.
Tab removes focus from the validated component and begins the asynchronous process of assigning focus to the next component that is enabled (The Save Button)
Next my validation kicks in and disables the save button
The asynchronous process then finished and attempts to assign focus to the now disabled Save button.
The Focus now becomes trapped and tabbing no longer shifts focus because no component actually has the focus.
Has anyone else come across this problem, how did you solve the problem of having the validation and disablement carried out before the focus traversal event started?
You could use an InputVerifier to validate the text field. In this case focus will be placed back on the text field in error.
Or you could change your focus listener to handle this situation. Something like:
FocusListener fl = new FocusAdapter()
{
public void focusLost(final FocusEvent e)
{
JTextField tf = (JTextField)e.getSource();
if (tf.getDocument().getLength() < 1)
{
System.out.println("Error");
button.setEnabled( false );
Component c = e.getOppositeComponent();
if (c instanceof JButton
&& c.isEnabled() == false)
{
tf.requestFocusInWindow();
}
}
else
button.setEnabled( true );
}
};
Related
I have a Form with various textboxes(say around 10) .After the user fills value in each textbox, it is validated on focuslost event for the textbox.
public void focusLost(FocusEvent e)
{
JTextField tf = (JTextField)(e.getSource());
String finalVal = tf.getText();
try
{
validate(finalVal);
}
catch(NmfException ex)
{
JOptionPane.showMessageDialog(parent, message, title,
JOptionPane.ERROR_MESSAGE);//Error Message is passed
/* Error pop up is displayed when validation fails. Message text with an 'Ok' button is displayed and the code waits for ok to be clicked to execute rest of the code*/
tf.setText(defaultVal);//Value is reset to default value
return;
}
}
The form has a 'Add' button which gets the values from the UI(from the textbox) and sends it to the server.Ideally, since the values are validated at each textfield the value sent to the server should be valid inputs.
But my issue is, when an invalid input is given to a textfield(say -5 an invalid input) and 'Add' button is clicked at once.
The focusLost event is triggered and the pop up is obtained,while the code waits for the 'OK' button in pop up to be pressed,the next event of button clicked is also called.So before the defaultVal can be set as textfield value,the Add button operation is done(there is no further validation in add operation) and invalid inputs are sent to the server.
How can ensure that Add operation is called only after the focusLost event operation is done.Please suggest a fix for the issue? What would be a best practice for such a scenario?
Set one Flag which should be check while click on 'Add'.
So if all validation should be true/OK then send to server.
if flag is false/invalid, while click on 'Add' then give user prompt
with error message.
As per your scenario if any one try to add invalid value then
focusLost event makes Flag -> false, and vice-versa.
Likewise need to design architecture of coding.
You could also use a mouse listener on the text fields, and validate in the mouseExited method
I have an JDialog containing a JEditorPane for showing non-user-editable HTML content, such as Help and Release Notes.
The JDialog has a "Close" button that is installed as the default button.
If the JEditorPane is left "focusable", then the Page Up/Down keys will scroll through the document, but pressing "Enter" does not fire the default button.
On the other hand, if the JEditorPane is set non-focusable, then Page Up/Down keys do not work, but pressing the "Enter" key does fire the default button, closing the dialog.
#SuppressWarnings("serial")
public class NoteViewer extends JFrame {
public static void main(String[] args) throws IOException {
final NoteViewer viewer = new NoteViewer(new URL("http://example.com/"));
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
viewer.setVisible(true);
}
});
}
public NoteViewer(URL url) throws IOException {
super("Note Viewer");
setSize(900, 200);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JEditorPane notes = new JEditorPane(url);
notes.setEditable(false);
// notes.setFocusable(false);
getContentPane().add(new JScrollPane(notes), BorderLayout.CENTER);
JButton close = new JButton("Close");
close.addActionListener(EventHandler.create(ActionListener.class, this, "dispose"));
Box box = Box.createHorizontalBox();
box.add(Box.createHorizontalGlue());
box.add(close);
getContentPane().add(box, BorderLayout.PAGE_END);
getRootPane().setDefaultButton(close);
}
}
Uncomment the notes.setFocusable(false) line to see the different behavior.
I would like the JEditorPane to process the navigation keystrokes (such as Page Up/Down), but ignore (not consume) the "editing" keystrokes, such as Enter, so that the Root Pane will invoke the default button.
After much hacking and single-stepping, I've got the behaviour I'm looking for with this code:
notes.getInputMap().put(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0),
"pressed");
but I am concerned that it is rather fragile. Do all platforms use VK_ENTER to invoke the default button? Do all platforms use "pressed" as the command-string to invoke the default button?
Finally, it is going about it the wrong way: instead of the JEditorPane ignoring the Enter key and letting the processing happen in an ancestor, this is the JEditorPane explicitly handling the enter key, which strikes me as wrong.
A non-editable JEditorPane should not capture all the editing keystrokes (A-Z, 0-9, Enter, Delete, etc.) and transform them into a warning beep, but rather leave them unhandled so that parent components get a chance. How can this be achieved in a general, non keystroke-by-keystroke input map fashion?
Do all platforms use "pressed" as the command-string to invoke the default button?
That is not what is happening.
You are mapping the Enter key to the "pressed" Action of the JEditorPane. However, there is no "pressed" Action so the Binding is ignored and the event is passed up to the root pane where the Enter key binding for the default button is used.
Normally "none" is used to indicate you want to ignore/remove the binding. Check out the section from the Swing tutorial on How to Remove Bindings for more information.
So I would say you solution is correct and should work on all platforms.
You may want to check out Key Bindings for a programs that displays all the key bindings for each component. You will see that there is no "pressed" action. JEditorPane actually uses "insert-break" to map to the Action.
A non-editable JEditorPane should not capture all the editing keystrokes (A-Z, 0-9, Enter, Delete, etc.) and transform them into a warning beep,
I don't have a "beeping" problem with a-z, 0-9. I do have a problem with the delete and backspace keys.
I'm using JDK8_45 on Windows 7.
Maybe you can prevent the dispatching of keys by using a KeyEventDispatcher. Maybe you check the source of the KeyEvent and if the source is the editor pane you only let the Enter key through? Might also need to allow the PageUp/PageDown events so scrolling will work.
Check out Global Event Dispatching for more information.
I think I just found a better way: set the JEditorPane as not editable and not focusable, and the JScrollPane as focusable.
JEditorPane notes = new JEditorPane(url);
notes.setEditable(false);
notes.setFocusable(false);
JScrollPane scroller = new JScrollPane(notes);
scroller.setFocusable(true);
getContentPane().add(scroller, BorderLayout.CENTER);
The Enter key is forwarded to the default button. Backspace, delete and friends don't generate any beeps.
Edit:
Doesn't allow selecting and copying of any text, so perhaps not the best solution.
I want to adjust my JTable such that when I start editing a cell and then select a range of cells, the cell editor doesn't disappear, but instead changes it's value to the "current selection".
I already have a custom ListSelectionListener that listens for changes in the selections and creates an Range object from that which can be converted to a String (eg. A1:C3), but I'm looking for a way to hook in at the moment where the cell editor is being stopped / the selection event starts, so that I can prevent the default behaviour and stay in editing mode while selecting cells. Does any one have an idea?
Edit:
I have come a little bit further. When throwing an error in the removeEditor() method, I get the following stack trace:
at GUI.STable.removeEditor(STable.java:71)
at javax.swing.JTable.editingStopped(JTable.java:4724)
at javax.swing.AbstractCellEditor.fireEditingStopped(AbstractCellEditor.java:141)
at javax.swing.DefaultCellEditor$EditorDelegate.stopCellEditing(DefaultCellEditor.java:368)
at javax.swing.DefaultCellEditor.stopCellEditing(DefaultCellEditor.java:233)
at GUI.STable$CustomTableCellEditor.stopCellEditing(STable.java:119)
at javax.swing.plaf.basic.BasicTableUI$Handler.mousePressed(BasicTableUI.java:1010)
This leads to the mousePressed event in BasicTableUI, which indeed closes the current editor:
if (table.isEditing() && !table.getCellEditor().stopCellEditing()) {
Component editorComponent = table.getEditorComponent();
if (editorComponent != null && !editorComponent.hasFocus()) {
SwingUtilities2.compositeRequestFocus(editorComponent);
}
return;
}
But how can I override this mousePressed handler in BasicTableUI?
I'm building a chat program. The user has the option to press a JButton SEND or just press ENTER on the keyboard to send the message. This is my code.
private void chatTextAreaKeyPressed(java.awt.event.KeyEvent evt) {
if(evt.getKeyCode() == KeyEvent.VK_ENTER) {
this.sendButtonActionPerformed(null);
this.chatTextArea.setText(null); // Clear JTextBox
}
}
The problem with this, is that after pressing ENTER, it sets the JTextBox with a empty new line. So that whatever I type next will always be on the second line instead of starting with an empty text box.
Anyone has any ideas? Much appreciated.
You need to consume the event with evt.consume() to ensure it isn't processed by the text field itself.
This indicates that all processing of the event has finished and no other listeners should act upon the event.
I've been creating a custom TabFolder extension that adds a key listener to allow quick tab switching using an ALT + # hotkey.
By adding the KeyAdapter to my TabFolder, the event handler works properly only when you have a tab header selected (in which case the ALT + ARROW_LEFT/ARROW_RIGHT also work.). I need this hot key to be active when any Widget with-in the TabFolder is active; however, it shouldn't be active if the selection is in a different tab folder or widget outside of a tab folder.
In an attempt to solve this, I wrote a simple recursive function to apply the key listener to all of the children of the tab folder:
public void applyQuickSwitchKeyBindings() {
removeKeyListener(ka);
addKeyListener(ka);
for(Control c: getChildren())
applyQuickSwitchKeyBindingsToChildren(c);
}
private void applyQuickSwitchKeyBindingsToChildren(Control c) {
if(c==null) return;
if(c instanceof Composite) {
Control[] controls = ((Composite)c).getChildren();
for(Control c2: controls)
applyQuickSwitchKeyBindingsToChildren(c2);
if(controls.length < 1) {
c.removeKeyListener(ka);
c.addKeyListener(ka);
}
}
}
Then i call the applyQuickSwitchKeyBindings() after I add the controls to each TabItem in the tab group.
The good news was that the quick switch hot key (ALT + #) worked great!
The bad news was that the original TAB ordering based on z-index is now gone. When you hit the SWT.TAB key you lose focus on your current text box and don't gain focus on anything else...
Questions:
1.) Can each control only have one KeyListener?
2.) Why is the original TAB traversal not working anymore?
Thanks in advance!
to 1) I'm pretty sure that more than one KeyListener is allowed.
to 2) I'm not sure, that depends on what you're doing in your KeyAdapter. Maybe you can post that too?
I just the tab order is broken somehow, you can reset ( or change ) it with a call to setTabList( Control[] ).
setTablList( new Control[] {
control1,
control2,
control3,
....
} );
So after more time learning and developing with SWT i've discovered my problem. When you add a listener it is applied to the widget/control you call the addXXXListener function on. So if that control is not active the listeners will not be fired.
The solution seems to be SWT's global Filter mechanism which allows you to add global application(Display) scope listeners.
Display.getCurrent().addFilter(SWT.keyPress, new KeyPressListener());
Pardon the incorrectness of this line, but if you google it you'll see what i mean.
I have also read to use this sparingly.