Как предотвратить переупорядочение одного столбца в JTable?

У меня есть JTable и мне нужно иметь возможность изменить порядок столбцов. Однако я хочу, чтобы первый столбец не мог быть переупорядочен. Я использовал следующее, чтобы включить переупорядочение:

table.getTableHeader().setReorderingAllowed(true);

Теперь можно изменить порядок столбцов, включая первый столбец, который мне не нужен. Есть ли способ заблокировать первый столбец?

Я видел некоторые решения, которые используют две таблицы с первым столбцом в отдельной таблице, но, возможно, есть лучший / более простой способ.

8 ответов

Решение

Я думаю, что вам нужно переопределить columnMoved() метод в TableColumnModelListener, TableColumnModelEvent класс имеет getFromIndex() метод, на который вы сможете посмотреть, чтобы определить, является ли ваш фиксированный столбец, а затем вы сможете отменить событие.

Надеюсь, это поможет.

Это решение, которое я использовал для предотвращения повторного заказа 1-го столбца

private int columnValue = -1; 
private int columnNewValue = -1; 


tblResults.getColumnModel().addColumnModelListener(new TableColumnModelListener() 
{ 
    public void columnAdded(TableColumnModelEvent e) {} 

    public void columnMarginChanged(ChangeEvent e) {} 

    public void columnMoved(TableColumnModelEvent e) 
    { 
        if (columnValue == -1) 
            columnValue = e.getFromIndex(); 

        columnNewValue = e.getToIndex(); 
    } 

    public void columnRemoved(TableColumnModelEvent e) {} 

    public void columnSelectionChanged(ListSelectionEvent e) {} 
}); 

tblResults.getTableHeader().addMouseListener(new MouseAdapter() 
{ 
    @Override 
    public void mouseReleased(MouseEvent e) 
    { 
        if (columnValue != -1 && (columnValue == 0 || columnNewValue == 0)) 
        tblResults.moveColumn(columnNewValue, columnValue); 

        columnValue = -1; 
        columnNewValue = -1; 
    } 
}); 

Ура,

Спустя почти 4 года нигде не видно оптимального решения.

Еще один неоптимальный подход для предотвращения перетаскивания первого столбца (и других столбцов над первым) состоит в том, чтобы перехватить mouseEvents до того, как mouseInputListener, установленный uidelegate, сможет их обработать ( аналогично недавнему QA).

Сотрудники

  • пользовательский MouseMotionListener, который делегирует все события первоначально установленному, кроме перетаскиваемого, если он приведет к другому столбцу над первым
  • заменить оригинал на кастом
  • обновлять замену каждый раз, когда изменяется LAF (потому что оригинал контролируется пользовательским интерфейсом). Это требует создания подкласса JTableHeader и сделать подключение в updateUI

Пользовательский MouseInputListener:

/**
 * A delegating MouseInputListener to be installed instead of
 * the one registered by the ui-delegate.
 * 
 * It's implemented to prevent dragging the first column or any other
 * column over the first.
 */
public static class DragHook implements MouseInputListener {

    private JTableHeader header;
    private MouseListener mouseDelegate;
    private MouseMotionListener mouseMotionDelegate;
    private int maxX;

    public DragHook(JTableHeader header) {
        this.header = header;
        installHook();
    }

    /**
     * Implemented to do some tweaks/bookkeeping before/after
     * passing the event to the original
     * 
     * - temporarily disallow reordering if hit on first column
     * - calculate the max mouseX that's allowable in dragging to the left
     * 
     */
    @Override
    public void mousePressed(MouseEvent e) {
        int index = header.columnAtPoint(e.getPoint());
        boolean reorderingAllowed = header.getReorderingAllowed();
        if (index == 0) {
            // temporarily disable re-ordering 
            header.setReorderingAllowed(false);
        }
        mouseDelegate.mousePressed(e);
        header.setReorderingAllowed(reorderingAllowed);
        if (header.getDraggedColumn() != null) {
            Rectangle r = header.getHeaderRect(index);
            maxX = header.getColumnModel().getColumn(0).getWidth() 
                    + e.getX() - r.x -1; 
        }
    }

