简体   繁体   中英

How to make JTable click on unselected do a drag instead of a select

If you have JTable set with table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION) and then you click drag on a row that is not already selected, it starts selecting multiple rows. We don't want that behavior. We want it so if you click on a node, even if it's not already selected, it will start dragging it.

We do need the multi select mode on, so setting it to single select (which does result in the behavior we want) is not an option.

Update: At this point, it appears it will require some type of ugly hack since the logic is in a private method BasicTableUI$Handler.canStartDrag

Unfortunately none of the other answers worked for me.

So I made my own hack/fix for the problem (I'm posting it here for others with the same problem):

public class SFixTable extends JTable {
private static final long serialVersionUID = 1082882838948078289L;

boolean pressed = false;
int  currSRow = -100;

public SFixTable(TableModel dm) {
    super(dm);
}

public SFixTable() {
    super();
}

public SFixTable(Vector<?> rowData, Vector<?> columnNames) {
    super(rowData, columnNames);
}

@Override
protected void processMouseEvent(MouseEvent e) {
    int row = rowAtPoint(e.getPoint());
    int col = columnAtPoint(e.getPoint());
    if (SwingUtilities.isLeftMouseButton(e) && !e.isShiftDown() && !e.isControlDown()) {
        boolean isDragRelease = (e.getID() == MouseEvent.MOUSE_RELEASED) && row != currSRow;
        boolean isStartClick = (e.getID() == MouseEvent.MOUSE_PRESSED);

        if (row >= 0 && col >= 0) {
            if (isStartClick) {
                super.changeSelection(row, col, false, false);
            } else if (isDragRelease) {
                super.changeSelection(currSRow, col, false, false);
            }
        }
        pressed = (e.getID() == MouseEvent.MOUSE_PRESSED);
        if (pressed) {
            currSRow = row;
        } else {
            currSRow = -100;
        }
    }

    super.processMouseEvent(e);
}

@Override
public boolean isCellSelected(int row, int col) {
    return (pressed)? (row == currSRow) : super.isCellSelected(row, col);
}

}

I found one possible solution. You bracket the mouse listeners so you can lie to the call to isCellSelected during the canStartDrag call.

Subclass JTable (or in my case, JXTreeTable). In the constructor call this:

private void setupSelectionDragHack()
{
    // Bracket the other mouse listeners so we may inject our lie
    final MouseListener[] ls = getMouseListeners();
    for (final MouseListener l : ls)
    {
        removeMouseListener(l);
    }
    addMouseListener(new MouseAdapter()
    {
        @Override
        public void mousePressed(final MouseEvent e)
        {
            // NOTE: it might not be necessary to check the row, but... I figure it's safer maybe?
            mousingRow = rowAtPoint(e.getPoint());
            mousingInProgress = true;
        }
    });
    for (final MouseListener l : ls)
    {
        addMouseListener(l);
    }
    addMouseListener(new MouseAdapter()
    {
        @Override
        public void mousePressed(final MouseEvent e)
        {
            mousingInProgress = false;
        }
    });
}

And then you'll need this:

@Override
public boolean isCellSelected(final int row, final int column)
{
    if (mousingInProgress && row == mousingRow)
    {
        // Only lie to the canStartDrag caller. We tell the truth to everyone else.
        final StackTraceElement[] elms = Thread.currentThread().getStackTrace();
        for (int i = 0; i < 3; i++)
        {
            if (elms[i].getMethodName().equals("canStartDrag"))
            {
                return mousingInProgress;
            }
        }
    }
    return super.isCellSelected(row, column);
}

It's an ugly hack in many ways, but... for now it seems to work.

If what you are looking for is to drag an unselected row in a single selection JTable, setting the table's selection model to SINGLE_SELECTION mode is not enough, you also have to set the column model's selection mode.

JTable table = new JTable();
table.getSelectionModel()
     .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.getColumnModel().getSelectionModel()
     .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

It's a bug:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6349223

and as you already assumed, it requires some ugly hack. Here's one (not from me, but from a user Aephyr on old sun forums which didn't survive the migration to OTN)

    table = new JTable() {
        // fix for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6349223
        // requirement is the option to turn off drag-selection if dragEnabled
        // fix posted in sun dev forum by Aephyr
        // http://forums.sun.com/thread.jspa?threadID=5436355&tstart=0
        private boolean pressed;

        @Override
        protected void processMouseEvent(MouseEvent e) {
                pressed = e.getID() == MouseEvent.MOUSE_PRESSED;
                if (pressed && !e.isShiftDown() && !e.isControlDown())
                        clearSelection();
                try {
                        super.processMouseEvent(e);
                } finally {
                        pressed = false;
                }
        }

        @Override
        public boolean isCellSelected(int row, int col) {
                return pressed ? true : super.isCellSelected(row, col);
        }

    };

Similar to kleopatra's answer, but this seems to handle a few issues with the previous one -- you can control-click to both add and remove items from a multiple selection, and you can successfully drag a multi-select group. I've tested this only with an ETable/Outline from NetBeans, but should work with a regular JTable.

table = new JTable() {
    private boolean inPress = false;

    @Override protected void processMouseEvent(MouseEvent e) {
        inPress = e.getID() == MouseEvent.MOUSE_PRESSED && e.getButton() == MouseEvent.BUTTON1 && !e.isShiftDown() && !e.isControlDown();
        try {
            super.processMouseEvent(e);
        } finally {
            inPress = false;
        }
    }

    @Override public boolean isCellSelected(int row, int col) {
        boolean selected = super.isCellSelected(row, col);
        if (inPress) {
            if (!selected)
                clearSelection();
            return true;
        }

        return selected;
    }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM