Change focus to next component in JTable using TAB - java

JTable's default behavior is changing focus to next cell and I want to force it to move focus to next component (e.g. JTextField) on TAB key pressed.
I overrided isCellEditable method of DefaultTableModel to always return false:
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
But it still doesn't change focus to next component!
How should I make JTable change focus to next component instead of next cell?

The shift-/tab keys are used by default for transfering focus between components. JTable explicitly requests to handle the shift-/tab internally (by providing sets of focusTraversalKeys which doesn't include those).
Following the general rule (if there's specilized api available for a task, use that instead of rolling your own), the solution is to set traversal keys to again contain them:
Set<AWTKeyStroke> forward = new HashSet<AWTKeyStroke>(
table.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
forward.add(KeyStroke.getKeyStroke("TAB"));
table.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forward);
Set<AWTKeyStroke> backward = new HashSet<AWTKeyStroke>(
table.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
backward.add(KeyStroke.getKeyStroke("shift TAB"));
table.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backward);

If you really want this, you need to change the default behavior of the tables action map.
ActionMap am = table.getActionMap();
am.put("selectPreviousColumnCell", new PreviousFocusHandler());
am.put("selectNextColumnCell", new NextFocusHandler());
Then you need a couple of actions to handle the traversal
public class PreviousFocusHandler extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
manager.focusPreviousComponent();
}
}
public class NextFocusHandler extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
manager.focusNextComponent();
}
}
Another approach would be to disable the underlying Action...
ActionMap am = table.getActionMap();
am.get("selectPreviousColumnCell").setEnabled(false);
am.get("selectNextColumnCell").setEnabled(false);
(haven't tested this)
The benefit of this approach is can enable/disable the behaviour as you need it without needing to maintain a reference to the old Actions

default (implemented KeyBinding for JTable) is about next cell and from last cell to first,
you can to remove KeyBindings by setting to the null value

To reset to the standard keyboard bindings (typically TAB and SHIFT+TAB), simply specify null for the keystrokes parameter to Component.setFocusTraversalKeys:
table.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
table.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);

Related

How do I add a tag to JMenuItem?

How can I set a tag for my menu item so that I can ue it later in the callback?
Something like this. Somebody have ever do it?
JMenuItem item = new JMenuItem(mnu.text);
item.setSomething(myTag) ???;
item.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt)
{
start_something(myTag);
}
});
You can use .setName() method for tagging it
final JMenuItem item = new JMenuItem();
item.setName("item1");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String tag = item.getName();
}
});
You can create a subclass as mentioned by Adir D but you can also add properties to the component itself and read those properties somewhere else. For a small number of properties or where a subclass doesn't fit, it might solve your problem.
See https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html
putClientProperty
public final void putClientProperty(Object key, Object value)
Adds an arbitrary key/value "client property" to this component.
The get/putClientProperty methods provide access to a small per-instance hashtable. Callers can use get/putClientProperty to annotate components that were created by another module. For example, a layout manager might store per child constraints this way. For example:
componentA.putClientProperty("to the left of", componentB);
If value is null this method will remove the property. Changes to client properties are reported with PropertyChange events. The name of the property (for the sake of PropertyChange events) is key.toString().
The clientProperty dictionary is not intended to support large scale extensions to JComponent nor should be it considered an alternative to subclassing when designing a new component.
Parameters:
key - the new client property key
value - the new client property value; if null this method will remove the property
See Also:
getClientProperty(java.lang.Object), Container.addPropertyChangeListener(java.beans.PropertyChangeListener)

How to Set JComponent to Traverse with TAB instead of CTRL+TAB without using isManagingFocus()

I have a class that is a custom swing component that extends JComponent. I am trying to make it so that the subcomponents of this custom component will be focus traversable by simply pressing TAB or SHIFT+TAB. Instead, they only seem to be traversable by pressing CTRL+TAB or CTRL+SHIFT+TAB by default no matter what I specify otherwise. I have referred to the answer from here although my component is not a JTextArea, and I have tried setting the focus traverse keys directly with the code below in the constructor, but to no avail.
setFocusCycleRoot(true);
setFocusTraversalKeysEnabled(true);
Set<AWTKeyStroke> forwardKeys = new HashSet<>();
forwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0));
setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
Set<AWTKeyStroke> backwardKeys = new HashSet<>();
backwardKeys.add(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK));
setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
I also tried to add a key listener to the component to effectively do the same thing with the code below, but that also did not work.
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_TAB) {
if (e.isShiftDown()) {
transferFocusBackward();
} else {
transferFocus();
}
e.consume();
}
}
});
I find that I get exactly the behavior I want if I do not include the either of the code blocks above, but rather override the isManagingFocus() method to always return true, but I would rather not do this as isManagingFocus() is deprecated.

How to make cells in a column un-selectable

I have a JTable in which some columns are uneditable. I do that by overriding the isCellEditable method. I now want to make the cells in these columns un-selectable. if the user is using the tab key to go through the cells, I want to focus to "skip" these uneditable cells. Can you please tell me how this is done? Thanks.
All navigation behaviour is controlled by actions registered in the table's actionMap. So the way to go is to hook into the action that is bound to the tab key, implement a wrapper that invokes that action as often as needed and replace the original action with the wrapper.
A raw code snippet for skipping not-editable cells:
Object actionKey = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.get(KeyStroke.getKeyStroke("TAB"));
final Action traverseAction = table.getActionMap().get(actionKey);
Action wrapper = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
traverseAction.actionPerformed(e);
while(shouldRepeat((JTable) e.getSource())) {
traverseAction.actionPerformed(e);
}
}
private boolean shouldRepeat(JTable source) {
int leadRow = source.getSelectionModel().getLeadSelectionIndex();
int leadColumn = source.getColumnModel().getSelectionModel().getLeadSelectionIndex();
return !source.isCellEditable(leadRow, leadColumn);
}
};
table.getActionMap().put(actionKey, wrapper);

How to add a function in jTable that sorts the column?

I know that by using JTable the column is sorted when we click on the column heading, but what I want is that, when I right-click on the column name a function name 'sort' should be displayed. Any suggestion in doing it?
Start by adding a MouseListener to the table. See How to write mouse listeners
You will need to translate the click point to a column, see JTable#columnAtPoint.
You will then need to update the SortKey for the table. Check out Sorting and Filtering for an example
If I understand you correctly, you want to sort by some explicit action (triggered f.i. in a popup) instead of by the normal left-click.
If so, the tricky part is to force the ui-delegate to do nothing. There are two options:
hook into the default mouse listener installed by the ui delegate, as described in a recent QA
let the ui do its stuff, but fool it by a sorter implementation that doesn't follow the rules (beware: that's as dirty as the first approach!)
The mis-behaving sorter:
public class MyTableRowSorter extends TableRowSorter {
public MyTableRowSorter(TableModel model) {
super(model);
}
/**
* Implemented to do nothing to fool tableHeader internals.
*/
#Override
public void toggleSortOrder(int column) {
}
/**
* The method that really toggles, called from custom code.
*
* #param column
*/
public void realToggleSortOrder(int column) {
super.toggleSortOrder(column);
}
}
// usage
final JTable table = new JXTable(new AncientSwingTeam());
table.setRowSorter(new MyTableRowSorter(table.getModel()));
Action toggle = new AbstractAction("toggleSort") {
#Override
public void actionPerformed(ActionEvent e) {
JXTableHeader header = SwingXUtilities.getAncestor(
JXTableHeader.class, (Component) e.getSource());
Point trigger = header.getPopupTriggerLocation();
int column = trigger != null ? header.columnAtPoint(trigger) : -1;
if (column < 0) return;
int modelColumn = header.getTable().convertColumnIndexToModel(column);
((MyTableRowSorter) header.getTable().getRowSorter())
.realToggleSortOrder(modelColumn);
}
};
JPopupMenu menu = new JPopupMenu();
menu.add(toggle);
table.getTableHeader().setComponentPopupMenu(menu);
Yeah, couldn't resist throwing in some SwingX api, lazy me :-) With plain Swing, you'll have to write some lines more but the basics are the same: install the tricksy sorter and use its custom toggle sort to really sort whereever needed, f.i. in a mouseListener.

Finding and retrieving a component from its container

In my swing-based UI, I have a JMenuBar which contains a a series of JMenu and JMenuItem objects. One of the menu-item objects also happens to be a JCheckBoxMenuItem.
Now, while the user can click on this JCheckBoxMenuItem in order to toggle the state of an application level setting, the user (in my application) also has access to a command line API to change the application setting. The details of this command line API are not relevant.
My question is this: When the user goes through the command line API and toggles the state of the setting (a static property / setting that applies to all open instances of my application), I would like to update the "checked / unchecked" property on the JCheckBoxMenuItem. To do this, I can either:
Store a reference to the checkboxmenuitem.
Traverse the JMenu container hierarchy to find the checkboxmenuitem.
I don't want to use method 1 because in the future, if I have more of these checkboxmenuitems, then i'll have to hang on to a reference to each one.
Method 2 seems cumbersome because I need to do:
Component[] childComponents = menu.getComponents();
for(Component c:childComponents)
{
if(c.getName().equals("nameOfTheCheckBoxMenuItem"))
{
componentFound = c;
}
}
Is there a better / more efficient way to find a component in a component hierarchy? Is there maybe a better way to solve this problem in general (changing the state of the jcheckboxmenuitem when the value of a property in my application changes), using say, a PropertyChangeListener (Although my understanding is that these only work on "beans").
1) I'd suggest to use CardLayout for nicest and easiest workaround for multi_JPanel application
2) then you can imlements
add Action / ActionListener
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (myCheckBox.isSelected()) {
// something
} else {
// something
}
}
};
add ItemListener
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
if (Whatever) {
// something
}
}
};

Categories