Scroll horizontally when dragging/reordering columns in the TableView - java

In JavaFX's TableView (and TreeTableView) it's really hard to reorder columns using drag & drop when the horizontal scrollbar is present, because the table doesn't scroll automatically when one want's to drag the column to the currently not visible (off the scroll pane viewport) position.
I've noticed that there are already a bug (enhancement) reports for this:
https://bugs.openjdk.java.net/browse/JDK-8092314
https://bugs.openjdk.java.net/browse/JDK-8092355
https://bugs.openjdk.java.net/browse/JDK-8213739
... but as it haven't been tackled for quite some time I am wondering whether there is any other way to achieve the same behavior using the current API.
There is SSCCE:
public class TableViewColumnReorderDragSSCCE extends Application {
public static final int NUMBER_OF_COLUMNS = 30;
public static final int MAX_WINDOW_WIDTH = 480;
#Override
public void start(Stage stage) {
stage.setScene(new Scene(createTable()));
stage.show();
stage.setMaxWidth(MAX_WINDOW_WIDTH);
}
private TableView<List<String>> createTable() {
final TableView<List<String>> tableView = new TableView<>();
initColumns(tableView);
return tableView;
}
private void initColumns(TableView<List<String>> tableView) {
for (int i=0; i<NUMBER_OF_COLUMNS; i++) {
tableView.getColumns().add(new TableColumn<>("Column " + i));
}
tableView.getItems().add(Collections.emptyList());
}
}
Steps to reproduce:
Run the above SSCCE
Try to drag Column 0 after Column 29
I am after a fully functional solution (if any).

As no complete solution was provided I've came up with one of my own. I've introduced a (ColumnsOrderingEnhancer) implementation which will enhance the table view columns reordering by automatic scrolling (when needed).
Usage (with the table view defined in the above SSCCE):
// Enhance table view columns reordering
final ColumnsOrderingEnhancer<List<String>> columnsOrderingEnhancer = new ColumnsOrderingEnhancer<>(tableView);
columnsOrderingEnhancer.init();
ColumnsOrderingEnhancer implementation:
public class ColumnsOrderingEnhancer<T> {
private final TableView<T> tableView;
public ColumnsOrderingEnhancer(TableView<T> tableView) {
this.tableView = tableView;
}
public void init() {
tableView.skinProperty().addListener((observable, oldSkin, newSkin) -> {
// This can be done only when skin is ready
final TableHeaderRow header = (TableHeaderRow) tableView.lookup("TableHeaderRow");
final MouseDraggingDirectionHelper mouseDraggingDirectionHelper = new MouseDraggingDirectionHelper(header);
final ScrollBar horizontalScrollBar = getTableViewHorizontalScrollbar();
// This is the most important part which is responsible for scrolling table during the column dragging out of the viewport.
header.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> {
final double totalHeaderWidth = header.getWidth();
final double xMousePosition = event.getX();
final MouseDraggingDirectionHelper.Direction direction = mouseDraggingDirectionHelper.getLastDirection();
maybeChangeScrollBarPosition(horizontalScrollBar, totalHeaderWidth, xMousePosition, direction);
});
});
}
private void maybeChangeScrollBarPosition(ScrollBar horizontalScrollBar, double totalHeaderWidth, double xMousePosition, MouseDraggingDirectionHelper.Direction direction) {
if (xMousePosition > totalHeaderWidth && direction == RIGHT) {
horizontalScrollBar.increment();
}
else if (xMousePosition < 0 && direction == LEFT) {
horizontalScrollBar.decrement();
}
}
private ScrollBar getTableViewHorizontalScrollbar() {
Set<Node> scrollBars = tableView.lookupAll(".scroll-bar");
final Optional<Node> horizontalScrollBar =
scrollBars.stream().filter(node -> ((ScrollBar) node).getOrientation().equals(Orientation.HORIZONTAL)).findAny();
try {
return (ScrollBar) horizontalScrollBar.get();
}
catch (NoSuchElementException e) {
return null;
}
}
/**
* A simple class responsible for determining horizontal direction of the mouse during dragging phase.
*/
static class MouseDraggingDirectionHelper {
private double xLastMousePosition = -1;
private Direction direction = null;
MouseDraggingDirectionHelper(Node node) {
// Event filters that are determining when scrollbar needs to be incremented/decremented
node.addEventFilter(MouseEvent.MOUSE_PRESSED, event -> xLastMousePosition = event.getX());
node.addEventFilter(MouseEvent.MOUSE_DRAGGED, event -> {
direction = ((event.getX() - xLastMousePosition > 0) ? RIGHT : LEFT);
xLastMousePosition = event.getX();
});
}
enum Direction {
LEFT,
RIGHT
}
public Direction getLastDirection() {
return direction;
}
}
}
End result (which works surprisingly well):