    /**
     * Implemented to pass the event to the original only if the
     * mouseX doesn't lead to dragging the column over the first.
     */
    @Override
    public void mouseDragged(MouseEvent e) {
        TableColumn dragged = header.getDraggedColumn();
        int index = getViewIndexForColumn(header.getColumnModel(), dragged);
        // dragged column is at second position, allow only drags to the right
        if (index == 1) {
            if (e.getX() < maxX) return;
        }
        mouseMotionDelegate.mouseDragged(e);
    }

    //-------- delegating-only methods

    @Override
    public void mouseReleased(MouseEvent e) {
        mouseDelegate.mouseReleased(e);
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        mouseDelegate.mouseClicked(e);
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        mouseDelegate.mouseEntered(e);
    }

    @Override
    public void mouseExited(MouseEvent e) {
        mouseDelegate.mouseExited(e);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        mouseMotionDelegate.mouseMoved(e);
    }

    //------------ un-/install listeners

    protected void installHook() {
        installMouseHook();
        installMouseMotionHook();
    }

    protected void installMouseMotionHook() {
        MouseMotionListener[] listeners = header.getMouseMotionListeners();
        for (int i = 0; i < listeners.length; i++) {
            MouseMotionListener l = listeners[i];
            if (l.getClass().getName().contains("TableHeaderUI")) {
                this.mouseMotionDelegate = l;
                listeners[i] = this;
            }
            header.removeMouseMotionListener(l);
        }
        for (MouseMotionListener l : listeners) {
            header.addMouseMotionListener(l);
        }
    }

    protected void installMouseHook() {
        MouseListener[] listeners = header.getMouseListeners();
        for (int i = 0; i < listeners.length; i++) {
            MouseListener l = listeners[i];
            if (l.getClass().getName().contains("TableHeaderUI")) {
                this.mouseDelegate = l;
                listeners[i] = this;
            }
            header.removeMouseListener(l);
        }
        for (MouseListener l : listeners) {
            header.addMouseListener(l);
        }
    }

    public void uninstallHook() {
        uninstallMouseHook();
        uninstallMouseMotionHook();
    }

    protected void uninstallMouseMotionHook() {
        MouseMotionListener[] listeners = header.getMouseMotionListeners();
        for (int i = 0; i < listeners.length; i++) {
            MouseMotionListener l = listeners[i];
            if (l == this) {
                listeners[i] = mouseMotionDelegate;
            }
            header.removeMouseMotionListener(l);
        }
        for (MouseMotionListener l : listeners) {
            header.addMouseMotionListener(l);
        }
    }

    protected void uninstallMouseHook() {
        MouseListener[] listeners = header.getMouseListeners();
        for (int i = 0; i < listeners.length; i++) {
            MouseListener l = listeners[i];
            if (l == this) {
                listeners[i] = mouseDelegate;
            }
            header.removeMouseListener(l);
        }
        for (MouseListener l : listeners) {
            header.addMouseListener(l);
        }
    }

}

Использование, которое переживает переключение LAF, например:

JTable table = new JTable(new AncientSwingTeam()) {

    @Override
    protected JTableHeader createDefaultTableHeader() {
        JTableHeader header = new JTableHeader(getColumnModel()) {
            DragHook hook;

            @Override
            public void updateUI() {
                if (hook != null) {
                    hook.uninstallHook();
                    hook = null;
                }
                super.updateUI();
                hook = new DragHook(this);
            }

         };
        return header;
    }

};

У меня была та же проблема, и я искал об этом. До сих пор я нашел два способа сделать это.

  • Метод "если бы я переписывал сам": модификация базовых классов из Java.

TableColumn потребовалось бы новое свойство, такое как "resizingAllowed", ему понадобится "reorderingAllowed". Исходя из этого, изменения происходят в BasicTableHeaderUI:

Уже есть:

private static boolean canResize(TableColumn column,
                                 JTableHeader header) {
    return (column != null) && header.getResizingAllowed()
                            && column.getResizable();
}

Это тоже нужно:

private static boolean canMove(TableColumn column,
                               JTableHeader header) {
    return (column != null) && header.getReorderingAllowed()
                                && column.getReorderable();
}

(Обратите внимание, что если вы не хотите, чтобы только первый столбец не двигался, вы можете обойтись без изменения TableColumns:

private static boolean canMove(TableColumn column,
                                 JTableHeader header) {
    return (column != null) && header.getReorderingAllowed()
                            && header.getColumnModel().getColumnIndex(column.getIdentifier()) != 0;
}

)

