java 如何防止单个列在 JTable 中重新排序?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1155137/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 15:24:39  来源:igfitidea点击:

How to keep a single column from being reordered in a JTable?

javaswingjtable

提问by Beardo

I have a JTableand I need to be able to reorder the columns. However I want the first column to not be able to be re-ordered. I used the following to enable reordering:

我有一个JTable,我需要能够重新排序列。但是我希望第一列无法重新排序。我使用以下内容启用重新排序:

table.getTableHeader().setReorderingAllowed(true);

The columns can now be reordered including the first column which I don't want. Is there any way to lock the first column?

现在可以重新排序列,包括我不想要的第一列。有没有办法锁定第一列?

I have seen some solutions that use two tables with the first column being in a separate table, but maybe there's a better/simpler way.

我见过一些使用两个表的解决方案,第一列在一个单独的表中,但也许有更好/更简单的方法。

采纳答案by phatmanace

I think that you need to override the columnMoved()method in TableColumnModelListener. the TableColumnModelEventclass has a getFromIndex()method that you should be able to look at to determine if it's your fixed column, and then you should be able to cancel the event.

我认为你需要覆盖columnMoved()的方法TableColumnModelListener。在TableColumnModelEvent类有一个getFromIndex()方法,你应该能够看到,以确定它是否是你的固定列,然后你应该能够取消事件。

Hope that helps. A

希望有帮助。一个

回答by Beardo

This is the solution that I used to prevent the 1st column from being re-ordered

这是我用来防止第一列被重新排序的解决方案

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

Cheers,

干杯,

回答by kleopatra

Nearly 4 years later, there's still no optimal solution in sight anywhere.

将近 4 年过去了,仍然看不到任何地方的最佳解决方案。

Yet another suboptimal approach to prevent dragging of the first column (and other columns over the first) is to intercept the mouseEvents beforethe mouseInputListener installed by the uidelegate can handle them (similar to a recent QA).

防止拖动第一列(和其他列超过第一列)的另一种次优方法是在 uidelegate 安装的 mouseInputListener 可以处理它们之前拦截 mouseEvents (类似于最近的 QA)。

The collaborators

合作者

  • a custom MouseMotionListener which delegates all events to the originally installed, except the dragged if it would lead to another column above the first
  • replace the original with the custom
  • update the replacement whenever the LAF is changed (because the original is controlled by the ui). This requires subclassing of JTableHeader and do the wiring in updateUI
  • 一个自定义的 MouseMotionListener ,它将所有事件委托给最初安装的事件,如果拖动的事件会导致第一列上方的另一列
  • 用自定义替换原始
  • 每当 LAF 更改时更新替换(因为原始由 ui 控制)。这需要 JTableHeader 的子类化并在 updateUI 中进行接线

The custom MouseInputListener:

自定义 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);
        }
    }

}

Usage which survives switching of LAF, f.i.:

在 LAF、fi 切换中幸存下来的用法:

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

};

回答by camickr

First you need to define a better and simpler way. What don't you like about the 2 table approach?

首先,您需要定义一种更好、更简单的方法。您不喜欢 2 表方法的哪些方面?

You can't use a TableColumnModelListener, because the event is fired "after" the column has already been moved.

您不能使用 TableColumnModelListener,因为该事件是在“列”已经被移动之后触发的。

The code for dragging the column is found in the BasicTableHeaderUI. So you could try overriding the code there, but then you would need to do it for all LAFs.

拖动列的代码可以在 BasicTableHeaderUI 中找到。因此,您可以尝试覆盖那里的代码,但是您需要为所有 LAF 执行此操作。

The above code invokes JTableHeader.getReorderingAllowed() on a mousePressed event to determine if column reordering is allowed. I guess you could override that method in the JTableHeader and perhaps use the MouseInfo class to get the current mouse location to determine if it was over the first column and then return false. But then now you would also need to create a custom JTable that uses the custom table header.

上面的代码在 mousePressed 事件上调用 JTableHeader.getReorderingAllowed() 以确定是否允许列重新排序。我猜您可以在 JTableHeader 中覆盖该方法,并可能使用 MouseInfo 类来获取当前鼠标位置以确定它是否在第一列上,然后返回 false。但是现在您还需要创建一个使用自定义表头的自定义 JTable。