It's not impossible to work around. You could start with something like this, though it is a very crude implementation, I'm sure in principle it can be refined to something reasonable:
tableView.setOnMouseExited(me -> {
if (me.isPrimaryButtonDown()) { // must be dragging
Bounds tvBounds = tableView.getBoundsInLocal();
double x = me.getX();
if (x < tvBounds.getMinX()) {
// Scroll to the left
tableView.scrollToColumnIndex(0);
} else if (x > tvBounds.getMaxX()) {
// Scroll to the right
tableView.scrollToColumnIndex(tableView.getColumns().size()-1);
}
}
});
In a proper implementation you would likely have to sneak around the Node hierarchy and find the width of the table columns and determine what the next out-of-view column is so you can scroll to the exact right column. Remember when you did that so you can do it again if the user continues to drag outside the table, but not too fast.
EDIT: Based on your self-answer, here is my take on it. I've compacted your code a bit and made it work on JavaFX 8.0:
static class TableHeaderScroller implements EventHandler<MouseEvent> {
private TableView tv;
private Pane header;
private ScrollBar scrollBar;
private double lastX;
public static void install(TableView tv) {
TableHeaderScroller ths = new TableHeaderScroller(tv);
tv.skinProperty().addListener(ths::skinListener);
}
private TableHeaderScroller(TableView tv) {
this.tv = tv;
}
private void skinListener(ObservableValue<? extends Skin<?>> observable, Skin<?> oldValue, Skin<?> newValue) {
if (header != null) {
header.removeEventFilter(MouseEvent.MOUSE_DRAGGED, this);
}
header = (Pane) tv.lookup("TableHeaderRow");
if (header != null) {
tv.lookupAll(".scroll-bar").stream().map(ScrollBar.class::cast)
.filter(sb -> sb.getOrientation() == Orientation.HORIZONTAL)
.findFirst().ifPresent( sb -> {
TableHeaderScroller.this.scrollBar = sb;
header.addEventFilter(MouseEvent.MOUSE_DRAGGED, TableHeaderScroller.this);
});
}
}
#Override
public void handle(MouseEvent event) {
double x = event.getX();
double sign = Math.signum(x - lastX);
lastX = x;
int dir = x < 0 ? -1 : x > header.getWidth() ? 1 : 0;
if (dir != 0 && dir == sign) {
if (dir < 0) {
scrollBar.decrement();
} else {
scrollBar.increment();
}
}
}
}

Related

Drag Cursor not showing on OSX

