Enhanced Tabs thanks to ScrolledPageBook

With SWT, tab folders are a way to integrate in a same container several widgets. However, it appears to be quite limited in terms of design and creativity. I here propose some alternatives for sexier user interfaces.

Tab Folder

This is our witness section.
Here is a snippet (the full code is avilable at the end of the article)…

public void createTabs( Composite parent ) {

	final TabFolder tabFolder = new TabFolder( parent, SWT.NONE );
	tabFolder.setLayoutData( new GridData( GridData.FILL_BOTH ));

	for( int i=0; i<=4; i++) {
		TabItem tabItem = new TabItem( tabFolder, SWT.NONE );
		tabItem.setText( "Item " + i );
		tabItem.setControl( createTabContent( tabFolder ));
	}
}

private Composite createTabContent( Composite parent ) {

	Composite c = new Composite( parent, SWT.NONE );
	c.setLayout( new GridLayout( 2, false ));
	String[] properties = { "First Name:", "Last Name:", "Nick Name:" };

	for( String property : properties ) {
		new Label( c, SWT.NONE ).setText( property );
		new Text( c, SWT.SINGLE | SWT.BORDER ).setLayoutData( new GridData( GridData.FILL_HORIZONTAL ));
	}

	return c;
}

And here is a screenshot.

An example with a Tab folder

As you can see, everything is centered, except the tabs.
And that’s not cool at all.

Custom Tab Folder

The CTabFolder class is a little more powerful, but similar limitations appear quickly. It is not possible to center the tabs. And they are either above or below the content, never on side.

An example with a CTab folder

public void createTabs( Composite parent ) {

	final CTabFolder tabFolder = new CTabFolder( parent, SWT.BORDER );
	tabFolder.setLayoutData( new GridData( GridData.FILL_BOTH ));
	tabFolder.setSimple( false );

	for( int i=0; i<=4; i++ ) {
		CTabItem tabItem = new CTabItem( tabFolder, SWT.NONE );
		tabItem.setText( " Item " + i + " " );
		tabItem.setControl( createTabContent( tabFolder ));
	}
}

Note that you can add images on the tab.

Scrolled Page Book

An alternative to these classes is ScrolledPageBook.
It can be used in Eclipse plugins, but also in standalone applications. In addition to SWT bundles, you only have to add org.eclipse.ui.forms to your classpath. No need to be in a form to use it.

Let’s see a first illustration, with navigation labels and not (yet) tabs.
The principle is simple: you have a set of labels and a scrolled page book. When the user clicks on a label, we change the displayed page in the book.

public void createTabs( Composite parent ) {

	// Add the container for navigation labels
	final int tabCpt = 5;
	Composite container = new Composite( parent, SWT.NONE );
	container.setLayout( new GridLayout( tabCpt, true ));
	container.setLayoutData( new GridData( SWT.CENTER, SWT.DEFAULT, true, false ));

	// Add the page book
	final ScrolledPageBook pageBook = new ScrolledPageBook( parent );
	pageBook.setLayoutData( new GridData( GridData.FILL_BOTH ));

	// The listener when the user clicks on a "tab"
	Listener listener = new Listener() {
		@Override
		public void handleEvent( Event event ) {
			pageBook.showPage( event.widget.getData());
		}
	};

	// Register the pages and bind it all
	for( int i=0; i<=4; i++ ) {
		Label l = new Label( container, SWT.BORDER );
		l.setText( "Item " + i );
		l.setData( i );

		pageBook.registerPage( i, createTabContent( pageBook.getContainer(), i ));
		l.addListener( SWT.MouseDown, listener );
	}

	// Force to display the first tab
	pageBook.showPage( 0 );
}

And here is a preview.

A first try with a scrolled page book

Ugly, isn’t it?
But we can fix it.

Tabs with a Scrolled Page Book

To have a real tab’s look & feel, we have to reduce the margin between the content and the labels. We also need make our labels look better. Partial borders will be added around the labels thanks to a paint listener.

Tabs with a scrolled page book

Here is the code (it’s a little bit longer than before).

/**
 * Remember the selected index.
 */
private Integer selectedIndex = 0;

/**
 * Creates the tabs.
 * @param parent the parent
 */
public void createTabs( Composite parent ) {

	// Add the container for navigation labels
	final int tabCpt = 5;
	Composite container = new Composite( parent, SWT.NONE );
	GridLayout layout = new GridLayout( tabCpt, true );
	layout.marginHeight = 0;
	container.setLayout( layout );
	container.setLayoutData( new GridData( SWT.CENTER, SWT.DEFAULT, true, false ));

	// Add the page book
	final ScrolledPageBook pageBook = new ScrolledPageBook( parent );
	pageBook.setLayoutData( new GridData( GridData.FILL_BOTH ));

	// The listener when the user clicks on a "tab"
	final List<Label> navigationLabels = new ArrayList<Label> ();
	Listener listener = new Listener() {
		@Override
		public void handleEvent( Event event ) {
			pageBook.showPage( event.widget.getData());

			// Remember the last selected index
			TabsWithScrolledPageBookSnippet.this.selectedIndex = (Integer) event.widget.getData();

			// Highlight the selected tab
			for( Label l : navigationLabels ) {
				Color color;
				if( l != event.widget && l.getParent() != event.widget )
					color = Display.getDefault().getSystemColor( SWT.COLOR_WIDGET_BACKGROUND );
				else
					color = Display.getDefault().getSystemColor( SWT.COLOR_WHITE );

				l.setBackground( color );
				l.getParent().setBackground( color );
			}
		}
	};

	// The paint listener, to paint partial borders
	PaintListener paintListener = new PaintListener() {
		@Override
		public void paintControl( PaintEvent event ) {

			Color color;
			if( TabsWithScrolledPageBookSnippet.this.selectedIndex.equals( event.widget.getData()))
				color = Display.getDefault().getSystemColor( SWT.COLOR_GRAY );
			else
				color = Display.getDefault().getSystemColor( SWT.COLOR_WIDGET_DARK_SHADOW );

			Rectangle rect = ((Composite) event.widget).getBounds();
			event.gc.setForeground( color );
			event.gc.setAntialias( SWT.ON );
			event.gc.drawLine( 0, 0, rect.width - 1, 0 );
			event.gc.drawLine( 0, 0, 0, rect.height - 1 );
			event.gc.drawLine( rect.width - 1, 0, rect.width - 1, rect.height - 1 );
		}
	};

	// Register the pages and bind it all
	for( int i=0; i<tabCpt; i++ ) {
		Label l = createTabLabel( i, container, paintListener, listener );
		navigationLabels.add( l );
		pageBook.registerPage( i, createTabContent( pageBook.getContainer(), i ));
	}

	// Force to display the first tab (and force it to be highlighted)
	navigationLabels.get( 0 ).notifyListeners( SWT.MouseDown, new Event());
}

/**
 * Creates a label for the tab (wrapped in a composite for better display).
 * @param index the tab index
 * @param parent the container for the label
 * @param paintListener the paint listener for the label's container (to paint partial borders)
 * @param mouseDownListener the listener for when a tab is selected
 * @return the created label
 */
private Label createTabLabel( int index, Composite parent, PaintListener paintListener, Listener mouseDownListener ) {

	// Wrap the labels in a composite
	Composite c = new Composite( parent, SWT.NONE );
	c.setLayout( new GridLayout());
	c.setLayoutData( new GridData( 80, 25 ));
	c.setData( index );

	// To paint partial borders
	c.addPaintListener( paintListener );

	// Deal with the content
	Label l = new Label( c, SWT.NONE );
	l.setLayoutData( new GridData( SWT.CENTER, SWT.CENTER, true, true ));
	l.setText( "Item " + index );
	l.setData( index );

	// The click listener
	l.addListener( SWT.MouseDown, mouseDownListener );
	c.addListener( SWT.MouseDown, mouseDownListener );

	return l;
}

Horizontal Tabs

By playing with the previous example, we can also obtain a result similar to the Eclipse tabbed properties. In the following example, the navigation labels are placed on the left of the content. I hardly changed the code (about 10 lines, it is just about the layout).

Another illustration of tabs implemented with a scrolled page book

The code is available in the archive below.

Conclusion

I hope these snippets (and screenshots) will help some of you.
They also illustrate the use of the Scrolled Page Book class. Obviously, this could be used to create a custom and reusable widget (as an example shared in the Nebula project). No need to say that it would be more simple with a HTML and CSS approach. E4 may help in such an approach.

The complete source code for the snippets is available on GitHub.


About this entry