NatTable with custom scrollbars

When talking about styling a SWT control via CSS, one issue is raised quite early. The scrollbars can not be styled! Looking at a dark theme, the importance on that issue becomes obvious, as you can see in the following screenshot.

NatTable_dark_default_scrollbars

Using NatTable the scrolling capabilities are via the ViewportLayer. With NatTable 1.1 the possibility was added to set custom scrollbars to the ViewportLayer. This enables for example to have multiple ViewportLayer in a layer composition (split viewport) or to create UI layouts with special scrolling interactions.

With the possibility to use a custom scrollbar implementation, it is possible to style a NatTable completely with a dark theme. As an example for a stylable scrollbar we use the FlatScrollBar from Code Affine.

Since the scrollbars of the Canvas, which is the base class of NatTable, can’t be exchanged directly, we need to create a wrapper composite for the NatTable. This way the scrollbars can be attached beneath the NatTable instead of being part inside the NatTable.

NatTable_wrapper

To create the above layout, a GridLayout with two columns can be used, where the NatTable will take all the available space.

// NatTable and scrollbar container
Composite container = new Composite(parent, SWT.NONE);
GridLayoutFactory
    .swtDefaults()
    .numColumns(2)
    .margins(0, 0)
    .spacing(0, 0)
    .applyTo(container);

// NatTable as main control
NatTable natTable = new NatTable(container, viewportLayer);
GridDataFactory
    .fillDefaults()
    .grab(true, true)
    .applyTo(natTable);

The vertical scrollbar is attached to the right, and the horizontal scrollbar is attached to the bottom. To ensure that the layout doesn’t break, the FlatScrollBar is wrapped into a Composite. This way we are also able to set a fixed width/height, while telling the FlatScrollBar to fill the available space.

// vertical scrollbar wrapped in another composite for layout
Composite verticalComposite =
    new Composite(container, SWT.NONE);
GridLayoutFactory
    .swtDefaults()
    .margins(0, 0)
    .spacing(0, 0)
    .applyTo(verticalComposite);
GridData verticalData = GridDataFactory
    .swtDefaults()
    .hint(14, SWT.DEFAULT)
    .align(SWT.BEGINNING, SWT.FILL)
    .grab(false, true)
    .create();
verticalComposite.setLayoutData(verticalData);

FlatScrollBar vertical =
    new FlatScrollBar(verticalComposite, SWT.VERTICAL);
GridDataFactory
    .fillDefaults()
    .grab(true, true)
    .applyTo(vertical);

// horizontal scrollbar wrapped in another composite for layout
Composite horizontalComposite =
    new Composite(container, SWT.NONE);
GridLayoutFactory
    .swtDefaults()
    .margins(0, 0)
    .spacing(0, 0)
    .applyTo(horizontalComposite);
GridData horizontalData = GridDataFactory
    .swtDefaults()
    .hint(SWT.DEFAULT, 14)
    .align(SWT.FILL, SWT.BEGINNING)
    .grab(true, false)
    .create();
horizontalComposite.setLayoutData(horizontalData);

FlatScrollBar horizontal =
    new FlatScrollBar(horizontalComposite, SWT.HORIZONTAL);
GridDataFactory
    .fillDefaults()
    .grab(true, true)
    .applyTo(horizontal);

To be independent of the scrollbar implementation, the IScroller<T> interface was introduced in NatTable. The two default implementations ScrollBarScroller and SliderScroller are shipped with NatTable Core to be able to set custom scrollbars using SWT default implementations. Using this abstraction it is also possible to use another scrollbar implementation, like the FlatScrollBar. The following code shows the implementation of a FlatScrollBarScroller.

class FlatScrollBarScroller
    implements IScroller<FlatScrollBar> {

    private FlatScrollBar scrollBar;

    public FlatScrollBarScroller(FlatScrollBar scrollBar) {
        this.scrollBar = scrollBar;
    }

    @Override
    public FlatScrollBar getUnderlying() {
        return scrollBar;
    }

    @Override
    public boolean isDisposed() {
        return scrollBar.isDisposed();
    }

    @Override
    public void addListener(int eventType, Listener listener) {
        scrollBar.addListener(eventType, listener);
    }

    @Override
    public void removeListener(int eventType, Listener listener) {
        scrollBar.removeListener(eventType, listener);
    }

    @Override
    public int getSelection() {
        return scrollBar.getSelection();
    }

    @Override
    public void setSelection(int value) {
        scrollBar.setSelection(value);
    }

    @Override
    public int getMaximum() {
        return scrollBar.getMaximum();
    }

    @Override
    public void setMaximum(int value) {
        scrollBar.setMaximum(value);
    }

    @Override
    public int getPageIncrement() {
        return scrollBar.getPageIncrement();
    }

    @Override
    public void setPageIncrement(int value) {
        scrollBar.setPageIncrement(value);
    }

    @Override
    public int getThumb() {
        return scrollBar.getThumb();
    }

    @Override
    public void setThumb(int value) {
        scrollBar.setThumb(value);
    }

    @Override
    public int getIncrement() {
        return scrollBar.getIncrement();
    }

    @Override
    public void setIncrement(int value) {
        scrollBar.setIncrement(value);
    }

    @Override
    public boolean getEnabled() {
        return scrollBar.getEnabled();
    }

    @Override
    public void setEnabled(boolean b) {
        scrollBar.setEnabled(b);
    }

    @Override
    public boolean getVisible() {
        return scrollBar.getVisible();
    }

    @Override
    public void setVisible(boolean b) {
        scrollBar.setVisible(b);
    }

}