We have a large Java app that is used on both Windows and OSX.
We do custom Drag and Drop between 2 of our JTables.
On Windows, this works perfectly. The custom cursor is displayed as you drag over the target JTable.
On the Mac, the custom cursor is never displayed. Instead a gray rectangle (border only) is displayed when you start dragging. This rectangle is the width of the table column, and the height of the table. Our logging is showing that the dragOver() and dropActionChanged() methods are getting called, and we are setting the custom cursor. It just never gets displayed.
If I disable our custom cursor code, the same box is displayed - but it has the Circle/slash icon in the middle as well.
I want to get rid of the weird box, and display the custom cursor.
Excerpts from the code:
private class FileTransferHandler extends TransferHandler {
private static final long serialVersionUID = 1L;
private final Logger log = LogManager.getLogger();
private final CursorDragSourceListener dragSourceListener = new CursorDragSourceListener();
// Left out the Drop handling code that was here
#Override
public int getSourceActions( final JComponent c) {
log.debug("FileTransferHandler.getSourceAction: ");
return COPY | MOVE;
}
#Override
protected Transferable createTransferable( final JComponent c) {
log.debug("FileTransferHandler.createTransferable:");
List<iFilePage> pages = new ArrayList<iFilePage>();
// Left out the code that builds the pages list
DragSource.getDefaultDragSource().addDragSourceListener(dragSourceListener);
dragSourceListener.setCursorChoice(pages.size() == 1);
return new FilePageTransferable(pages);
}
#Override
protected void exportDone( final JComponent c,
final Transferable t,
final int action) {
log.debug("FileTransferHandler.exportDone: {}", action, t);
tblFixed.getSelectionModel().clearSelection();
DragSource.getDefaultDragSource().removeDragSourceListener(dragSourceListener);
return;
}
}
private static class CursorDragSourceListener implements DragSourceListener {
private Cursor singlePage = null;
private Cursor multiPage = null;
private Cursor badSinglePage = null;
private Cursor useCursor = null;
private boolean useSingle = false;
public CursorDragSourceListener() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
URL url;
String name;
Image img;
url = FileUtils.getResourceURL("/images/page.png");
name = "DragPage";
img = toolkit.createImage(url);
singlePage = toolkit.createCustomCursor(img, new Point(16, 16), name);
url = FileUtils.getResourceURL("/images/badpage_stack.png");
name = "DragBadPage";
img = toolkit.createImage(url);
badSinglePage = toolkit.createCustomCursor(img, new Point(16, 16), name);
url = FileUtils.getResourceURL("/images/page_stack.png");
name = "DragPageStack";
img = toolkit.createImage(url);
multiPage = toolkit.createCustomCursor(img, new Point(16, 16), name);
return;
}
public void setCursorChoice( final boolean single) {
log.debug("CursorDragSourceListener.setCursorChoice: {}", single);
useSingle = single;
if (useSingle) {
useCursor = singlePage;
} else {
useCursor = multiPage;
}
return;
}
#Override
public void dropActionChanged( final DragSourceDragEvent dsde) {
log.debug("CursorDragSourceListener.dropActionChanged: {}", dsde.getDropAction(), useSingle);
if (dsde.getDropAction() == 2) {
if (!useSingle) {
useCursor = badSinglePage;
} else {
useCursor = singlePage;
}
} else {
if (useSingle) {
useCursor = singlePage;
} else {
useCursor = multiPage;
}
}
dsde.getDragSourceContext().setCursor(useCursor);
return;
}
#Override
public void dragOver( final DragSourceDragEvent dsde) {
try {
Object x = dsde.getDragSourceContext().getTransferable()
.getTransferData(DataFlavor.javaFileListFlavor);
log.trace("CursorDragSourceListener.dragOver: {}", (x != null) ? x.getClass().getSimpleName() : "null");
if (x instanceof ArrayList) {
dsde.getDragSourceContext().setCursor(useCursor);
}
} catch (Exception e) {
log.error("CursorDragSourceListener.dragOver:", e);
}
}
#Override
public void dragExit( final DragSourceEvent dse) {
}
#Override
public void dragEnter( final DragSourceDragEvent dsde) {
}
#Override
public void dragDropEnd( final DragSourceDropEvent dsde) {
}
}
After a bunch more checking and analysis, it turns out that our Custom Selection Model was causing this problem on OSX.
We have a selection model that allows you to select multiple individual cells, not just whole rows.
So the getMinSelectionindex() and getMaxSelectionIndex() methods returned dummy data, since we never used them.
That works fine on MS Win, but apparently the OSX drag and drop for JTable uses those calls.
After modifying our code to return reasonable values, the selection box is no longer as tall as the table.
The custom cursors appear most of the time, but still randomly disappear for no apparent reason.

TableView - Move up and Move down functionality of a cell

