Monday, March 31, 2014

Hurdles #4 - Apache Pivot - Scroll Panes

Hurdles article number 4 and continuing to overcome challenges with Apache Pivot, this time we are looking at controlling the current position of a Scroll Pane in response to an event.

The premise of this article is that I have a Scroll Pane that contains a running log of information that is updated periodically. This log appends rows to a Table Pane that is the Scroll Pane's view. In order to ensure that any new information is immediately visible what I want to do is scroll the Scroll Pane to the bottom of the view.

There are a couple of pre-built methods to help with this. The Scroll Pane comes with setScrollTop(int y) and setScrollLeft(int x) to adjust the viewport position relative to the top and left respectively. It also has scrollAreaToVisible(Bounds bounds) which serves as an auto-scroll function to zoom to a sub-component.

The problem with these methods is that they will not work properly inside an event handler that modifies the view component! Within the event handler that is appending information to the view the following problems become immediately apparent:

  1. getHeight and getBounds methods on both the Scroll Pane and View components return 0.
  2. scrollAreaToVisible can throw OutOfBounds exceptions
  3. setScrollTop(y) when given a hard-coded value will adjust the Scroll Pane, but the scroll bars themselves will not be updated.
Through my investigations into the underlying code I discovered that I could obtain a reference to the Scroll Pane Skin object which is the object that controls the Scroll bars, tracks the current and max position of the viewport, and the rendering of these components. However many of the objects and methods within the Skin are private and inaccessible without rewriting the entire class, and the ones that are visible have the same problem of returning 0 values for height and bounds.

Eventually I discovered a link in the Apache Pivot Users forum that held the key to the solution. 2.0.2 How to move scroll bar to bottom of ScrollPane? The issue is that many of the view attributes are invalidated on a change to the View component, and are only reset as part of the painting code itself, after the event handler has already returned.

The solution to the problem is to queue a callback within the ApplicationContext itself specifically to force the repositioning of the Scroll Pane after all the paint operations have completed. I have included the code snippit below.

In the update event handler:
ApplicationContext.queueCallback(new ScrollPaneCallbackHandler(scrollPaneComponent));

Separate ScrollPaneCallbackHandler class:

private class ScrollPaneCallbackHandler implements Runnable {
private ScrollPane pane;
public ScrollPaneCallbackHandler(ScrollPane pane) {
this.pane = pane;
}
/**
* Resets ScrollTop position to show bottom of view component
*/
public void run() {
pane.setScrollTop(pane.getView().getHeight() - pane.getHeight());
}
}