Tuesday, January 26, 2010

Изменение поведения клавиатурных сокращений в JTable

Сначала добавляем свои идентификаторы действий в карту клавиатурных сокращений. Делать это удобно в UI-делегате (по крайней мере, именно так происходит со стандартными компонентами Swing) в методе installUI.
Процесс обработки клавиатурного сочетания следующий:
- в методе компонента processKeyBinding сохраним нажатую комбинацию.
- далее в методе  компонента changeSelection обрабатываем комбинацию (подсмотренно у JIDE).



Для примера реализуем аналог NavigatableTable из JIDE. Пишем модель навигации:

public interface NavigatableModel {

    boolean    isNavigableAt(int rowIndex, int columnIndex);
    boolean    isNavigationOn();
   
}

Реализуем эту модель в нашей модели таблицы:
public class NavigationTableModel extends DefautTableModel implements NavigatableModel {

    @Override
    public int getColumnCount() {
        return 10;
    }

    @Override
    public int getRowCount() {
        return 10;
    }

    @Override
    public Object getValueAt(int row, int column) {
        return row*column;
    }

    @Override
    public boolean isCellEditable(int row, int column) {
        return true;
    }

    public boolean isNavigableAt(int rowIndex, int columnIndex) {
        return rowIndex % 2 == columnIndex % 2;
    }

    public boolean isNavigationOn() {
        return true;
    }
}

UI-делегат для таблицы:




public class BasicNavigatableTableUI extends BasicTableUI {

    public static ComponentUI createUI(JComponent jcomponent) {
        return new BasicNavigatableTableUI();
    }


    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        c.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0), "scrolRightChangeSelection");
        c.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_TAB, InputEvent.SHIFT_DOWN_MASK), "scrolLeftChangeSelection");
        c.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "scrolDownChangeSelection");
        c.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.SHIFT_DOWN_MASK), "scrolUpChangeSelection");
    }
}

В делегате добавляются идентификаторы действий:
  • TAB - перейти к следующей доступной ячейке в ряду
  • Shift+TAB - перейти к предыдущей доступной ячейке в ряду
  • ENTER - перейти к следующей доступной ячейке в колонке
  • Shift+ENTER - перейти к предыдущей доступной ячейке в колонке
И сама таблица:

public class NavigatableTable extends JTable {


    public boolean isCellNavigatable(int row, int column) {
        TableModel tableModel = getModel();
        if (!(tableModel instanceof NavigatableModel)) {
            return true;
        }
        NavigatableModel navigatableModel = (NavigatableModel) tableModel;
        if (!navigatableModel.isNavigationOn()) {
            return true;
        }
        return navigatableModel.isNavigableAt(row, column);
    }


    @Override
    protected boolean processKeyBinding(KeyStroke ks, KeyEvent e, int condition, boolean pressed) {
        keyStroke = ks;
        return super.processKeyBinding(ks, e, condition, pressed);
    }

    @Override
    public void changeSelection(int rowIndex, int columnIndex, boolean toggle, boolean extend) {
        TableModel tableModel = getModel();
        if (!(tableModel instanceof NavigatableModel)) {
            super.changeSelection(rowIndex, columnIndex, toggle, extend);
            return;
        }
        NavigatableModel navigatableModel = (NavigatableModel) tableModel;
        if (!navigatableModel.isNavigationOn()) {
            super.changeSelection(rowIndex, columnIndex, toggle, extend);
            return;
        }

        scrollRectToVisible(getCellRect(rowIndex, columnIndex, true));
        int row = getSelectedRow();
        int column = getSelectedColumn();

        final Object o = getInputMap().get(keyStroke);
        if (o==null) {
            super.changeSelection(rowIndex, columnIndex, toggle, extend);
            return;
        }

        if (o.equals("scrolRightChangeSelection")) {
            boolean found = false;
            int i = column + 1;
            int j = row;
            while (!found) {
                while (i
                    if (isCellNavigatable(j, i)) {
                        found = true;
                        break;
                    }
                    i++;
                }
                if (!found) {
                    i = 0;
                    j++;
                    if (j>=getRowCount()) {
                        j=0;
                    }
                }
                if (j==row && i==column) { //full table scan complete
                    break;
                }

            }
            column = i;
            row = j;
        }
        else if (o.equals("scrolLeftChangeSelection")) {
            boolean found = false;
            int i = column - 1;
            int j = row;
            if (i<0) {
                i = getColumnCount()-1;
                j--;
            }
            if (j<0) {
                j=getRowCount()-1;
            }
            while (!found) {
                if (j==row && i==column) { //full table scan complete
                    break;
                }
                while (i>=0) {
                    if (isCellNavigatable(j, i)) {
                        found = true;
                        break;
                    }
                    i--;
                }
                if (!found) {
                    i = getColumnCount()-1;
                    j--;
                    if (j<0) {
                        j=getRowCount()-1;
                    }
                }

            }
            column = i;
            row = j;
        }
        else if (o.equals("scrolDownChangeSelection")) {
            boolean found = false;
            int i = row + 1;
            int j = column;
            while (!found) {
                while (i
                    if (isCellNavigatable(i, j)) {
                        found = true;
                        break;
                    }
                    i++;
                }
                if (!found) {
                    i = 0;
                    j++;
                    if (j>=getRowCount()) {
                        j=0;
                    }
                }
                if (i==row && j==column) { //full table scan complete
                    break;
                }

            }
            column = j;
            row = i;
        }
        else if (o.equals("scrolUpChangeSelection")) {
            boolean found = false;
            int i = row - 1;
            int j = column;
            if (i<0) {
                i = getRowCount()-1;
                j--;
            }
            if (j<0) {
                j=getColumnCount()-1;
            }
            while (!found) {
                if (j==column && i==row) { //full table scan complete
                    break;
                }
                while (i>=0) {
                    if (isCellNavigatable(i, j)) {
                        found = true;
                        break;
                    }
                    i--;
                }
                if (!found) {
                    i = getRowCount()-1;
                    j--;
                    if (j<0) {
                        j=getColumnCount()-1;
                    }
                }

            }
            column = j;
            row = i;
        }
        super.changeSelection(row, column, toggle, extend);
    }

    @Override
    public void updateUI() {
        if (UIManager.get("BasicNavigatableTableUI")==null) {
            UIManager.put("BasicNavigatableTableUI", "grid.BasicNavigatableTableUI");
            UIManager.put("grid.BasicNavigatableTableUI", BasicNavigatableTableUI.class);
        }
        final ComponentUI newUI = UIManager.getUI(this);
        setUI(newUI);
    }

    public String getUIClassID() {
        return "BasicNavigatableTableUI";
    }




    private KeyStroke keyStroke;


}

Заметьте, что в начале changeSelection при некоторых условиях (например, управление навигацией отключено в модели: isNavigationOn() возвращает false), то управление передается родительскому методу и начинают действовать обработчики клавиатурных сокращений по умолчанию.

No comments :

Post a Comment