I have a situation where I have a TableView. I'm trying to implement a feature that allows a cell to be moved up or down. After moving the cell up or down (with the cell content), I want to change focus to the new location of the cell.
The problem is that it doesn't change to the new location. It for some reason stays in the original selected cell location.
This is the code used to move up, move down and change focus:
I am attempting to move a single selected cell.
public class TableController
{
private ObservableList<SimpleStringProperty> observablePrnPropertyData;
#FXML
private TableView<SimpleStringProperty> table;
#FXML
private TableColumn<SimpleStringProperty, String> data;
#FXML
private void initialize()
{
this.data.setCellValueFactory(cellData -> cellData.getValue());
this.data.setCellFactory(event -> new EditCell(this.observablePrnPropertyData, this.table));
}
public void display(final PrnProperty prnProperty)
{
this.observablePrnPropertyData = PrnPropertyUtil.getObservableDataFromPrnProperty(prnProperty);
this.table.setItems(this.observablePrnPropertyData);
}
private final class EditCell extends TableCell<SimpleStringProperty, String>
{
#Override
public void updateItem(String item, boolean empty)
{
super.updateItem(item, empty);
if (empty)
{
this.setText(null);
this.setGraphic(null);
}
else
{
this.setUpContextMenu();
this.setText(this.getString());
this.setGraphic(null);
}
}
private void setUpContextMenu()
{
// Context menu
ContextMenu contextMenu = new ContextMenu();
// context menu Move up
this.moveUp = new MenuItem("Move up");
this.moveUp.setOnAction(event -> this.moveUp(this.table, this.observablePrnPropertyData));
contextMenu.getItems().add(this.moveUp);
// Context menu for move down
this.moveDown = new MenuItem("Move down");
this.moveDown.setOnAction(event -> this.moveDown(this.table, this.observablePrnPropertyData));
contextMenu.getItems().add(this.moveDown);
// Add context menu
this.setContextMenu(contextMenu);
}
public void moveUp(final TableView<?> table, ObservableList listToManipulate)
{
final int selectedIndex = table.getSelectionModel().getSelectedIndex();
final Object removeItem = listToManipulate.remove(selectedIndex);
final int newIndex = selectedIndex - 1;
listToManipulate.add(newIndex, removeItem);
this.changeTableCellFocus(table, newIndex);
}
public void moveDown(final TableView<?> table, ObservableList listToManipulate)
{
final int selectedIndex = table.getSelectionModel().getSelectedIndex();
final Object remove = listToManipulate.remove(selectedIndex);
final int newIndex = selectedIndex + 1;
listToManipulate.add(newIndex, remove);
this.changeTableCellFocus(table, newIndex);
}
public void changeTableCellFocus(final TableView<?> table, final int focusIndex)
{
table.requestFocus();
table.getSelectionModel().clearAndSelect(focusIndex);
table.getFocusModel().focus(focusIndex);
}
}
}
It would be great if someone can give a working example. I really want to know what i'm doing wrong.
If you just have to focus next and previous line, you can try: table.getFocusModel().focusNext() and .focusPrevious(), like described here: http://docs.oracle.com/javafx/2/api/javafx/scene/control/FocusModel.html
For your case use this code will work fine, it is simple you must first get the selected index, move the item to the next (or previous) row, then select this new row.
void upClicked(){
if(table.getSelectionModel().getSelectedItem() != null) // check if the user really selected a row in the table
{
if(table.getSelectionModel().getSelectedIndex() != 0) // if the row first one so do nothing
{
int index = table.getSelectionModel().getSelectedIndex(); // get the selected row index
SimpleStringProperty x = table.getSelectionModel().getSelectedItem(); // get the selected item
table.getItems().set(index, table.getItems().get(index-1)); // move the selected item up
table.getItems().set(index-1, x); // change the row with the item in above
table.getSelectionModel().select(index-1); // select the new row position
}
}
}
void downClicked(){
if(table.getSelectionModel().getSelectedItem() != null)
{
if(table.getSelectionModel().getSelectedIndex() != table.getItems().size()-1) // if the row is in last so dont do nothing
{
int index = table.getSelectionModel().getSelectedIndex();
SimpleStringProperty x = table.getSelectionModel().getSelectedItem();
table.getItems().set(index, table.getItems().get(index+1));
table.getItems().set(index+1, x);
table.getSelectionModel().select(index+1);
}
}
}

JavaFX OnMouseDragged not getting fired when dragging an Item in a Treeview

