0
votes

In an app that's mainly a set of JTables and JTextFields, I tried to make it accessibility with keyboard as well as mouse. My thoughts are on the best way to help the user to navigate around between components using the VK_TAB key.

My first goal was to stop JTables "swallow" the VK_TAB key when the user tries to navigate to a neighbor JTextField. I tried to put together a minimal compilable and runnable example below. I asked about the focussed JTable reacting to VK_ENTER: after that, it would react to VK_TAB by giving focus to the next cell until ESC is pressed.

The runnable example below is the result of what I made of the answers I got. When the code is compiled, row selection mode is active and tab key hops between the text field and the table. When you press Enter, it switches to single editing mode and you can change cell using tab key until esc key is pressed.

The problem I still have is: when you edit a cell and press esc key, the single cell is still selected. Instead, the whole row should be selected. How can I reach that?

Thanks!

package TableTest;

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.border.EmptyBorder;
import javax.swing.table.DefaultTableModel;

public class MyFrame extends JFrame {

    private static final long serialVersionUID = 1L;

    public MyFrame() {
        super();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                MyFrame frame = new MyFrame();
                frame.init();
                frame.setVisible(true);
            }

        });
    }

    private void init() {
        JPanel contentPane = new JPanel(new BorderLayout());// new GridBagLayout()
        contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
        setContentPane(contentPane);

        JTable table = new JTable(new DefaultTableModel(new Object[][] { { 1, 2, 3 }, //
                { 4, 5, 6 }, //
                { 7, 8, 9 }, //
                { "#", 0, "*" }, }, //
                new String[] { "First", "Second", "Third" }));

        // When TAB is hit, go to next Component instead of next cell
        final KeyStroke tabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0);
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(tabKey, "tabNext");
        final AbstractAction tabNext = new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent ae) {
                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
            }
        };
        table.getActionMap().put("tabNext", tabNext);

        // When Shift+TAB is hit, go to previous Component instead of previous cell
        final KeyStroke shiftTabKey = KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK);
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(shiftTabKey, "tabBefore");
        final AbstractAction tabBefore = new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent event) {
                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent();
            }
        };
        table.getActionMap().put("tabBefore", tabBefore);

        // on VK_ENTER, navigate in JTable only ("edit mode")
        final KeyStroke enterKey = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
        final AbstractAction editModeAction = new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent event) {
                editMode(table, tabKey, shiftTabKey);
            }
        };
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(enterKey, "editModeAction");
        table.getActionMap().put("editModeAction", editModeAction);

        // On VK_ESCAPE or when JTable loses focus, quit the "edit mode"
        final KeyStroke escKey = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
        final AbstractAction quitEditModeAction = new AbstractAction() {
            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent event) {
                quitEditMode(table, tabKey, shiftTabKey);
            }
        };
        table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(escKey, "quitEditModeAction");
        table.getActionMap().put("quitEditModeAction", quitEditModeAction);
        final FocusListener listener = new FocusListener() {
            @Override
            public void focusGained(FocusEvent event) {
                //do nothing
            }

            @Override
            public void focusLost(FocusEvent event) {
                quitEditMode(table, tabKey, shiftTabKey);
            }
        };
        table.addFocusListener(listener);

        JTextField jtf = new JTextField("Text here");

        contentPane.add(jtf, BorderLayout.NORTH);
        contentPane.add(table, BorderLayout.CENTER);

        pack();

        //printActions(table);
    }

    private void editMode(JTable table, final KeyStroke tabKey, final KeyStroke shiftTabKey) {
        System.out.println("editing activated");
        table.setCellSelectionEnabled(true);
        InputMap input = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        input.remove(shiftTabKey);
        input.remove(tabKey);
        input.put(shiftTabKey, "selectPreviousColumnCell");
        input.put(tabKey, "selectNextColumnCell");
    }

    private void quitEditMode(JTable table, final KeyStroke tabKey, final KeyStroke shiftTabKey) {
        System.out.println("editing de-activated");
        table.setCellSelectionEnabled(false);
        table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        InputMap input = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        input.remove(shiftTabKey);
        input.remove(tabKey);
        input.put(shiftTabKey, "tabBefore");
        input.put(tabKey, "tabNext");
    }

    // print a String representation of each KeyStroke from the InputMap
    private void printActions(JTable table) {
        InputMap input = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        if (input != null && input.allKeys() != null) {
            for (KeyStroke key : input.allKeys()) {
                if (key != null) {
                    printKeyStroke(key);
                    printActionName(input, key);
                }
            }
        }
    }

    // build the String represantation
    private void printKeyStroke(KeyStroke key) {
        StringBuilder tk = new StringBuilder("[");
        int modifiers = key.getModifiers();
        if ((modifiers & InputEvent.SHIFT_DOWN_MASK) != 0)
            tk.append("shift+");
        if ((modifiers & InputEvent.CTRL_DOWN_MASK) != 0)
            tk.append("ctrl+");
        if ((modifiers & InputEvent.META_DOWN_MASK) != 0)
            tk.append("cmd+");
        if ((modifiers & InputEvent.ALT_DOWN_MASK) != 0)
            tk.append("alt+");
        tk.append("'");
        tk.append(KeyEvent.getKeyText(key.getKeyCode()));
        tk.append("'=");
        tk.append("keycode=");
        tk.append(key.getKeyCode());
        tk.append("]");
        System.out.print(tk.toString());
    }

    private void printActionName(InputMap input, KeyStroke key) {
        System.out.print(": ");
        Object string = input.get(key);
        if (string != null && string instanceof String)
            System.out.println(string.toString());
    } 

}
1

1 Answers

0
votes

With some adjustments to your quitEditMode method, you'll get the wanted result:

    private void quitEditMode(JTable table, final KeyStroke tabKey, final KeyStroke shiftTabKey) {
        System.out.println("editing de-activated");
        table.setCellSelectionEnabled(false);
        table.getCellEditor(table.getSelectedRow(), table.getSelectedColumn() ).stopCellEditing();
        table.setRowSelectionAllowed( true );
        table.setRowSelectionInterval( table.getSelectedRow(), table.getSelectedRow() );
        InputMap input = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
        input.remove(shiftTabKey);
        input.remove(tabKey);
        input.put(shiftTabKey, "tabBefore");
        input.put(tabKey, "tabNext");
    }