Лучшая практика для обработки VK_TAB в Java Swing для перемещения между компонентами, а также для перемещения между ячейками таблицы только с помощью клавиатуры - PullRequest
0 голосов
/ 02 мая 2020

Я пытаюсь найти лучшую практику с точки зрения доступности таблиц по отношению к другим компонентам. В приложении, которое в основном представляет собой набор JTables и JTextFields, я попытался сделать его доступным как для клавиатуры, так и для мыши. Мои мысли о том, как помочь пользователю перемещаться между компонентами с помощью клавиши VK_TAB.

Моей первой целью было остановить JTables "проглотить" ключ VK_TAB, когда пользователь пытается перейти к соседнему JTextField, используя решение из Coderanch . Я попытался собрать минимальный скомпилируемый и запускаемый пример ниже.

package TableTest;

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

import javax.swing.AbstractAction;
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.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
        table.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "tabNext");
        table.getActionMap().put("tabNext", new AbstractAction() {
            private static final long serialVersionUID = 1L;

            public void actionPerformed(ActionEvent ae) {
                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent();
            }
        });

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

            public void actionPerformed(ActionEvent ae) {
                KeyboardFocusManager.getCurrentKeyboardFocusManager().focusPreviousComponent();
            }
        });

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

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

        pack();

    }
}

Но это довольно радикально и разочаровывает пользователя, который хочет перейти к ячейке таблицы, например, для редактирования, используя только клавиатуру. Итак, моя вторая цель - предоставить клавиатуре доступ и к Table Cells.

Что является лучшим вариантом здесь? Я подумал о фокусируемом JTable, реагирующем на VK_ENTER: после этого он будет реагировать на VK_TAB, давая фокусировку следующей ячейке, пока не будет нажата ... ES C или что-то еще.

Спасибо!

Ответы [ 2 ]

0 голосов
/ 05 мая 2020

Спасибо, camickr!

Поэтому, когда я изменяю код, следуя вашему совету, это работает.

Вот полный пример:

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");
    }
}


Я получил привязки клавиш к действиям из ActionMap и InputMap JTable. Я попытался добавить небольшой метод

    // 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());
    } 
0 голосов
/ 02 мая 2020

Каков лучший метод здесь?

Реализация по умолчанию:

  1. Tab - переход к следующему компоненту
  2. Ctrl + Tab - перемещение к следующему компоненту

  3. Shift + Tab - перемещение к предыдущему компоненту

  4. Ctrl + Shift + Tab - перемещение к компонент previoius

Некоторые компоненты обрабатывают клавишу Tab. Например:

  1. JTable - вкладка используется для перехода к следующей ячейке
  2. Текстовые компоненты (JTextArea, JTextPane) - вставит символ табуляции в текст

Так что для компонентов, которые обрабатывают клавишу Tab, пользователь будет использовать Ctrl + Tab для перехода к следующему компоненту при использовании клавиатуры.

Редактировать:

Я думал о фокусированная JTable, реагирующая на VK_ENTER

Вы уже знаете, как назначить другое Действие клавише Tab.

Так что теперь все, что вам нужно сделать, это назначить Действие Tab по умолчанию для Введите ключ. Вы можете сделать это, изменив привязку в InputMap таблицы:

InputMap im = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke addedKeyStroke = KeyStroke.getKeyStroke("ENTER");
im.put(addedKeyStroke, "selectNextColumnCell");

Извлеките Привязки клавиш для простого приложения, которое отображает все Действия по умолчанию для каждого компонента Swing.

...