How can I instruct my Swing component to grab focus right now? requestFocus() doesn't seem to be dispatched instantly.
Ideally, I would like this (ran from EDT):
textInput.requestFocusInWindow();
System.out.println(textInput.hasFocus());
To print true.
Below is the SSCCE. Notes/requirements:
Table is navigated with keyboard. Column C2 has a compound editor.
When I type a letter in column C2, editing starts. Text component inside the compound editor gains focus. It needs to type the letter that started the editor. Implementation of this point is marked with comments saying "Trick".
The text field is a 3rd party editor that has a focus listener interfering with my code. Here it's simulated as selectAll().
Presently the order of dispatch is: Type letter into text component, then dispatch focus listeners. Then the next letters are dispatched correctly because it's the text field what has focus.
I need it to set focus on the text component, dispatch focus listeners, and then pass key event to it.
public class JTableIssue extends JFrame {
public JTableIssue() {
JTable table = new JTable(new Object[][] {
{ "Apple", "Orange", "Strawberry" },
{ "Pineapple", "Orange", "Zergz" } }, new Object[] { "C1",
"C2", "C3" });
table.getColumn("C2").setCellEditor(new MyEditor());
add(new JScrollPane(table));
pack();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
new JTableIssue().setVisible(true);
}
}
class MyEditor extends AbstractCellEditor implements TableCellEditor {
MyTextField textField = new MyTextField();
JPanel panel;
MyEditor() {
panel = new JPanel(new BorderLayout()){
// Trick: Pass all key typed to text field
#Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
int condition, boolean pressed) {
if (ks.getKeyEventType() == KeyEvent.KEY_TYPED) {
textField.processKeyBinding(ks, e, condition, pressed);
}
return super.processKeyBinding(ks, e, condition, pressed);
}
};
textField.addFocusListener(new FocusAdapter() {
#Override
public void focusGained(FocusEvent e) {
textField.selectAll();
}
});
panel.add(textField, BorderLayout.CENTER);
// Trick: Pass focus to text field when editor is added to table
panel.addAncestorListener(new AncestorListener() {
public void ancestorRemoved(AncestorEvent event) {
}
public void ancestorMoved(AncestorEvent event) {
}
public void ancestorAdded(AncestorEvent event) {
textField.requestFocus();
}
});
}
public Object getCellEditorValue() {
return textField.getText();
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
textField.setText(value.toString());
return panel;
}
}
class MyTextField extends JTextField {
// Trick: "public"
#Override
public boolean processKeyBinding(javax.swing.KeyStroke ks,
java.awt.event.KeyEvent e, int condition, boolean pressed) {
return super.processKeyBinding(ks, e, condition, pressed);
};
}
I figured it out.
Events in AncestorListener and processKeyBinding() are a part of handling the same event: "key typed".
Apparently the only way to grab focus is requestFocus(), which is added to event queue after the current stream of events triggered by "key typed". So grabbing focus and executing FocusListeners will always be executed later.
The solution is: In processKeyBinding(), don't pass the key to the inner component immediately. Enqueue it in event queue, so that it's performed after focus transfer and listeners. That is, wrap:
if (ks.getKeyEventType() == KeyEvent.KEY_TYPED) {
textField.processKeyBinding(ks, e, condition, pressed);
}
Into SwingUtilities.invokeLater().
I don't think that this is possible. UI actions are inherently asynchronous (although usually very fast), and there's no way to force them to behave synchronously. If you really need this, you can fire an event in the text input's focus handler, and wait on that event in the other thread.
I don't see how to control the order of events and in general that is not something you should be trying to do.
3.The text field is a 3rd party editor that has a focus listener interfering
with my code
Maybe you can remove the FocusListener from the editor. Maybe you can then invoke the listener directly by using
savedFocusListener.focusGained(FocusEvent);
in the AncestorListener code that set focus on the text field.
Related
I want to create a JDialog where the text in the textfields is selected but only if the focus is gained from keyboard (TAB, CTRL+TAB). I have found several topics on this matter but had problems with implementing it.
Here is one which I was trying.
And my code:
public class Dialogg extends JDialog implements FocusListener, MouseListener {
private boolean focusFromMouse = false;
public Dialogg() {
JTextField tf1 = new JTextField("text1");
JTextField tf2 = new JTextField("text2");
tf1.addMouseListener(this);
tf2.addMouseListener(this);
tf1.addFocusListener(this);
tf2.addFocusListener(this);
}
#Override
public void focusGained(FocusEvent e) {
if (!focusFromMouse) {
JTextField tf = (JTextField) e.getComponent();
tf.selectAll();
focusFromMouse = true;
}
}
#Override
public void focusLost(FocusEvent e) {
focusFromMouse = false;
}
#Override
public void mouseClicked(MouseEvent e) {
focusFromMouse = true;
}
}
It does not work as intended, it does not matter what is focus source the text always highlights. When I run the code and follow it step by step it turns out that focusGained code happens before mouseClicked code so the flag is not reset when it should. Any hints?
EDIT:
As suggested by M. Prokhorov I have deleted less relevant (for the question) lines from the code.Thank you.
EDIT 2:
I am trying to wrap focus listener as suggested by camickr. It looks like this now:
tf1.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent evt){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if (!focusFromMouse){
tf1.selectAll();
focusFromMouse=true;
}
}
});
}
public void focusLost(FocusEvent evt){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
focusFromMouse=false;
}
});
}
});
public void mouseClicked(MouseEvent e) {
focusFromMouse=true;
I am printing line after each event to see the action order and still mouseClicked happens last. What am I doing wrong?
EDIT 3:
OK, I have found a solution which fulfils requirements of my simple Dialog.
I could not find a way of doing this with use of invokeLater or EventQueue. Vladislav's method works but as I understand it restricts the user to only use the keyboard.
I have used the initial approach but I have added an auxiliary variable and few conditions which allow to pass the flag "unharmed" trough Events that should not change the flag at given moment. It may not be subtle or universal but works for my app. Here is the code:
public void focusGained(FocusEvent e) {
if(!focusFromMouse){
if (higlight){
JTextField tf = (JTextField) e.getComponent();
tf.selectAll();
focusFromMouse=false;
}
}
}
public void focusLost(FocusEvent e) {
if (focusFromMouse){
higlight=false;
focusFromMouse=false;
}else{
higlight=true;
}
}
public void mousePressed(MouseEvent e) {
focusFromMouse=true;
}
At the first, by default, focus on JTextField is requested by mouse-press event, not by mouse-click.
So, this method:
public void mouseClicked(MouseEvent e) {
focusFromMouse = true;
}
is useless because the mouse-click event is triggered after the mouse-press event.
One way to solve your problem is to remove all native MouseListeners from JTextField:
...
for( MouseListener ml : tf1.getMouseListeners() ){
tf1.removeMouseListener(ml);
}
for( MouseMotionListener mml : tf1.getMouseMotionListeners() ){
tf1.removeMouseMotionListener(mml);
}
...
Another way is to handle all mouse events and consume those of them, which are triggered by JTextField:
Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
if( event.getSource() == tf1 ){
((MouseEvent)event).consume();
}
}
}, AWTEvent.MOUSE_EVENT_MASK);
When I run the code and follow it step by step it turns out that focusGained code happens before mouseClicked
Wrap the code in the FocusListener in a SwingUtilities.invokeLater(). The will place the code on the end of the Event Dispatch Thread (EDT), so the code will run after the variable in the MouseListener has been set.
See Concurrency in Swing for more information about the EDT.
Edit:
Just noticed the other answer. You might be able to do something simpler. Istead of listener for mouseClicked, listen for mousePressed. A mouseClicked event is only generated AFTER the mouseReleased event, so by that time the FocusListener logic has already been executed, even when added to the end of the EDT.
Edit 2:
If the above doesn't work then you might be able to use the EventQueue.peek() method to see if a MouseEvent is on the queue. This might even be easier than worrying about using the invokeLater.
I have a Java Swing interface with multiple JTextArea's and I am implementing an "Edit" menu with various different functions like "Find", "Copy", "Paste", etc. When I click on the JMenuItem I need to know which JTextArea had the focus which is achievable through a TextAction (I haven't gone down the route of a FocusListener and keeping track of what last had the focus):
JMenuItem miFind = new JMenuItem(new EditHandler("Find"));
class EditHandler extends TextAction {
private String s = null;
public EditHandler(String vs) {
super(vs);
s = vs;
}
#Override
public void actionPerformed(ActionEvent e) {
JTextComponent c = getFocusedComponent();
if (s.equals("Find")) {
showFindDialog(c);
}
}
}
This works well and good but I want to be able to disable the "Find" JMenuItem under certain contexts (i.e. if the specific JTextArea is disabled or is empty. I can implement an ActionListener on a JMenu but I can't use getFocusedComponent() to identify what JTextArea has the focus.
According to the Java docs the JMenu constructor takes an Action (like a JMenuItem) and I have tried the following:
mEdit = new JMenu(new EditHandler("Edit"));
However, although the constructor fires, the actionPerformed() event isn't firing within my EditHandler for the JMenu. If I can get it to fire then I was planning to either enable or disable my "Find" JMenuItem.
The best way for you is using of actions map of the text component to place the corresponding action. In this case you can disable it for some text components.
#Override
public void actionPerformed(ActionEvent e) {
JTextComponent c = getFocusedComponent();
if (s.equals("Find")) {
Action a = c.getActionMap().get("Find");
if (a.isEnabled()) {
// generate new event to modify the source (menu item -> text component)
ActionEvent ae = new ActionEvent(c, e.getID(), e.getCommand());
a.actionPerformed(ae);
}
}
}
For each your text component you must provide an action and register it using the action map of the component.
public class UniversalFindAction extends AbstractAction {
public void actionPerformed(ActionEvent ae) {
JTextComponent c = (JTextComponent) ae.getSource();
showFindDialog(c);
}
}
// registering of action
JTextComponent comp = new JTextArea();
comp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK), "Find");
comp.getActionMap().put("Find", new UniversalFindAction());
Thanks to #sergiy-medvynskyy I have implemented a Global Focus Listener to keep track of the last JTextArea to be focused:
KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("permanentFocusOwner", new PropertyChangeListener() {
#Override
public void propertyChange(final PropertyChangeEvent e) {
if (e.getNewValue() instanceof JTextArea) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
tFocused = (JTextArea)e.getNewValue();
}
});
}
}
});
I then check the tFocused object using a MenuListener on my JMenu to verify what JTextArea currently has the focus. I can then call setEnabled() on my respective JMenuItem's depending on the context.
Is it possible that when I clicked the textfield it would clear the recent text that was inputed there?. Mine was like, suppose these are textfields.
Name: Last Name First Name Middle Initial
Then I would click the Last Name and it would be cleared, same as First Name and Middle Initial. thanks for reading, hope you can help me.
Consider a FocusListener, one where all the text is selected:
myTextField.addFocusListener(new FocusAdapter() {
public void focusGained(FocusEvent fEvt) {
JTextField tField = (JTextField)fEvt.getSource();
tField.selectAll();
}
});
By selecting all of the text, you give the user the option of either typing and thus deleting the current text and replacing it with the new text, or using the mouse or arrow keys to keep the current text and possibly change it.
I think Hovercraft is right. Better to use a FocusListener for this purpose.
I would write a utility class that could deal with this, I've done something similar for auto select. Means I don't have to extend every text component that comes along or mess around with lost of small focus listeners that do the same thing.
public class AutoClearOnFocusManager extends FocusAdapter {
private static final AutoClearOnFocusManager SHARED_INSTANCE = new AutoClearOnFocusManager();
private AutoClearOnFocusManager() {
}
public static AutoClearOnFocusManager getInstance() {
return SHARED_INSTANCE;
}
#Override
public void focusGained(FocusEvent e) {
Component component = e.getComponent();
if (component instanceof JTextComponent) {
((JTextComponent)component).setText(null);
}
}
public static void install(JTextComponent comp) {
comp.addFocusListener(getInstance());
}
public static void uninstall(JTextComponent comp) {
comp.removeFocusListener(getInstance());
}
}
Then you just need to use
JTextField textField = new JTextField("Some text");
AutoClearOnFocusManager.install(textField);
If you're just looking to supply a "prompt" (text inside the field that prompts the user), you could also look at the Prompt API
Why don't use the mouseClicked event?
So, you can have something like
jTextFieldMyText.addMouseListener(new java.awt.event.MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent evt) {
jTextFieldMyTextMouseClicked(evt);
}
});
private void jTextFieldMyTextMouseClicked(java.awt.event.MouseEvent evt) {
jTextFieldMyText.setText("");
}
In the case of focus
jTextFieldMyText.addFocusListener(new java.awt.event.FocusAdapter() {
public void focusGained(java.awt.event.FocusEvent evt) {
jTextFieldMyTextFocusGained(evt);
}
});
private void jTextFieldMyTextFocusGained(java.awt.event.MouseEvent evt) {
jTextFieldMyText.setText("");
}
If deleting text inmediatelly isn't what's wanted, use selectAll() instead of setText("") as suggested many times
I need your suggestions and guidence on following task.
I have a frame which has two JComboBoxes supposed they are named combo1 and combo2, a JTable and other components.
At initial stage when frame is visible with above component. The combo1 combobox is filled with some values but no value is selected at initial stage, the combo2 combobox is disabled and the table is empty.
I have added an actionListener on combo1 as well as combo2. There are two types of values in combo1 suppose those values are type1 and type2.
Condition 1:
When we selects value type1 from Combo1 the actionListener method is called of combo1 which invokes a method which combo2 remains disabled and adds some rows to table related to selected value type1 from combo1.
Condition 2:
when we selects value type2 from combo1 the actionListener method is called of combo1 which invokes a method who makes combo2 filled with some values related to type2 and gets enabled but no value is selected from combo2 and table also should remain empty until we selects any value from combo2.
table at every addition of value to combo2 the action listener method of combo2 is gets fired. In actionListener method of combo2 which gets combo2 selected value but here there is no selected value of combo2 which leads to a NullPointerException.
So what should I do that the action listner method of combo2 will not be get executed after addition of an values to combo2.
You could remove the action listener before you add the new elements, and add it back once you're done . Swing is single threaded so there is no need to worry about other threads needing to fire the listener.
Your listener could probably also check if something is selected and take appropriate action if not. Better than getting a NPE.
What i do instead of adding and removing action listeners i have a boolean variable in my action listeners that is true if it has to allow the action through or false if it has to block it.
I then set it to false when i do some changes that will fire off the action listener
JComboBox test = new JComboBox();
test.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
if(testActionListenerActive)
{
//runn your stuff here
}
}
});
//then when i want to update something where i want to ignore all action evetns:
testActionListenerActive = false;
//do stuff here like add
SwingUtilities.invokeLater(() -> testActionListenerActive = false);
//and now it is back enabled again
//The reason behind the invoke later is so that if any event was popped onto the awt queue
//it will not be processed and only events that where inserted after the enable
//event will get processed.
try this:
indicatorComboBox = new JComboBox() {
/**
* Do not fire if set by program.
*/
protected void fireActionEvent() {
// if the mouse made the selection -> the comboBox has focus
if(this.hasFocus())
super.fireActionEvent();
}
};
although its late, a better alternative would be to disabled the combobox to be modified prior to being modified. by doing so, you prevent firing events of the modified combobox, when for example, you use methods likes removeAllItems() or addItem()
String orderByOptions[] = {"smallest","highest","longest"};
JComboBox<String> jcomboBox_orderByOption1 = new JComboBox<String(orderByOptions);
JComboBox<String> jcomboBox_orderByOption2 = new JComboBox<String(orderByOptions);
JComboBox<String> jcomboBox_orderByOption3 = new JComboBox<String(orderByOptions);
jcomboBox_orderByOption1.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent itemEvent)
{
int eventID = itemEvent.getStateChange();
if (eventID == ItemEvent.SELECTED)
{
Object selectedItem = jcomboBox_orderByOption1.getSelectedItem();
jcomboBox_orderByOption2.setEnabled(false);
jcomboBox_orderByOption2.removeAllItems();
for (String item: string_orderByOptions)
{
if (!item.equals(selectedItem))
{
jcomboBox_orderByOption2.addItem(item);
}
}
jcomboBox_orderByOption2.setEnabled(true);
}
}
});
jcomboBox_orderByOption2.addItemListener(new ItemListener()
{
public void itemStateChanged(ItemEvent itemEvent)
{
int eventID = itemEvent.getStateChange();
if (eventID == ItemEvent.SELECTED)
{
Object selectedItem1 = jcomboBox_orderByOption1.getSelectedItem();
Object selectedItem2 = jcomboBox_orderByOption2.getSelectedItem();
jcomboBox_orderByOption3.setEnabled(false);
jcomboBox_orderByOption3.removeAllItems();
for (String item: string_orderByOptions)
{
if (!item.equals(selectedItem1) && !item.equals(selectedItem2))
{
jcomboBox_orderByOption3.addItem(item);
}
}
jcomboBox_orderByOption3.setEnabled(true);
}
}
});
The cleaner way is to use lambda expressions like this:
do(comboBox, () -> comboBox.setSelectedItem("Item Name"));
For the above to work, you need the following method defined somewhere:
public static void do(final JComboBox<String> component, final Runnable f) {
final ActionListener[] actionListeners = component.getActionListeners();
for (final ActionListener listener : actionListeners)
component.removeActionListener(listener);
try {
f.run();
} finally {
for (final ActionListener listener : actionListeners)
component.addActionListener(listener);
}
}
This works:
/** Implements a Combo Box with special setters to set selected item or
* index without firing action listener. */
public class MyComboBox extends JComboBox {
/** Constructs a ComboBox for the given array of items. */
public MyComboBox(String[] items) {
super(items);
}
/** Flag indicating that item was set by program. */
private boolean isSetByProgram;
/** Do not fire if set by program. */
protected void fireActionEvent() {
if (isSetByProgram)
return;
super.fireActionEvent();
}
/** Sets selected Object item without firing Action Event. */
public void setSelection(Object item) {
isSetByProgram = true;
setSelectedItem(item);
isSetByProgram = false;
}
/** Sets selected index without firing Action Event. */
public void setSelection(int index) {
isSetByProgram = true;
setSelectedIndex(index);
isSetByProgram = false;
}
}
Note: You can't just override setSelectedItem(...) or setSelectedIndex(...) because these are also used internally when items are actually selected by user keyboard or mouse actions, when you do not want to inhibit firing the listeners.
To determine whether or not to perform various methods in actionListener interface methods (actionPerformed() blocks of code) use setActionCommand() on source components (combo1 or combo2).
For your example, before adding elements to combo2, call setActionCommand("doNothing") and guard your comboBoxActionPerformed() method.
Here's a compilable example that uses this principle to have one combo set another combo's selected index while also displaying a String in a JTextField. By using setActionCommand() and guarding the comboActionPerformed() block of code, the JTextField will cycle through each word in the wordBank. If the comboActionPerformed() method was not guarded or if the actionCommand String was not changed, 2 actionEvents will trigger and the textField will skip words.
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
/** #author PianoKiddo */
public class CoolCombos extends JPanel {
JComboBox<String> candyCombo;
JComboBox<String> flavorCombo;
JTextField field;
String[] wordBank;
int i = 0;
CoolCombos() {
super();
initComponents();
addComponentsToPanel();
}
private void initComponents() {
initCombos();
initTextField();
}
private void initCombos() {
ActionListener comboListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
comboActionPerformed(e);
}
};
String[] candyList = {"Sourpatch", "Skittles"};
String[] flavorList = {"Watermelon", "Original"};
candyCombo = new JComboBox<>(candyList);
candyCombo.addActionListener(comboListener);
flavorCombo = new JComboBox<>(flavorList);
flavorCombo.addActionListener(comboListener);
}
private void initTextField() {
wordBank = new String[]{"Which", "Do", "You", "Like", "Better?"};
field = new JTextField("xxxxx");
field.setEditable(false);
field.setText(wordBank[i]);
}
private void addComponentsToPanel() {
this.setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
this.add(candyCombo);
this.add(flavorCombo);
this.add(field);
}
public void comboActionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (!command.equals("doNothing")) {
JComboBox combo = (JComboBox) e.getSource();
if (combo.equals(candyCombo)) {
setOtherComboIndex(candyCombo, flavorCombo); }
else {
setOtherComboIndex(flavorCombo, candyCombo); }
displayText(); //replace here for toDo() code
}
}
private void setOtherComboIndex(JComboBox combo, JComboBox otherCombo) {
String command = otherCombo.getActionCommand();
otherCombo.setActionCommand("doNothing"); //comment this line to skip words.
otherCombo.setSelectedIndex(combo.getSelectedIndex());
otherCombo.setActionCommand(command);
}
private void displayText() {
i++;
String word;
if (i > 4) { i = 0; }
word = wordBank[i];
field.setText(word);
this.repaint();
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("CoolCombos");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
JComponent newContentPane = new CoolCombos();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setMinimumSize(frame.getSize());
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
I kind of went the stupid simple route with this issue for my program since I am new to programming.
I changed the action listeners to have a counter if statement:
if(stopActionlistenersFromFiringOnLoad != 0){//action performed ;}
Then at the end of the java program creation, I added 1 to the counter:
topActionlistenersFromFiringOnLoad += 1;
To avoid that addItem method fire events is better to use an DefaultComboBoxModel in the JComboBox to add data. Also, if you invoke a model.addElement(), an event is fired, so, you can add all the elements to the model and later use JComboBox.setModel(model). In this way, if you add elements to the model, events are not fired because you have not link the JComboBox with the model. Then, I show you an example.
private void rellenarArrendatarioComboBox(ArrayList<Arrendatario> arrendatarios) {
DefaultComboBoxModel model = new DefaultComboBoxModel();
model.addElement(new Arrendatario(" -- Seleccione un arrendatario --"));
for (Arrendatario arrendatario : arrendatarios) {
model.addElement(arrendatario);
}
ArrendatarioComboBox.setModel(model);
}
First, we create the model, add all elements to the model (events are not fired because you have not link the JComboBox with the model), we link the model with the JComboBox using ArrendatarioComboBox.setModel(model). After linking, events are fired.
I have many custom editors for a JTable and it's an understatement to say that the usability, particularly in regard to editing with the keyboard, is lacking.
The main reason for this is that my editors are always created with a similar (though often more complex) situation to this:
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.add(field, BorderLayout.CENTER);
field.setText((String) value);
container.add(new JButton("..."), BorderLayout.EAST);
return container;
}
I.E a panel with more than one component inside. The actual text editor is a descendant of the component being returned as the editor.
So, rendering issues aside, from what I can tell, the JTable is focusing the component that is returned by the getTableCellEditorComponent method so when you press a key with a cell highlighted it passes focus and the key press to the panel, thinking that's the editor.
Is there anyway I can inform JTable that the "real" editor is the JTextfield?
Adding a hacky requestFocusInWindow on the correct component is insufficient as the key press won't get passed on.
If I read your question correctly, you want the user to be able to type into a cell immediately, without activating the cell editor first, i.e., you want whatever keystroke activated the cell to be the first character entered into the text field.
My first attempt was to add a propertyChangeListener on the focusOwner property of the KeyboardFocusManager, only to notice that the focus never leaves the JTable. You probably ran into that as well. Time for plan B.
I got that "first keypress" thing to work by adding a KeyListener to the table that records the last KeyEvent for the keyPressed() method in an instance field. The getTableCellEditorComponent() method reads the character from there. I also needed that hacky requestFocusInWindow() call you mention if the user is to keep typing any characters after the first one.
For my sample app, I created a subclass of JTable that adds a KeyListener to itself. It's a much better idea to make your CellEditor instance implement KeyListener and add that to the regular JTable instead, but I'll leave that to you.
Here's your code snippet as I modified it:
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
JPanel container = new JPanel();
container.setLayout(new BorderLayout());
container.add(field, BorderLayout.CENTER);
// Will want to add an instanceof check as well as a check on Character.isLetterOrDigit(char).
char keypressed = ((StickyKeypressTable)table).getLastKeyPressed();
field.setText(String.valueOf(keypressed));
container.add(new JButton("..."), BorderLayout.EAST);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
// This needs to be in an invokeLater() to work properly
field.requestFocusInWindow();
}
});
return container;
}
As far as nastiness goes this sits somewhere up there with Vogon Poetry, but it should solve your immediate problem.
I fixed something similar in 2 steps
First override the editCellAt from your JTable and call requestFocus after preparing the editor:
public boolean editCellAt( int row, int column, EventObject e )
{
if ( cellEditor != null && !cellEditor.stopCellEditing() )
{
return false;
}
if ( row < 0 || row >= getRowCount() ||
column < 0 || column >= getColumnCount() )
{
return false;
}
if ( !isCellEditable(row, column) )
return false;
TableCellEditor editor=getCellEditor(row, column);
if ( editor != null && editor.isCellEditable(e) )
{
editorComp=prepareEditor(editor, row, column);
if ( editorComp == null )
{
removeEditor();
return false;
}
//aangepast
Rectangle rect=getCellRect(row, column, false);
if ( datamodel_.useAdaptedEditorRect() )
rect=datamodel_.changeRectangle(rect, editorComp);
editorComp.setBounds(rect);
add(editorComp);
editorComp.validate();
setCellEditor(editor);
setEditingRow(row);
setEditingColumn(column);
editor.addCellEditorListener(this);
//NEXT LINE ADDED
editorComp.requestFocus();
return true;
}
return false;
}
Then overload the requestFocus from your JPanel and make sure your textfield is put as editorComponent:
public class EditorPanel extends JPanel {
JComponent editorComponent;
public boolean isRequestFocusEnabled()
{
return true;
}
public void requestFocus()
{
editorComponent.requestFocus();
}
}
You can always grab the keyEvent and set it yourself:
AWTEvent event = EventQueue.getCurrentEvent();
if ( event instanceof KeyEvent )
{
char newSelection = ( (KeyEvent) event).getKeyChar();
int keyCode = ( (KeyEvent) event ).getKeyCode();
editorComponent.requestFocus();
if ( editorComponent instanceof JTextField )
{
if ( ( newSelection >= (char) FIRST_ALLOWED_CHAR ) && ( newSelection != (char) LAST_ALLOWED_CHAR ) ) //comes from DefaultKeyTypedAction
( (JTextField) editorComponent ).setText(Character.toString(newSelection));
if ( keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE )
( (JTextField) editorComponent ).setText("");
}
}
else
editorComponent.requestFocus();
I think that I solved it.
To tell you the truth, I don't know what solved the problem, since I'm using a custom editor, a custom renderer and stuff...
When a cell is highlighted and I press "abc", the 3 letters go on screen (cell, in this case).
grid.addKeyListener(new KeyAdapter() {
public void keyTyped(KeyEvent ke) {
int l = grid.getSelectedRow();
int c = grid.getSelectedColumn();
grid.editCellAt(l, c);
}
});
Well... I tried... =)
(I don't know if it's the same because my JTable uses JTextField and JComboBox as editors).
I had very similar problem. In my case I had complex TableCellEditor which consists of JSpinner and some other components. The problem was that when cell editor started I wanted to transfer focus to its internal component. I fixed this by calling panel.transferFocusDownCycle() but this in turn caused keyboard events to stop working - when my internal component had focus and I pressed key up, I was expecting that component will intercept this event and change its value. Instead table changed row focus to one above... I fixed this by adding KeyListener and dispatching all key events directly to the internal component.
This is wrapper class based on JPanel I wrote to make my life easier.
public class ContainerPanel extends JPanel implements KeyListener, FocusListener {
private JComponent component = null;
public ContainerPanel(JComponent component) {
this.component = component;
addKeyListener(this);
addFocusListener(this);
setFocusCycleRoot(true);
setFocusTraversalPolicy(new ContainerOrderFocusTraversalPolicy());
add(component);
}
#Override
public void keyTyped(KeyEvent e) {
component.dispatchEvent(e);
}
#Override
public void keyPressed(KeyEvent e) {
component.dispatchEvent(e);
}
#Override
public void keyReleased(KeyEvent e) {
component.dispatchEvent(e);
}
#Override
public void focusGained(FocusEvent e) {
component.transferFocusDownCycle();
}
#Override
public void focusLost(FocusEvent e) {
}
}