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.
Related
I have started implementing JCEF in a project of mine, and I am initializing the embedded browser in a JInternalFrame inside of a JFrame, alongside a series of form fields on a JPanel next to the JInternalFrame. The browser component doesn't fully initialize until the JFrame actually becomes visible, and I'm finding that my JTextFields are uneditable unless the JFrame loses and regains focus.
Any idea of what could be happening and how to fix it? This only happens when using a JInternalFrame with the JCEF component...
It also happens every time I call loadURL to load a new page in the browser: the JTextFields become uneditable again, until I lose/gain focus in the JFrame.
UPDATE:
I have found a hack which allows the JTextFields to become editable again, but I wouldn't call it a solution because it is not very elegant. I added a load handler to the CefClient instance ( client.addLoadHandler(new CefLoadHandlerAdapter()) ) with an #Ovveride on the onLoadingStateChange method, which in turn gives access to the current browser component. From there I can detect when loading in the browser is complete, and use SwingUtilities to get the Window that the browser component is in. Then I setVisible(false) and setVisible(true) on that Window. I say it's not a solution because every time the browser is done loading the Window disappears and reappears. Even though the JTextFields are editable again, it is quite ugly to see the window flashing. I've tried all kinds of revalidate() and repaint() methods to no avail, unless I didn't call them right...
client.addLoadHandler(new CefLoadHandlerAdapter() {
#Override
public void onLoadingStateChange(CefBrowser browser, boolean isLoading,
boolean canGoBack, boolean canGoForward) {
if (!isLoading) {
//browser_ready = true;
System.out.println("Browser has finished loading!");
SwingUtilities.windowForComponent( browser.getUIComponent() ).setVisible(false);
SwingUtilities.windowForComponent( browser.getUIComponent() ).setVisible(true);
}
}
});
If anyone can suggest a better solution, please do!
I figured out the problem by studying the sample JCEF application a little better. I need to implement a FocusHandler in order to release the embedded browser's hold on keyboard input:
private boolean browserFocus_ = true;
---
jTextField1.addFocusListener(new FocusAdapter() {
#Override
public void focusGained(FocusEvent e) {
if (!browserFocus_) return;
browserFocus_ = false;
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
jTextField1.requestFocus();
}
});
I am looking to figure out how to set the text of a label on an external Application Window.
What I have:
I have two windows so far. The first one is the main application window that will appear when the user starts the program. The second window is another separate window that I have created specifically to display a custom error window.
The problem: I seem to be unable to call the label that I have created on the error window and set the text to something custom. Why? I want to be able to reuse this window many times! This window is aimed for things like error handling when there is invalid input or if the application cannot read/save to a file.
I was going to post screen shots but you need 10 rep for that. It would have explained everything better.
Here is the code for the label on the Error_dialog window:
Label Error_label = new Label(container, SWT.NONE);
Error_label.setBounds(10, 10, 348, 13);
Error_label.setText("Label I actively want to change!");
Here is the condition I would like to fire off when it is met:
if(AvailableSpaces == 10){
//Set the label text HERE and then open the window!
showError.open();
}
I have included this at the top of the class as well:
Error_dialog showError = new Error_dialog();
Just save the label as a field in your dialog class and add a 'setter' method. Something like:
public class ErrorDialog extends Dialog
{
private Label errorLabel;
... other code
public void setText(String text)
{
if (errorLabel != null && !errorLabel.isDisposed()) {
errorLabel.setText(text);
}
}
You will need to use your dialog like this:
ErrorDialog dialog = new ErrorDialog(shell);
dialog.create(); // Creates the controls
dialog.setText("Error message");
dialog.open();
Note: you should stick to the rules for Java variable names - they always start with lower case.
Further learn to use Layouts. Using setBounds will cause problems if the user is using different fonts.
All of the examples for key listeners I have been able to find deal with components other than the main panel such as a text box or Menu.
I know how to use setMnemonic to program Menu hotkeys but this method does not seem to be available and the link to the oracle keylistener tutorial is broken.
When I do a Right Click > Events > Key > KeyPressed on the main form I get the following but none of keys cause mainPanelKeyPressed.
What is the correct way to use the key events to trigger an action independent of the focus?
mainPanel.addKeyListener(new java.awt.event.KeyAdapter() {
public void keyPressed(java.awt.event.KeyEvent evt) {
mainPanelKeyPressed(evt);
}
});
private void mainPanelKeyPressed(java.awt.event.KeyEvent evt) {
// Added to help find the ID of each 'arrow' key
JOptionPane.showMessageDialog(null, "mainPanelKeyPressed");
}
What is the correct way to use the key events to trigger an action independent of the focus?
See: How to Use Key Bindings
Or use a JMenuBar with menus and menu items.
the focus is important. you may need to click around and experiment, and use component.requestFocusInWindow() to help.
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 );
}
};
I've found several pages and SO answers about the enter-as-tab problem in Java, but all propose either overriding methods of JTextField or adding a key listener to every component.
But isn't there any other way? Can't I override something of the LookAndFeel or install some global policy?
After some documentation crawling I found a solution: It is possible to set the focus traversal keys on KeyboardFocusManager instead of a JComponent instance.
// 1. Get default keys
Set<AWTKeyStroke> ftk = new HashSet<AWTKeyStroke>(
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.getDefaultFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
// 2. Add our key
ftk.add(KeyStroke.getKeyStroke("ENTER"));
// 3. Set new keys
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.setDefaultFocusTraversalKeys(
KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, ftk);
This adds the enter key to the list of keys which are used for forward traversal. (Backward traversal similar)
you can probably use http://java.sun.com/products/jfc/tsc/special_report/kestrel/keybindings.html
to change the keyBinding for the enter key
or you can add focustravesal keys
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, your keys here);
The hammer - of setting the enter as focus traversal key for all component except those which register their own - is just fine if it's really required. The obvious drawback is that default bindings to the enter stop working, in particular
action/Listeners on textFields
default buttons
any other component type with a custom binding to enter
If those side-effects are problematic, there's the less intrusive alternative of tweaking the binding in the shared ancestor actionMap of the textFields.
// "early" in the app instantiate a textField
JTextField text = new JTextField();
ActionMap map = text.getActionMap();
// get a reference to the default binding
final Action notify = map.get(JTextField.notifyAction);
while (map.getParent() != null) {
// walk up the parent chain to reach the top-most shared ancestor
map = map.getParent();
}
// custom notify action
TextAction tab = new TextAction(JTextField.notifyAction) {
#Override
public void actionPerformed(ActionEvent e) {
// delegate to default if enabled
if (notify.isEnabled()) {
notify.actionPerformed(e);
}
// trigger a focus transfer
getTextComponent(e).transferFocus();
}
};
// replace default with augmented custom action
map.put(JTextField.notifyAction, tab);
After replacing the default, all textFields will use the custom action. The one beware is that the replacement has to be repeated whenever the LAF is changed.