Reputation: 6999
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
Upvotes: 4
Views: 2619
Reputation: 36
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);
Upvotes: 1
Reputation: 893
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);
}
}
Upvotes: 3
Reputation: 164
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;
}
};
Upvotes: 1
Reputation: 51525
It's a bug:
https://bugs.java.com/bugdatabase/view_bug?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 https://bugs.java.com/bugdatabase/view_bug?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);
}
};
Upvotes: 1
Reputation: 6999
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.
Upvotes: 3