Stefan S.
Stefan S.

Reputation: 4103

Scroll Table on Drag

I want to have a table as a drop target were it's actually relevant where the item is dropped. So users might want to readjust the visible items after they already started dragging.

The default behavior I'm missing is: hover over the last visible line and scroll the table down. Hover over the first visible line and scroll up.

So what I'm doing now is I'm trying to scroll the icon the user hovers over into the center of the table:

    final Display display = new Display();
    final Shell shell = new Shell(display);
    shell.setLayout(new GridLayout());

    // drag source: the label

    final Label label = new Label(shell, SWT.NONE);
    label.setText("Drag from me!");
    label.setDragDetect(true);
    label.setLayoutData(GridDataFactory.fillDefaults().grab(true, false).create());

    final DragSource source = new DragSource(label, DND.DROP_MOVE | DND.DROP_COPY);
    source.setTransfer(new Transfer[]{TextTransfer.getInstance()});
    source.addDragListener(new DragSourceAdapter() {
        @Override
        public void dragSetData(DragSourceEvent event) {
            event.data = label.getText();
        }
    });

    // drop target: the viewer

    final TableViewer viewer = new TableViewer(shell,
            SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
    viewer.setContentProvider(ArrayContentProvider.getInstance());
    viewer.setLabelProvider(new ColumnLabelProvider());
    viewer.setInput(IntStream.range(1, 100).mapToObj(String::valueOf).collect(Collectors.toList()));
    viewer.getControl().setLayoutData(GridDataFactory.fillDefaults().grab(true, true).hint(100, 100).create());

    final DropTarget target = new DropTarget(viewer.getTable(), DND.DROP_MOVE | DND.DROP_COPY | DND.DROP_DEFAULT);
    target.setTransfer(new Transfer[]{TextTransfer.getInstance()});
    target.addDropListener(new DropTargetAdapter() {

        private static final int TIME_BETWEEN_MOVEMENTS = 700;
        private long lastMovementTime;

        @Override
        public void dragOver(DropTargetEvent event) {
            final Table table = viewer.getTable();
            final TableItem item = table.getItem(table.toControl(new Point(event.x, event.y)));
            if (item != null) {
                final long currentTime = System.currentTimeMillis();
                if (currentTime > this.lastMovementTime + TIME_BETWEEN_MOVEMENTS) {
                    table.setTopIndex(Math.max(0, table.indexOf(item) - getVisibleItems(table) / 2));
                    this.lastMovementTime = currentTime;
                }
                table.setSelection(item);
            }
        }

        private int getVisibleItems(Table table) {
            return (table.getClientArea().height - table.getHeaderHeight()) / table.getItemHeight();
        }
    });

    // open everything

    shell.pack();
    shell.open();

    while (!shell.isDisposed()) {
        if (!display.readAndDispatch()) {
            display.sleep();
        }
    }
    display.dispose();

This scrolls quickly for tall tables and not for small ones, so I'm wondering if there is any better way, since this is a very common use case in most of the software I'm using.

How do I scroll a table while dragging?

Upvotes: 0

Views: 893

Answers (1)

Baz
Baz

Reputation: 36884

Ok, I experimented a bit and came up with a solution that should at least give you a good starting point. What it does is, it listens to various mouse events and remembers the items that have been selected. When the edge (top or bottom) is reached, it'll move the table up/down by one item.

private static boolean dragging = false;
private static int     start    = -1;
private static int     end      = -1;

public static void main(String[] args)
{
    final Display display = new Display();
    final Shell shell = new Shell(display);
    shell.setLayout(new FillLayout());

    List<String> input = IntStream.range(1, 100)
                                  .mapToObj(String::valueOf)
                                  .collect(Collectors.toList());

    final TableViewer viewer = new TableViewer(shell, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.FULL_SELECTION | SWT.BORDER);
    viewer.setContentProvider(ArrayContentProvider.getInstance());
    viewer.setLabelProvider(new ColumnLabelProvider());
    viewer.setInput(input);

    Table table = viewer.getTable();
    // Stop dragging when the mouse is released
    table.addListener(SWT.MouseUp, e -> dragging = false);
    // Start dragging when the mouse is clicked
    table.addListener(SWT.MouseDown, e -> {
        dragging = true;
        start = -1;
        end = -1;
    });
    // Listen to mouse movement
    table.addListener(SWT.MouseMove, e -> {
        // If we're currently dragging
        if (dragging)
        {
            // Get the item under the mouse pointer
            TableItem item = table.getItem(new Point(e.x, e.y));

            if(item != null)
            {
                int current = table.indexOf(item);

                // If this is the first selected item, remember the position
                if (start == -1 && end == -1)
                {
                    start = current;
                    end = current;
                }
                // Else, just remember the "end" of the drag
                else
                    end = current;

                // Tell the viewer to select the items
                viewer.setSelection(new StructuredSelection(input.subList(Math.min(start, end), Math.max(start, end))));

                // Now, we actually move the table when the drag movement reaches the top or bottom
                int tableHeight = table.getClientArea().height;
                int itemHeight = table.getItemHeight();

                if (e.y < itemHeight && current > 0)
                    table.showItem(table.getItem(current - 1));
                else if (e.y > tableHeight - itemHeight && current < table.getItemCount() - 1)
                    table.showItem(table.getItem(current + 1));
            }
        }
    });

    shell.pack();
    shell.open();
    shell.setSize(400, 300);

    while (!shell.isDisposed())
    {
        if (!display.readAndDispatch())
        {
            display.sleep();
        }
    }
    display.dispose();
}

Upvotes: 2

Related Questions