Home » Java » Desktop Java » JavaFX Tip 15: ListView Autoscrolling

About Dirk Lemmermann

JavaFX Tip 15: ListView Autoscrolling

I recently had to implement autoscrolling functionality for FlexGanttFX and thought that my solution might be useful for others. You find the basic concepts of it in the listing below. The main idea is that a background thread is used to adjust the pixel location of the virtual flow node used by the list view. The thread starts when a drag over is detected “close” to the top or bottom edges. “Close” is defined by a proximity variable.

This code can obviously be improved by using a property for the proximity value and the types “Task” and “Service” for the threading work.
 
 

package com.dlsc;

import javafx.application.Platform;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.Region;

/*
 * Yes, unfortunately we need to use private API for this.
 */
import com.sun.javafx.scene.control.skin.VirtualFlow;

public class AutoscrollListView<T> extends ListView<T> {

	final double proximity = 20;

	public AutoscrollListView() {
		addEventFilter(MouseEvent.DRAG_DETECTED,
                      evt -> startDrag());
		addEventFilter(DragEvent.DRAG_OVER,
                      evt -> autoscrollIfNeeded(evt));
		addEventFilter(DragEvent.DRAG_EXITED,
                      evt -> stopAutoScrollIfNeeded(evt));
		addEventFilter(DragEvent.DRAG_DROPPED,
                      evt -> stopAutoScrollIfNeeded(evt));
		addEventFilter(DragEvent.DRAG_DONE,
                      evt -> stopAutoScrollIfNeeded(evt));
	}

	private void startDrag() {
		Dragboard db = startDragAndDrop(TransferMode.MOVE);
		ClipboardContent content = new ClipboardContent();

		/*
		 * We have to add some content, otherwise drag over
                 * will not be called.
		 */
		content.putString("dummy");
		db.setContent(content);
	}

	private void autoscrollIfNeeded(DragEvent evt) {
		evt.acceptTransferModes(TransferMode.ANY);

		/*
		 * Determine the "hot" region that will trigger automatic scrolling.
		 * Ideally we use the clipped container of the list view skin but when
		 * the rows are empty the dimensions of the clipped container will be
		 * 0x0. In this case we try to use the virtual flow.
		 */
		Region hotRegion = getClippedContainer();
		if (hotRegion.getBoundsInLocal().getWidth() < 1) {
			hotRegion = this;
			if (hotRegion.getBoundsInLocal().getWidth() < 1) {
				stopAutoScrollIfNeeded(evt);
				return;
			}
		}

		double yOffset = 0;

		// y offset

		double delta = evt.getSceneY() -
                                  hotRegion.localToScene(0, 0).getY();
		if (delta < proximity) {
			yOffset = -(proximity - delta);
		}

		delta = hotRegion.localToScene(0, 0).getY() +
                                  hotRegion.getHeight() -
				  evt.getSceneY();
		if (delta < proximity) {
			yOffset = proximity - delta;
		}

		if (yOffset != 0) {
			autoscroll(yOffset);
		} else {
			stopAutoScrollIfNeeded(evt);
		}
	}

	private VirtualFlow<?> getVirtualFlow() {
		return (VirtualFlow<?>) lookup("VirtualFlow");
	}

	private Region getClippedContainer() {

		/*
		 * Safest way to find the clipped container. lookup() does not work at
		 * all.
		 */
		for (Node child :
                             getVirtualFlow().getChildrenUnmodifiable()) {
			if (child.getStyleClass().
                                       contains("clipped-container")) {
				return (Region) child;
			}
		}

		return null;
	}

	class ScrollThread extends Thread {
		private boolean running = true;
		private double yOffset;

		public ScrollThread() {
			super("Autoscrolling List View");
			setDaemon(true);
		}

		@Override
		public void run() {

			/*
			 * Some initial delay, especially useful when
                         * dragging something in from the outside.
			 */

			try {
				Thread.sleep(300);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}

			while (running) {

				Platform.runLater(() -> {
					scrollY();
				});

				try {
					sleep(15);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		private void scrollY() {
			VirtualFlow<?> flow = getVirtualFlow();
			flow.adjustPixels(yOffset);
		}

		public void stopRunning() {
			this.running = false;
		}

		public void setDelta(double yOffset) {
			this.yOffset = yOffset;
		}
	}

	private ScrollThread scrollThread;

	private void autoscroll(double yOffset) {
		if (scrollThread == null) {
			scrollThread = new ScrollThread();
			scrollThread.start();
		}

		scrollThread.setDelta(yOffset);
	}

	private void stopAutoScrollIfNeeded(DragEvent evt) {
		if (scrollThread != null) {
			scrollThread.stopRunning();
			scrollThread = null;
		}
	}
}
Reference: JavaFX Tip 15: ListView Autoscrolling from our JCG partner Dirk Lemmermann at the Pixel Perfect blog.

Do you want to know how to develop your skillset to become a Java Rockstar?

Subscribe to our newsletter to start Rocking right now!

To get you started we give you our best selling eBooks for FREE!

 

1. JPA Mini Book

2. JVM Troubleshooting Guide

3. JUnit Tutorial for Unit Testing

4. Java Annotations Tutorial

5. Java Interview Questions

6. Spring Interview Questions

7. Android UI Design

 

and many more ....

 

Receive Java & Developer job alerts in your Area

I have read and agree to the terms & conditions

 

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments