【问题标题】:How to keep a single column from being reordered in a JTable?如何防止单个列在 JTable 中重新排序?
【发布时间】:2010-11-12 09:53:27
【问题描述】:

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

table.getTableHeader().setReorderingAllowed(true);

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

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

【问题讨论】:

  • 我通过你的问题得到了 70% 并开始写回复告诉你使用 2 个表......然后我读完了你的帖子。
  • 所以你是说第一列应该独立于其他列?
  • 他说的是左右移动整列。
  • 哦,我在考虑排序。这更有意义。
  • 是的,将列向左或向右移动,不对列内的数据进行排序

标签: java swing jtable


【解决方案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; 
    } 
}); 

干杯,

【讨论】:

    【解决方案2】:

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

    另一种防止拖动第一列(以及第一列上方的其他列)的次优方法是在 uielegate 安装的 mouseInputListener 可以处理它们之前拦截 mouseEvents (similar to a recent QA)。

    合作者

    • 一个自定义的 MouseMotionListener,它将所有事件委托给最初安装的事件,除了拖动的事件,如果它会导致第一列上方的另一列
    • 用自定义替换原来的
    • 每当 LAF 更改时更新替换(因为原始由 ui 控制)。这需要 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、f.i. 切换后仍然存在的用法:

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

    【讨论】:

      【解决方案3】:

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

      希望对您有所帮助。一个

      【讨论】:

      • 这看起来是最好的方法。
      • 我一直在玩这个,我的问题是(因为我是 Java 新手)如何取消活动?
      • 您选择是否将方法调用传递给超类。如果不这样做,它将不会应用于表格。
      • 您必须对 TableColumnModel 而不是 TableColumnModelListener 的实现进行此更改,覆盖 moveColumn(int columnIndex, int newIndex)。您无法取消该事件,即使 MouseEvent.consume() 也无效。 TableColumnModelListener 没有超类,因为它是一个接口。
      • 正如@gouessej 所说,TableColumnModelListener 是一个接口——没有super.columnMoved 可以调用或不调用。答案是错误的。
      【解决方案4】:

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

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

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

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

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

      Fixed Column Table 是我对如何用两个表实现的版本。好点吗?我不知道,但它很简单,因为它只需一行代码即可使用它。

      【讨论】:

      • 他可能不喜欢“2tables 方法”,因为我猜这会破坏数据模型。这将迫使最终将数据分离到两个模型中,这在 JTable 和图形方面肯定很简单,但在模型方面更烦人。
      • 它并不强制你使用两个模型。只有 OP 知道他的要求是什么。我们无法读懂他的心思。
      • 你不需要两个模型,你可以很容易地从同一个 TableModel 做两个表的方法。从概念和维护的角度来看,这是最简单的方法。
      【解决方案5】:

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

      • “如果我自己重写”方法:从 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()”方法。这样,当您将一列拖到这个固定的列时,您仍然会拖动它,但不会交换它们。

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


      • “让我们尝试用我们实际拥有的东西来阻止正常行为”方法:不修改 UI,该方法专注于 JTableHeader 以阻止 UI 发出的命令。

      首先,为了阻止拖动第一列,我们需要一个来自 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);
      }
      

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

      所以我还在找,不知道有没有人有关于这部分的建议。

      【讨论】:

      • 干得好。请让我知道您对我的建议的看法,并再次感谢您的详细解释。
      【解决方案6】:

      起初,我使用了 Gnoupi 的最后一个建议,即继承 TableColumnModel 并覆盖 moveColumn,但仍然有一些烦人的跳转。

      这是“我的”完全有效且经过测试的解决方案,没有令人讨厌的跳跃,它主要依赖于 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) {
              }
          });
      

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

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

      illegalTableColumnMoveFromIndex 是检测到最近一次非法移动时被移动的列的索引。

      illegalTableColumnMoveToIndex 是检测到最近一次非法移动时被移动到的列的索引。

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

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

      我承认它可能没有 kleopatra 的解决方案那么强大。感谢大家的帮助,我真的很感激,我很高兴看到协作工作很有效:)

      【讨论】:

        【解决方案7】:

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

        1. 将 XPStyle 类复制给自己。
        2. 扩展WindowsTableHeaderUI。看看source code
        3. 使用它:getTableHeader().setUI(new TreeTableWindowsTableHeaderUI());

        感谢 Gnoupi 的努力。

        【讨论】:

          【解决方案8】:

          我将在移动完成后将列放回原处。就像这样。

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

          【讨论】:

            猜你喜欢
            • 2013-07-08
            • 2013-03-21
            • 2020-12-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-04-07
            • 1970-01-01
            • 2013-02-12
            相关资源
            最近更新 更多