Java 如何避免在JTable中对单个列重新排序?
我有一个JTable,我需要能够对列重新排序。但是,我希望第一列不能重新排序。我使用以下选项来启用重新排序:Java 如何避免在JTable中对单个列重新排序?,java,swing,jtable,Java,Swing,Jtable,我有一个JTable,我需要能够对列重新排序。但是,我希望第一列不能重新排序。我使用以下选项来启用重新排序: table.getTableHeader().setReorderingAllowed(true); 这些列现在可以重新排序,包括我不想要的第一列。有没有办法锁定第一列 我见过一些解决方案使用两个表,第一列在一个单独的表中,但可能有更好/更简单的方法。我认为需要重写TableColumnModelListener中的columnMoved方法。TableColumnModelEvent
table.getTableHeader().setReorderingAllowed(true);
这些列现在可以重新排序,包括我不想要的第一列。有没有办法锁定第一列
我见过一些解决方案使用两个表,第一列在一个单独的表中,但可能有更好/更简单的方法。我认为需要重写TableColumnModelListener中的columnMoved方法。TableColumnModelEvent类有一个getFromIndex方法,您应该可以查看该方法以确定它是否是固定列,然后可以取消该事件
希望有帮助。A首先,您需要定义一种更好、更简单的方法。您不喜欢2表方法的哪些方面 不能使用TableColumnModelListener,因为事件是在列已被移动后激发的 在BasicTableHeaderUI中可以找到用于拖动列的代码。因此,您可以尝试重写那里的代码,但随后需要对所有LAF执行此操作 上述代码在mousePressed事件上调用JTableHeader.getReorderingAllowed,以确定是否允许列重新排序。我想您可以在JTableHeader中重写该方法,或者使用MouseInfo类获取当前鼠标位置,以确定它是否位于第一列上,然后返回false。但是现在您还需要创建一个使用自定义表头的自定义JTable 当然,有了上述任何一个建议,您都可以防止移动第一列。但不要忘记,还需要防止第二列插入到第一列之前。我认为这个问题没有简单的答案
这是我用两个表实现的版本。更好吗?我不知道,但它很简单,因为它只有一行代码可以使用。我也有同样的问题,我正在搜索它。到目前为止,我找到了两种方法 if我自己重写它的方法:从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中有两个要修改的位置:
在鼠标按下的情况下,调用canMove而不是header.getReorderingAllowed。这样可以确保不应移动的列不会移动。
但这还不够,我们需要防止在拖动另一根柱子时移动不动的柱子。在获取newColumnIndex时,还需要更改鼠标标记:
如果0@Override
public void setDraggedColumn(TableColumn pAColumn)
{
int lIndex = -1;
if (pAColumn != null)
lIndex = getColumnModel().getColumnIndex(pAColumn.getIdentifier());
if (lIndex != 0)
super.setDraggedColumn(pAColumn);
}
@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);
}
这将防止用户拖动第一列。但正如前面所描述的,这只是问题的一部分,我们需要防止另一个拖动的列与第一个列交换
到目前为止,我还没有一个正确的方法。我尝试将TableColumnModel子类化,并重写moveColumn方法:
@Override
public void setDraggedColumn(TableColumn pAColumn)
{
int lIndex = -1;
if (pAColumn != null)
lIndex = getColumnModel().getColumnIndex(pAColumn.getIdentifier());
if (lIndex != 0)
super.setDraggedColumn(pAColumn);
}
@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方法中的鼠标位置,您将从拖动的列跳转到另一个位置
所以我还在搜索,想知道是否有人对这一部分有意见。我使用了“让我们试着用我们实际拥有的东西来阻止正常行为”的方法。Gnoupi说他没有解决问题的第二部分。以下是仅适用于Windows XP L&F的解决方案: 将XPStyle类复制到您自己。 扩展WindowsTableHeaderUI。请看下面的图片。 使用它:getTableHeader.setUInew TreeTableWindowsTableHeaderUI;
感谢Gnoupi的努力。这是我用来防止第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年后,仍然没有选择 imal解决方案随处可见 另一种防止在第一列上拖动第一列和其他列的次优方法是在uidelegate安装的mouseInputListener能够处理mouseEvents之前拦截mouseEvents 合作者 自定义MouseMotionListener,它将所有事件委托给最初安装的,但拖动的事件除外,前提是它将导致第一列之上的另一列 将原始文件替换为自定义文件 每当LAF更改时更新替换,因为原始LAF由ui控制。这需要对JTableHeader进行子类化,并在updateUI中进行连接 自定义鼠标输入侦听器:
/**
* 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);
}
}
}
起初,我使用了最后一个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中的代码足以防止冻结列被拖动,其余代码允许防止另一列被拖动到冻结列
当我扩展WindowsTableHeaderUI时,它与Microsoft Windows下的一样工作,而是使用反射API设置表头UI的鼠标输入侦听器,调用uninstallerListeners,最后调用header.addMouseListenermouseInputListener和header.addMouseMotionListenermouseInputListener,以便跨平台驱动我的解决方案,而无需对每个表头UI的类的名称做任何假设
我承认它可能比克利奥帕特拉的解决方案要差一点。感谢大家的帮助,我真的很感激,我也很高兴看到协作工作能够奏效:我会在移动完成后将专栏放回原处。差不多吧
@Override
public void moveColumn(int from, int to) {
super.moveColumn(from, to);
if (from == 0 || to == 0) {
super.moveColumn(to, from);
}
}
我通过你的问题得到了70%的答案,并开始写一个回复,告诉你使用两个表格…然后我读完了你的文章。你是说第一列应该独立于其他列?他说的是移动整个列,左右。哦,我在想排序。这更有意义。是的,将列向左或向右移动,而不是对列中的数据进行排序这看起来是最好的方法。我一直在玩弄它,我的问题是,因为我是Java新手,如何取消事件?您可以选择是否将方法调用传递给超类。否则,它将不会应用于表。您必须对TableColumnModel而不是TableColumnModelListener的实现进行此更改,覆盖moveColumnint columnIndex、int newIndex。您无法取消该事件,即使MouseeEvent.consume无效。TableColumnModelListener没有超类,因为它是一个接口。正如@gouessej所说,TableColumnModelListener是一个接口——没有super.columnMoved可调用或不可调用。答案是错误的。我猜,他可能不喜欢2tables方法,因为它打破了数据模型。这将迫使最终在两个模型中分离数据,在JTable和图形方面确实很简单,但在模型方面更烦人。这并不强迫您使用两个模型。只有OP知道他的要求是什么。我们无法理解他的想法。你不需要两个模型,你可以很容易地从同一个表模型中使用两个表的方法。无论是从概念角度还是从维护角度来看,这都是最简单的方法。干得好。请让我知道您对我的建议的看法,再次感谢您的详细解释。