Bascially I try to implement the behaviour, that when dragging an item from a treeView and going either at the bottom or top of the treeView, the treeView will automatically scroll down or up.
Thus far I was able to simply extend the TreeView and add a onMouseDragged event handler to it. like this
public ExtendedTreeView()
{
super();
setOnMouseDragged((MouseEvent event) -> onMouseMoved(event));
}
The event handler then looks like this (System outs are bascially just for debug purpose)
private void onMouseMoved(MouseEvent event)
{
System.out.println("onMouseMoved-----------------------------------------------------------------");
System.out.println(event.getEventType().toString());
// -----only apply when there is a drag event in progress
if(event.getEventType().equals(MouseEvent.MOUSE_DRAGGED))
{
// -----first check if we are the scrollEdges
System.out.println(String.format("Size: %f : %f", getWidth(), getHeight()));
System.out.println(String.format("Bounds: %f : %f : %f : %f", _scrollBounds.getTop(), _scrollBounds.getRight(), _scrollBounds.getBottom(), _scrollBounds.getLeft()));
System.out.println(String.format("Node: %f : %f", event.getX(), event.getY()));
//-----up and down directions are preferred
ScrollDirection direction = ScrollDirection.None;
if(event.getY() <= _scrollBounds.getTop())
direction = ScrollDirection.Top;
else if(event.getY() >= _scrollBounds.getBottom())
direction = ScrollDirection.Bottom;
else if(event.getX() >= _scrollBounds.getRight())
direction = ScrollDirection.Right;
else if(event.getX() <= _scrollBounds.getLeft())
direction = ScrollDirection.Left;
System.out.println(String.format("Direction: %s", direction.toString()));
}
}
When I drag and move the mouse in the TreeView it get the desired result.
The Problem is how ever
as soon as I drag an actuall Item, the drag event only occurs once and then never again.
The Item also has a drag and drop handling which essentially looks like this.
Note that this is actually a controller which has info about which TreeCell he belongs to.
#FXML
public void onDragDetected(MouseEvent event)
{
Base item = getTreeCell().getItem();
Dragboard dragboard = getTreeCell().startDragAndDrop(TransferMode.MOVE);
//-----get info from the item and put it in the clipboard
dragboard.setContent(content);
//event.consume();
}
#FXML
public void onDragEntered(DragEvent event)
{
boolean acceptDrag = false;
Dragboard dragboard = event.getDragboard();
Base current = getTreeCell().getItem();
//-----get the info from the clipboard and do something with it
//-----essentially the acceptDrag will be set to true here
if((_isDragAccepted = acceptDrag))
{
event.acceptTransferModes(TransferMode.MOVE);
//event.consume();
}
}
#FXML
public void onDragOver(DragEvent event)
{
if(_isDragAccepted)
{
event.acceptTransferModes(TransferMode.MOVE);
//event.consume();
}
}
#FXML
public void onDragExited(DragEvent event)
{
_isDragAccepted = false;
//event.consume();
}
#SuppressWarnings("unchecked")
#FXML
public void onDragDropped(DragEvent event)
{
Dragboard dragboard = event.getDragboard();
TreeItem<Base> currentItem = getTreeCell().getTreeItem();
Base current = getTreeCell().getItem();
//-----get info from clipboard again and do the necessary stuff
//-----essentially an item will be transfered here from one node to another
event.setDropCompleted(true);
//event.consume();
}
I also got some information about the event handling in javafx from this link
According to this if the event was not consumed, it should bubble up all the way to the source again, hence it should also pass the TreeView should it not ? So I would really like to know what I am doing wrong here.
Ok So I figured out what the problem was,
I was listening to the wrong event, the event I needed to register was the onDragOver event instead of the onMouseDragged event.
So in case anyone ever needs an autoscrollTreeView, the final solution for an autoscroll treeView looks like this now:
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.DragEvent;
import com.sun.javafx.scene.control.skin.VirtualFlow;
#SuppressWarnings("restriction")
public class AutoscrollTreeView<T> extends TreeView<T>
{
// region Enumerations
private enum ScrollDirection
{
None, Top, Bottom
}
// endregion
// region Static
private static final double _milliSecondToSecondFactor = 1.0d / 1000.0d;
// endregion
// region Fields
/**
* the time interval in milliseconds in which the scroll is performed
*/
private final LongProperty _checkInterval = new SimpleLongProperty(50);
/**
* the actual scroll speed when being in the scroll areas
*/
private final DoubleProperty _scrollSpeed = new SimpleDoubleProperty(1.0);
/**
* the scroll speed increment per second the user remain in the scroll area
*/
private final DoubleProperty _scrollSpeedIncrementPerSecond = new SimpleDoubleProperty(0.0);
/**
* distance from the top, which defines the area which will start a scroll in the -y axis
*/
private final DoubleProperty _dragIdentifierTop = new SimpleDoubleProperty();
/**
* distance from the bottom, which defines the area which will start a scroll in the +y axis
*/
private final DoubleProperty _dragIdentifierBottom = new SimpleDoubleProperty();
/**
* time at which the user entered the any scroll area
*/
private long _initialDragTime = -1;
/**
* last time the interval was checked
*/
private long _lastCheck = -1;
// endregion
// region Constructor
public AutoscrollTreeView()
{
super();
addEventHandlers();
}
public AutoscrollTreeView(TreeItem<T> root)
{
super(root);
addEventHandlers();
}
// endregion
// region Getter/Setter
public final void setCheckInterval(long value)
{
_checkInterval.set(value);
}
public final long getCheckInterval()
{
return _checkInterval.get();
}
public final LongProperty checkIntervalProperty()
{
return _checkInterval;
}
public final void setScrollSpeed(double value)
{
_scrollSpeed.set(value);
}
public final double getScrollSpeed()
{
return _scrollSpeed.get();
}
public final DoubleProperty scrollSpeedProperty()
{
return _scrollSpeed;
}
public final void setScrollSpeedIncrementPerSecond(double value)
{
_scrollSpeedIncrementPerSecond.set(value);
}
public final double getScrollSpeedIncrementPerSecond()
{
return _scrollSpeedIncrementPerSecond.get();
}
public final DoubleProperty scrollSpeedIncrementPerSecondProperty()
{
return _scrollSpeedIncrementPerSecond;
}
public final void setDragIdentiferTop(double value)
{
_dragIdentifierTop.set(value);
}
public final double getDragIdentifierTop()
{
return _dragIdentifierTop.get();
}
public final DoubleProperty dragIdentifierTopProperty()
{
return _dragIdentifierTop;
}
public final void setDragIdentiferBottom(double value)
{
_dragIdentifierBottom.set(value);
}
public final double getDragIdentifierBottom()
{
return _dragIdentifierBottom.get();
}
public final DoubleProperty dragIdentifierBottomProperty()
{
return _dragIdentifierBottom;
}
// endregion
// region Events
private void onDragEvent(DragEvent event)
{
// -----only apply when there is a drag event in progress
if(event.getEventType().equals(DragEvent.DRAG_OVER))
{
if(_lastCheck == -1 || System.currentTimeMillis() - _lastCheck > _checkInterval.get())
{
ScrollDirection direction = ScrollDirection.None;
if(event.getY() <= _dragIdentifierTop.get())
direction = ScrollDirection.Top;
else if(event.getY() >= getHeight() - _dragIdentifierBottom.get())
direction = ScrollDirection.Bottom;
if(direction != ScrollDirection.None)
{
double additionalScrollSpeed = 0;
if(_initialDragTime > 0)
additionalScrollSpeed = _scrollSpeedIncrementPerSecond.get() * (System.currentTimeMillis() - _initialDragTime) * _milliSecondToSecondFactor;
else
_initialDragTime = System.currentTimeMillis();
if(direction == ScrollDirection.Bottom)
scrollY(_scrollSpeed.get() + additionalScrollSpeed);
else
scrollY(-(_scrollSpeed.get() + additionalScrollSpeed));
}
else
{
_initialDragTime = -1;
}
_lastCheck = System.currentTimeMillis();
}
}
else
{
_initialDragTime = -1;
_lastCheck = -1;
}
}
// endregion
// region Private
/**
* adds the necessary event filters
*/
private void addEventHandlers()
{
addEventHandler(DragEvent.DRAG_OVER, event -> onDragEvent(event));
addEventHandler(DragEvent.DRAG_EXITED, event -> onDragEvent(event));
addEventHandler(DragEvent.DRAG_DROPPED, event -> onDragEvent(event));
addEventHandler(DragEvent.DRAG_DONE, event -> onDragEvent(event));
}
private void scrollY(double offset)
{
VirtualFlow<?> flow = ((VirtualFlow<?>) lookup("VirtualFlow"));
flow.adjustPixels(offset);
}
// endregion
}