Of course with either of the above suggestions you might be able to prevent the first column from being moved. But don't forget you also need to prevent the 2nd column from being inserted before the first column. I don't believe there is a short simple answer to the question.

当然,使用上述任何一个建议,您都可以防止第一列被移动。但是不要忘记您还需要防止在第一列之前插入第二列。我不相信这个问题有一个简短的答案。

Fixed Column Tableis my version of how this would be imlemented with two tables. Is it better? I don't know, but it is simple since its only a single line of code to use it.

固定列表是我关于如何使用两个表来实现的版本。这个会比较好吗?我不知道,但它很简单,因为它只有一行代码就可以使用它。

回答by Gnoupi

I had the same issue, and I was searching about it. So far I found two ways of doing that.

我有同样的问题,我正在寻找它。到目前为止,我找到了两种方法。

  • The "if I was rewriting it myself" method: Modifying the base classes from Java.
  • “如果我自己重写它”方法:从 Java 修改基类。

TableColumnwould need a new property, like the "resizingAllowed", it would need the "reorderingAllowed". From this, the modifications take place in BasicTableHeaderUI:

TableColumn需要一个新属性,比如“resizingAllowed”,它需要“reorderingAllowed”。由此,修改发生在BasicTableHeaderUI

There is already :

已经有:

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

It would need too :

它也需要:

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

(Note that if you don't want the first column only to not move, you can do without changing the TableColumns :

(请注意,如果您不希望第一列只移动,则可以不更改 TableColumns :

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

)

)

After, two places to modify in the MouseInputListener:

之后,修改了两个地方MouseInputListener

  • in the mousePressed, calling the canMove()instead of the header.getReorderingAllowed(). This ensures that a column which shouldn't be moved, won't be.
  • But this is not enough, we need to prevent the immobile columns from being moved during dragging another one. You need to change the mouseDragged, too, when it is getting the "newColumnIndex" :

    if (0 < newColumnIndex && newColumnIndex < cm.getColumnCount())

  • 在 中mousePressed,调用canMove()而不是header.getReorderingAllowed()。这确保不应移动的列不会移动。
  • 但这还不够,我们还需要防止在拖动另一列时不动的列被移动。mouseDragged当它获得“newColumnIndex”时,您也需要更改:

    if (0 < newColumnIndex && newColumnIndex < cm.getColumnCount())

You need to add the condition if this new index can be moved, for example using the "canMove()" method. This way, when you will drag a column to this immobile one, you will still drag it, but it won't swap them.

如果可以移动这个新索引,您需要添加条件,例如使用“canMove()”方法。这样,当您将一列拖到这个固定的列时,您仍然会拖拉它,但不会交换它们。

Note that this method would require you to explicitly set the UI for the JTableHeader used for your JTable, which is not really ideal. But this is the most adapted though, as it deals with the problem on the place it is supposed to.

请注意,此方法需要您为用于 JTable 的 JTableHeader 显式设置 UI,这并不理想。但这是最适合的,因为它在它应该处理的地方处理问题。



  • The "Let's try to block the normal behavior with what we actually have" method: Not modifying the UI, this method focus on the JTableHeader to block the commands made by the UI.
  • “让我们尝试用我们实际拥有的东西来阻止正常行为”方法:不修改 UI,该方法专注于 JTableHeader 来阻止 UI 发出的命令。

First, to block dragging the first column, we need a subclass from JTableHeader, with this overridden method :

首先,为了阻止拖动第一列,我们需要一个来自 JTableHeader 的子类,使用这个重写方法:

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

This will prevent a user from dragging the first column. But like described earlier, this is only one part of the problem, we need to prevent another dragged column from swapping with this first one.

这将防止用户拖动第一列。但是就像前面描述的那样,这只是问题的一部分,我们需要防止另一个拖动的列与第一个交换。

So far, I don't have a correct method for this. I tried by subclassing the TableColumnModel, and overriding the moveColumn()method :

到目前为止,我没有正确的方法。我尝试通过子类化 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);
}

But this won't work, as the UI will update anyway the mouse position in the mouseDraggedmethod, you will have a jump from your dragged column to another place.