После, два места для изменения в MouseInputListener:

  • в mousePressedвызывая canMove() вместо header.getReorderingAllowed(), Это гарантирует, что столбец, который не должен быть перемещен, не будет.
  • Но этого недостаточно, мы должны предотвратить перемещение неподвижных столбцов при перетаскивании другого. Вам нужно изменить mouseDraggedТакже, когда он получает "newColumnIndex":

    if (0

Вам нужно добавить условие, если этот новый индекс можно переместить, например, с помощью метода canMove (). Таким образом, когда вы будете перетаскивать столбец в этот неподвижный, вы все равно будете его перетаскивать, но он не поменяет их местами.

Обратите внимание, что этот метод потребует от вас явной установки пользовательского интерфейса для JTableHeader, используемого для вашей JTable, что на самом деле не идеально. Но это наиболее приспособленный вариант, поскольку он решает проблему в том месте, где он должен.


  • Метод "Давайте попробуем заблокировать нормальное поведение тем, что у нас есть": не изменяя пользовательский интерфейс, этот метод фокусируется на JTableHeader, чтобы блокировать команды, сделанные пользовательским интерфейсом.

Во-первых, чтобы заблокировать перетаскивание первого столбца, нам нужен подкласс из JTableHeader с этим переопределенным методом:

@Override
public void setDraggedColumn(TableColumn pAColumn)
{
    int lIndex  = -1;
    if (pAColumn != null)
        lIndex = getColumnModel().getColumnIndex(pAColumn.getIdentifier());
    if (lIndex != 0)
        super.setDraggedColumn(pAColumn);
}

Это предотвратит перетаскивание пользователем первого столбца. Но, как описано выше, это только одна часть проблемы, нам нужно предотвратить замену другого перетаскиваемого столбца на этот первый.

Пока у меня нет правильного метода для этого. Я попытался создать подкласс TableColumnModel и переопределить moveColumn() метод:

@Override
public void moveColumn(int pColumnIndex, int pNewIndex)
{
    //Move only if the first column is not concerned
    if (pColumnIndex =! 0 && pNewIndex != 0)
        super.moveColumn(pColumnIndex, pNewIndex);
}

Но это не сработает, так как пользовательский интерфейс все равно обновит положение мыши в mouseDragged метод, у вас будет прыжок из перетаскиваемой колонны в другое место.

Так что я все еще ищу и задаюсь вопросом, есть ли у кого-то предложения относительно этой части.

Сначала вам нужно определить лучший и простой способ. Что вам не нравится в подходе с двумя столами?

Вы не можете использовать TableColumnModelListener, потому что событие вызывается "после", столбец уже был перемещен.

Код для перетаскивания столбца находится в BasicTableHeaderUI. Таким образом, вы можете попробовать переопределить код там, но тогда вам нужно будет сделать это для всех LAF.

Приведенный выше код вызывает JTableHeader.getReorderingAllowed() для события mousePressed, чтобы определить, разрешено ли изменение порядка столбцов. Я думаю, вы могли бы переопределить этот метод в JTableHeader и, возможно, использовать класс MouseInfo, чтобы получить текущее местоположение мыши, чтобы определить, было ли оно над первым столбцом, а затем вернуть false. Но тогда теперь вам также необходимо создать пользовательский JTable, который использует пользовательский заголовок таблицы.

Конечно, с любым из приведенных выше предложений вы можете предотвратить перемещение первого столбца. Но не забывайте, что вам также нужно предотвратить вставку второго столбца перед первым столбцом. Я не верю, что есть короткий простой ответ на вопрос.

Фиксированная таблица столбцов - моя версия того, как это будет реализовано с двумя таблицами. Это лучше? Я не знаю, но это просто, так как его можно использовать только одной строкой кода.

Сначала я использовал самое последнее предложение Gnoupi, состоящее в создании подкласса TableColumnModel и переопределении moveColumn, но все еще были некоторые раздражающие переходы.

Это "моё" полностью работающее и протестированное решение без неприятных скачков, в основном оно опирается на предложения СтаниславКо и Клеопатры. Я добавил более сложный механизм, чтобы отменить нежелательное движение при отпускании кнопки мыши:

table.getTableHeader().setUI(new WindowsTableHeaderUI() {
        @Override
        protected MouseInputListener createMouseInputListener() {
            return new BasicTableHeaderUI.MouseInputHandler() {

                @Override
                public void mouseDragged(MouseEvent e) {
                    if (header.isEnabled() && header.getReorderingAllowed() && header.getDraggedColumn() != null && header.getDraggedColumn().getModelIndex() == frozenColumnModelIndex) {
                        header.setDraggedDistance(0);
                        header.setDraggedColumn(null);
                        return;
                    }
                    super.mouseDragged(e);
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    if (header.isEnabled() && header.getReorderingAllowed() && header.getDraggedColumn() != null &&
                        0 <= illegalTableColumnMoveFromIndex && illegalTableColumnMoveFromIndex < header.getTable().getColumnModel().getColumnCount()) {
                        header.setDraggedDistance(0);
                        header.setDraggedColumn(null);
                        header.getTable().getColumnModel().moveColumn(illegalTableColumnMoveToIndex, illegalTableColumnMoveFromIndex);
                        illegalTableColumnMoveFromIndex = -1;
                        illegalTableColumnMoveToIndex = -1;
                        return;
                    }
                    super.mouseReleased(e);
                }
            };
        }
    });
    table.getColumnModel().addColumnModelListener(new TableColumnModelListener() {
        @Override
        public void columnAdded(TableColumnModelEvent e) {
        }

        @Override
        public void columnRemoved(TableColumnModelEvent e) {
        }

        @Override
        public void columnMoved(TableColumnModelEvent e) {
            if (e.getFromIndex() != e.getToIndex() && table.getColumnModel().getColumn(e.getFromIndex()).getModelIndex() == frozenColumnModelIndex) {
                illegalTableColumnMoveFromIndex = e.getFromIndex();
                illegalTableColumnMoveToIndex = e.getToIndex();
            } else {
                illegalTableColumnMoveFromIndex = -1;
                illegalTableColumnMoveToIndex = -1;
            }
        }

        @Override
        public void columnMarginChanged(ChangeEvent e) {
        }

        @Override
        public void columnSelectionChanged(ListSelectionEvent e) {
        }
    });

Обратите внимание, что последний действительный ход принимается вместо полного возврата перетаскивания столбца.

frozenColumnModelIndex - это индекс "замороженного" столбца в модели таблицы.

invalidTableColumnMoveFromIndex - это индекс столбца, из которого он был перемещен при обнаружении последнего незаконного перемещения.

invalidTableColumnMoveToIndex - это индекс столбца, куда он был перемещен при обнаружении последнего незаконного перемещения.

Код внутри mouseDragged достаточен для предотвращения перетаскивания замороженного столбца, остальное позволяет предотвратить перетаскивание другого столбца в замороженный столбец.

Он работает как в Microsoft Windows, так как я расширяю WindowsTableHeaderUI, но вместо этого использую API отражения, чтобы установить слушатель ввода мыши для пользовательского интерфейса заголовка таблицы, вызвать uninstallerListeners() и, наконец, вызвать header.addMouseListener (mouseInputListener) и header.addMouseMotionListener (mouseInputListener) в Чтобы сделать мое решение кроссплатформенным, не делая предположений об имени класса для каждого пользовательского интерфейса заголовка таблицы.

Я признаю, что это может быть немного менее надежным, чем решение Клеопатры. Я благодарю всех вас за вашу помощь, я очень благодарен и очень рад видеть, что совместная работа просто работает:)

Я использовал метод "Давайте попробуем заблокировать нормальное поведение с помощью того, что у нас есть". Гноупи сказал, что он не решил вторую часть проблемы. Вот решение для Windows XP L&F:

  1. скопируйте класс XPStyle себе.
  2. простираться WindowsTableHeaderUI, Посмотрите на исходный код.
  3. используй это: getTableHeader().setUI(new TreeTableWindowsTableHeaderUI());

Спасибо Gnoupi за усилия.

Я просто верну колонку после того, как движение будет завершено. Так что-то вроде.

@Override
public void moveColumn(int from, int to) {
      super.moveColumn(from, to);
      if (from == 0 || to == 0) {
           super.moveColumn(to, from);
      }
}
Другие вопросы по тегам