GWT TextArea - Resize Handler

Some browsers show for HTML Textareas a handle to resize the textbox. Is there any way to react to this resize events in GWT?
I tried it with this code, but the event is not triggered:
TextArea textArea = new TextArea();
textArea.addHandler(new ResizeHandler() {
#Override
public void onResize(ResizeEvent event) {
System.out.println("resize");
}
}, ResizeEvent.getType());
"It doesn't look like you can specifically attach events to resizing a textarea. The resize event fires when the window is resized."
https://stackoverflow.com/a/2096352/1467482
The question already is two years old, but for them who are brought by Google to this question I have a solution.
You can use the mouse-down, mouse-move and mouse-up events to react on resizing of the text area. Here is a skeleton code:
TextArea textArea = new TextArea();
...
textArea.addMouseDownHandler(new MouseDownHandler() {
private HandlerRegistration mouseMoveUpRegistration;
private int lastWidth;
private int lastHeight;
#Override
public void onMouseDown(MouseDownEvent event) {
lastWidth = getOffsetWidth();
lastHeight = getOffsetHeight();
if (mouseMoveUpRegistration == null) {
mouseMoveUpRegistration = Event.addNativePreviewHandler(new NativePreviewHandler() {
#Override
public void onPreviewNativeEvent(NativePreviewEvent event) {
if (event.getTypeInt() == Event.ONMOUSEMOVE || event.getTypeInt() == Event.ONMOUSEUP) {
int width = getOffsetWidth();
int height = getOffsetHeight();
if (width != lastWidth || height != lastHeight) {
// do whatever you want to to while still resizing
lastWidth = width;
lastHeight = height;
}
if (event.getTypeInt() == Event.ONMOUSEUP) {
// do whatever you want to do when resizing finished
// perhaps check actual and initial size to see if the size really changed
/* mouse up => additionally remove the handler */
if (mouseMoveUpRegistration != null) {
mouseMoveUpRegistration.removeHandler();
mouseMoveUpRegistration = null;
}
}
}
});
}
}
});

BasicScrollBarUI: Label instead of Button - Can't scroll

I have made a custom BasicScrollBarUI.
I have replaced the incrButton / decrButton by JLabels.
Therefore i had to override the InstallListeners(), to add the Listener to the incr/decrLabel instead of the JButtons.
//TODO
protected void installListeners() {
trackListener = createTrackListener();
buttonListenerCustom = createArrowButtonListenerCustom();
modelListener = createModelListener();
propertyChangeListener = createPropertyChangeListener();
scrollbar.addMouseListener(trackListener);
scrollbar.addMouseMotionListener(trackListener);
scrollbar.getModel().addChangeListener(modelListener);
scrollbar.addPropertyChangeListener(propertyChangeListener);
// scrollbar.addFocusListener(getHandler());
if (incrLabel != null) {
incrLabel.addMouseListener(buttonListenerCustom);
System.out.println("OK gemacht");
}
if (decrLabel != null) {
decrLabel.addMouseListener(buttonListenerCustom);
}
scrollListener = createScrollListener();
scrollTimer = new Timer(scrollSpeedThrottle, scrollListener);
scrollTimer.setInitialDelay(300); // default InitialDelay?
}
Therefore i had to override the ArrowButtonListener, to react on the Labels.
protected class ArrowButtonListenerCustom extends MouseAdapter {
boolean handledEvent;
public void mousePressed(MouseEvent e) {
if (!scrollbar.isEnabled()) {
return;
}
// not an unmodified left mouse button
// if(e.getModifiers() != InputEvent.BUTTON1_MASK) {return; }
if (!SwingUtilities.isLeftMouseButton(e)) {
return;
}
int direction;
if (e.getSource() == incrLabel) {
direction = 1;
incrLabel.setIcon(new ImageIcon(increaseButtonPressedImage));
} else {
direction = -1;
decrLabel.setIcon(new ImageIcon(decreaseButtonPressedImage));
}
scrollByUnit(direction);
scrollTimer.stop();
scrollListener.setDirection(direction);
scrollListener.setScrollByBlock(false);
scrollTimer.start();
handledEvent = true;
if (!scrollbar.hasFocus() && scrollbar.isRequestFocusEnabled()) {
scrollbar.requestFocus();
}
}
public void mouseReleased(MouseEvent e) {
scrollTimer.stop();
handledEvent = false;
scrollbar.setValueIsAdjusting(false);
incrLabel.setIcon(new ImageIcon(increaseButtonImage));
decrLabel.setIcon(new ImageIcon(decreaseButtonImage));
}
}
Of course createArrowButtonListenerCustom() returns a new instance of ArrowButtonListenerCustom.
Now my Problem:
When I click on the incr/decrLabel, the List scrolls correctly, but the thumb of the ScrollBar doesn't move (or better: the Thumb isn't repainted. If I move the Mouse over the thumb, it gets repainted on the right place). I have the same problem, when I scroll with the MouseWheel.
I don't understand, why this doesn't work.
Thanks for your help!

Categories