Using the above FlatScrollBarScroller, the created FlatScrollBar instances can be set to the ViewportLayer.

As the layout will always show the space for the scroller with the GridData instances above, we need to register a listener that hides the wrapper Composites of the FlatScrollBar instances in case the FlatScrollBar is hidden, and a listener that shows the Composites again in case the FlatScrollBar becomes visible again. This is done by setting a GridLayoutData with a matching exclude flag.

// create the vertical scroller
FlatScrollBarScroller verticalScroller =
    new FlatScrollBarScroller(vertical);

// register the hide/show listener
verticalScroller.addListener(SWT.Hide, new Listener() {
    @Override
    public void handleEvent(Event event) {
    	GridDataFactory
            .createFrom(verticalData)
            .exclude(true)
            .applyTo(verticalComposite);
    	GridDataFactory
            .createFrom(horizontalData)
            .span(2, 1)
            .applyTo(horizontalComposite);
    }
});
verticalScroller.addListener(SWT.Show, new Listener() {
    @Override
    public void handleEvent(Event event) {
    	verticalComposite.setLayoutData(verticalData);
    	horizontalComposite.setLayoutData(horizontalData);
    }
});

// create the horizontal scroller
FlatScrollBarScroller horizontalScroller =
    new FlatScrollBarScroller(horizontal);

// register the hide/show listener
horizontalScroller.addListener(SWT.Hide, new Listener() {
    @Override
    public void handleEvent(Event event) {
    	GridDataFactory
            .createFrom(verticalData)
            .span(1, 2)
            .applyTo(verticalComposite);
    	GridDataFactory
            .createFrom(horizontalData)
            .exclude(true)
            .applyTo(horizontalComposite);
    }
});
horizontalScroller.addListener(SWT.Show, new Listener() {
    @Override
    public void handleEvent(Event event) {
    	verticalComposite.setLayoutData(verticalData);
    	horizontalComposite.setLayoutData(horizontalData);
    }
});

// set the custom IScroller to the ViewportLayer
viewportLayer.setVerticalScroller(verticalScroller);
viewportLayer.setHorizontalScroller(horizontalScroller);

The last part is to set the style information to the NatTable and the FlatScrollBar instances.

// set a dark background to the wrapper container
container.setBackground(GUIHelper.COLOR_BLACK);

// set a dark styling to the scrollbars
vertical.setBackground(GUIHelper.COLOR_BLACK);
vertical.setPageIncrementColor(GUIHelper.COLOR_BLACK);
vertical.setThumbColor(GUIHelper.COLOR_DARK_GRAY);

horizontal.setBackground(GUIHelper.COLOR_BLACK);
horizontal.setPageIncrementColor(GUIHelper.COLOR_BLACK);
horizontal.setThumbColor(GUIHelper.COLOR_DARK_GRAY);

// set a dark styling to NatTable
natTable.setBackground(GUIHelper.COLOR_BLACK);
natTable.setTheme(new DarkNatTableThemeConfiguration());

Doing the steps described above it is possible to create a completely dark themed NatTable using custom scrollbars as shown in the picture below.

NatTable_dark_custom_scrollbars

At the time writing this blog post, there is no wrapper or adapter implementation in NatTable for creating a NatTable with custom scrollbars. But it might be added in the future, based on the above explanations.

The full example code is available here.

About Dirk Fauth

Dirk Fauth is a Software Architect for Rich Client Systems working for the Robert Bosch GmbH in Stuttgart and a lecturer in Java basics for the Baden-Wuerttemberg Cooperative State University (DHBW). He is active in developing, teaching and talking about OSGi, Eclipse RCP applications and Eclipse related technologies. He is project lead of the Nebula NatTable project, Eclipse Platform committer and also a committer and contributor to several other Eclipse projects. (Twitter: @fipro78)
This entry was posted in Dirk Fauth, Eclipse. Bookmark the permalink.