但这不起作用,因为 UI 无论如何都会更新mouseDragged方法中的鼠标位置,您将从拖动的列跳转到另一个位置。

So I'm still searching, and wonder if someone has propositions concerning this part.

所以我还在搜索,不知道有人对这部分有什么建议。

回答by gouessej

At first, I used the very last Gnoupi's suggestion consisting in subclassing the TableColumnModel and overriding moveColumn but there were still some annoying jumps.

起初,我使用了 Gnoupi 的最后一个建议,包括对 TableColumnModel 进行子类化和覆盖 moveColumn 但仍然有一些恼人的跳跃。

This is "my" fully working and tested solution with no nasty jump, it mainly relies on StanislavKo and kleopatra's suggestions. I added a more complicated mechanism to revert the unwanted move when releasing the mouse button :

这是“我的”完全有效且经过测试的解决方案,没有令人讨厌的跳跃,它主要依赖于 StanislavKo 和 kleopatra 的建议。我添加了一个更复杂的机制来在释放鼠标按钮时恢复不需要的移动:

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

Note that the latest valid move is accepted instead of completely reverting the column drag.

请注意,将接受最新的有效移动,而不是完全恢复列拖动。

frozenColumnModelIndex is the index of the "frozen" column in the table model.

frozenColumnModelIndex 是表模型中“frozen”列的索引。

illegalTableColumnMoveFromIndex is the index of the column from where it was moved when the latest illegal move was detected.

invalidTableColumnMoveFromIndex 是在检测到最新的非法移动时移动它的列的索引。

illegalTableColumnMoveToIndex is the index of the column to where it was moved when the latest illegal move was detected.

非法TableColumnMoveToIndex 是当检测到最新的非法移动时列的索引。

The code inside mouseDragged is enough to prevent the frozen column from being dragged, the rest allows to prevent another column from being dragged to the frozen column.

mouseDragged 里面的代码足以防止冻结列被拖动,其余的允许防止另一列被拖动到冻结列。

It works as is under Microsoft Windows as I extend WindowsTableHeaderUI but rather use the reflection API to set the mouse input listener of the table header UI, call uninstallerListeners() and finally call header.addMouseListener(mouseInputListener) and header.addMouseMotionListener(mouseInputListener) in order to drive my solution cross-platform without making any assumption on the name of the class for each table header UI.

它在 Microsoft Windows 下工作,因为我扩展了 WindowsTableHeaderUI,而是使用反射 API 来设置表标题 UI 的鼠标输入侦听器,调用 uninstallerListeners() 并最终调用 header.addMouseListener(mouseInputListener) 和 header.addMouseMotionListener(mouseInputListener)为了在不假设每个表头 UI 的类名称的情况下跨平台驱动我的解决方案。

I admit it might be a bit less robust than kleopatra's solution. I thank you all for your help, I'm really grateful and I'm really happy to see that collaborative work just works :)

我承认它可能不如 kleopatra 的解决方案健壮。我感谢大家的帮助,我真的很感激,我很高兴看到协作工作正常工作:)

回答by sbidwai

I will just put the column back after the move is complete. So something like.

移动完成后,我会将列放回原处。所以有点像。

@Override
public void moveColumn(int from, int to) {
      super.moveColumn(from, to);
      if (from == 0 || to == 0) {
           super.moveColumn(to, from);
      }
}

回答by StanislavKo

I have used the "The 'Let's try to block the normal behavior with what we actually have' method" approach. Gnoupisaid that he did not solve the second part of the problem. Here is the solution for just Windows XP L&F:

我使用了“让我们尝试用我们实际拥有的方法阻止正常行为”的方法。Gnoupi说他没有解决问题的第二部分。这是仅适用于 Windows XP L&F 的解决方案:

  1. copy XPStyle class to yourself.
  2. extend WindowsTableHeaderUI. Take a look at the source code.
  3. use it: getTableHeader().setUI(new TreeTableWindowsTableHeaderUI());
  1. 将 XPStyle 类复制到自己。
  2. 延伸WindowsTableHeaderUI。看看源代码
  3. 用它: getTableHeader().setUI(new TreeTableWindowsTableHeaderUI());

Thanks to Gnoupifor the efforts.

感谢Gnoupi的努力。