1. Custom widgets in SWT

A custom widget is a UI element that you typically create because the existing native widgets cannot support a certain requirement. Custom SWT widgets are usually developed in pure Java.

SWT provides custom widgets in the org.eclipse.swt.custom package, such as CLabel, CCombo and StyledText. In addition, the Eclipse Nebula project provides multiple custom widgets.

There are three common approaches to creating a custom SWT widget:

  • Compound custom widgets that contain other existing widgets (Composite)

  • Custom-drawn widgets that have no children and are drawn directly (Canvas)

  • Mixing both (Composite and Canvas)

The following example is a compound composite. It uses the JFace layout factory API.

package com.vogella.rcp.custom.widgets.widget;

import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;

public class LabeledTextWidget extends Composite {

    private Label label;
    private Text text;

    public LabeledTextWidget(Composite parent, int style) {
        this(parent, style, SWT.NONE, SWT.BORDER);
    }

    public LabeledTextWidget(Composite parent, int style, int labelStyle, int textStyle) {
        super(parent, style);
        GridLayoutFactory.fillDefaults().numColumns(2).applyTo(this);

        label = new Label(this, labelStyle); (1)
        GridDataFactory.fillDefaults().applyTo(label);

        text = new Text(this, textStyle); (2)
        GridDataFactory.fillDefaults().grab(true, false).applyTo(text);
    }

    public void setLabelText(String text) {
        checkWidget();
        label.setText(text);
    }

    public Text getText() {
        checkWidget();
        return text;
    }
}
1 Pass this to the child widgets of your compound custom widget, not the parent Composite.
2 The same applies to the Text widget.

2. General guidelines for custom widgets

Follow these guidelines when you develop custom widgets.

2.1. Disposing the system resources

If you allocate system resources, like fonts or images, you must dispose them once you no longer use them.

To achieve this, attach a DisposeListener to your widget with the addDisposeListener() method of Canvas or Composite. Alternatively, use the JFace LocalResourceManager to create system resources and have them automatically disposed when the widget they are registered with is disposed.

package com.vogella.rcp.custom.widgets.widget;

import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;

public class ResourceManagerCanvas extends Canvas {

    private ResourceManager resourceManager;

    public ResourceManagerCanvas(Composite parent, int style) {
        super(parent, style);
        // The LocalResourceManager attaches the DisposeHandler to the Canvas for you
        resourceManager = new LocalResourceManager(JFaceResources.getResources(), this);

        // Always create colors, fonts and images with the ResourceManager instance
        Color orange = resourceManager.createColor(new RGB(255, 191, 0));
        Font boldArialFont = resourceManager.createFont(FontDescriptor.createFrom("Arial", 12, SWT.BOLD));
        // draw your widget...
    }
}

Do not override the dispose() method of the Canvas or Composite to dispose your system resources. Always add a DisposeListener instead, as dispose() is not called for child widgets when their parent is disposed.

It is not necessary to dispose Color objects since Eclipse 4.16.

2.2. Fail fast

Invoke the checkWidget() method at the beginning of each public method, before any other call. The checkWidget method of the Widget class checks preconditions for the proper use of a Widget. It verifies that the widget is not disposed and is accessed from the UI thread.

package com.vogella.rcp.custom.widgets.widget;

import org.eclipse.swt.widgets.Composite;

public class CustomWidget extends Composite {

    public CustomWidget(Composite parent, int style) {
        super(parent, style);
    }

    public void callFunctionalityOfCustomWidget() {
        checkWidget();

        // do custom things with the CustomWidget
    }
}

2.3. Implement the computeSize method

By implementing the computeSize method, you allow Layout classes and other widgets to determine the preferred size of your custom widget. This allows the layout of the parent Composite to arrange its child widgets properly.

package com.vogella.rcp.custom.widgets.widget;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;

public class ComputeSizeWidget extends Composite {

    private static final int LINE_WIDTH = 30;
    private static final int LINE_HEIGHT = 2;

    public ComputeSizeWidget(Composite parent, int style) {
        super(parent, style);
    }

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        // respect the hints, but never grow beyond LINE_WIDTH x LINE_HEIGHT
        int width = wHint != SWT.DEFAULT ? Math.min(wHint, LINE_WIDTH) : LINE_WIDTH;
        int height = hHint != SWT.DEFAULT ? Math.min(hHint, LINE_HEIGHT) : LINE_HEIGHT;

        return new Point(width, height);
    }
}

2.4. Use SWT errors

If errors occur in your custom widget, use one of the three static error methods of the SWT class.

  • public static void error (int code)

  • public static void error (int code, Throwable throwable)

  • public static void error (int code, Throwable throwable, String detail)

The code parameter of each error method must be one of the SWT.ERROR_* constants. If a parameter of a method must not be null, call SWT.error(SWT.ERROR_NULL_ARGUMENT) if it is null.

Content assist proposals for SWT.ERROR constants with ERROR_NULL_ARGUMENT selected

Also have a look at the other SWT.ERROR_* constants. They are all described in detail in the Javadoc.

2.5. Support bidirectional behavior

The SWT class contains two style flags for the constructor of a widget, which define its bidirectional behavior. SWT.RIGHT_TO_LEFT arranges the text and other elements of the widget from right to left. SWT.LEFT_TO_RIGHT is the default for most widgets.

3. Custom-drawn widgets

Use a Canvas for self-drawn widgets that do not have children. To draw on a Canvas or Composite, register a paint listener via the addPaintListener() method. Call the redraw() method instead of calling your internal paint methods directly, so that the framework can queue redraw requests and process them together.

package com.vogella.rcp.custom.widgets.widget;

import org.eclipse.jface.resource.ColorDescriptor;
import org.eclipse.jface.resource.FontDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;

public class CustomCanvasWithPaintListener extends Canvas {

    private static final int BORDER_MARGIN = 4;

    private final ResourceManager resourceManager;

    private String text = "";

    public CustomCanvasWithPaintListener(Composite parent, int style) {
        super(parent, style);
        // the LocalResourceManager disposes its resources when the canvas is disposed
        resourceManager = new LocalResourceManager(JFaceResources.getResources(), this);

        addPaintListener(paintEvent -> paintBorderAndText(paintEvent.gc));
    }

    protected void paintBorderAndText(GC gc) {
        Font boldArialFont = resourceManager.create(FontDescriptor.createFrom("Arial", 14, SWT.BOLD));
        // set the font before textExtent, so that the size of the text is calculated correctly
        gc.setFont(boldArialFont);
        Point textExtent = gc.textExtent(getText());

        // system colors are managed by the OS and must not be disposed
        gc.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK));
        // draw the background first, so that everything else is drawn on top of it
        gc.fillRoundRectangle(3, 3, textExtent.x + BORDER_MARGIN, textExtent.y + BORDER_MARGIN, 8, 8);

        Color orange = resourceManager.create(ColorDescriptor.createFrom(new RGB(255, 191, 0)));
        gc.setBackground(orange);
        gc.setForeground(orange);
        gc.drawRoundRectangle(3, 3, textExtent.x + BORDER_MARGIN, textExtent.y + BORDER_MARGIN, 8, 8);

        gc.drawString(getText(), 5, 4, true);
    }

    public String getText() {
        checkWidget();
        return text;
    }

    public void setText(String text) {
        checkWidget();
        this.text = text;
        getParent().layout();
        // trigger a repaint instead of calling paintBorderAndText(GC) directly
        redraw();
    }
}

This widget can be used via the following code.

parent.setLayout(new GridLayout(1, false));

CustomCanvasWithPaintListener paintListenerCanvas = new CustomCanvasWithPaintListener(parent, SWT.NONE);
paintListenerCanvas.setText("This is a custom-drawn widget");
// the canvas has no computeSize() implementation, so give it a size
paintListenerCanvas.setLayoutData(GridDataFactory.fillDefaults().hint(300, 50).create());

The widget should look similar to the following screenshot.

Custom drawn widget showing orange text on a black border

3.1. SWT events and listeners

The widget should also handle user interactions. Therefore, it should fire events like other widgets do, for example, a Button fires a widgetSelected event to its attached SelectionListener. In most cases, the existing SWT events are sufficient. A custom-drawn button, for example, should reuse the default SelectionListener. To reuse an existing SWT event, either support TypedListener or use the low-level Listener API via the addListener(int, Listener) method, which works for every widget. For widgets with a special purpose, you can also define custom events and listeners.

Suppose you have a custom Gameboy widget and want to listen to clicks on its buttons. First, define a custom event that extends java.util.EventObject.

package com.vogella.rcp.custom.widgets.widget.gameboy;

import java.util.EventObject;

public class GameboySelectionEvent extends EventObject {

    private static final long serialVersionUID = 1059740358205812736L;

    public static final int BUTTON_POWER = 0;
    public static final int BUTTON_A = 1;
    public static final int BUTTON_B = 2;
    public static final int BUTTON_START = 3;
    public static final int BUTTON_SELECT = 4;
    public static final int BUTTON_UP = 5;
    public static final int BUTTON_DOWN = 6;
    public static final int BUTTON_LEFT = 7;
    public static final int BUTTON_RIGHT = 8;

    private final int gameboyButton;

    public GameboySelectionEvent(Object source, int gameboyButton) {
        super(source);
        assert gameboyButton >= BUTTON_POWER && gameboyButton <= BUTTON_RIGHT;
        this.gameboyButton = gameboyButton;
    }

    public int getButton() {
        return gameboyButton;
    }
}

This event implementation provides information about which button was selected. Typically, you pass the event information to the constructor of the custom event and define getters for it. Next, provide a listener interface which receives the event.

package com.vogella.rcp.custom.widgets.widget.gameboy;

import java.util.EventListener;

/**
 * Notified every time a button on the {@link GameboyWidget} is selected.
 */
@FunctionalInterface
public interface GameboySelectionListener extends EventListener {

    void gameboyButtonSelected(GameboySelectionEvent gameboySelectionEvent);
}

Finally, provide an API to add and remove your custom listener and fire your custom events to the registered listeners.

3.2. Positions in custom drawings

In custom drawings, you need to store the positions of the drawn elements, especially if an element should trigger events. Now that the widget is drawn with a PaintListener and the listener interfaces and events are defined, you need to determine when to fire those events. To do this, track the positions of the custom drawings on the widget. The following HoverCheck class recognizes whenever a certain part of a widget is hovered.

package com.vogella.rcp.custom.widgets.widget.listener;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.util.Objects;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;

public class HoverCheck implements Listener, DisposeListener {

    private final PropertyChangeSupport changeSupport = new PropertyChangeSupport(this);

    private final Control containerWidget;
    private final Rectangle targetBounds;

    private boolean hovering;

    public HoverCheck(Control containerWidget, Point widgetCoords, Point widgetSize) {
        this.containerWidget = Objects.requireNonNull(containerWidget);
        targetBounds = new Rectangle(widgetCoords.x, widgetCoords.y, widgetSize.x, widgetSize.y);
        containerWidget.addDisposeListener(this);
        containerWidget.addListener(SWT.MouseMove, this);
    }

    @Override
    public void handleEvent(Event event) {
        if (event.type == SWT.MouseMove) {
            // the mouse event coordinates are relative to the container widget
            setHovering(targetBounds.contains(event.x, event.y));
        }
    }

    @Override
    public void widgetDisposed(DisposeEvent e) {
        // the container may already be disposed when the whole shell closes
        if (!containerWidget.isDisposed()) {
            containerWidget.removeListener(SWT.MouseMove, this);
        }
    }

    public void addHoverListener(PropertyChangeListener hoverChangeListener) {
        changeSupport.addPropertyChangeListener("hovering", hoverChangeListener);
    }

    public void removeHoverListener(PropertyChangeListener hoverChangeListener) {
        changeSupport.removePropertyChangeListener("hovering", hoverChangeListener);
    }

    public boolean isHovering() {
        return hovering;
    }

    protected void setHovering(boolean hovering) {
        boolean oldValue = this.hovering;
        this.hovering = hovering;
        // only notifies the listeners if the value changed
        changeSupport.firePropertyChange("hovering", oldValue, hovering);
    }
}

The class receives the widget itself, the x and y coordinates and the size of the part for which hovering is checked. From this information, it creates a Rectangle for the position and size of this part (for example, a Gameboy button) and uses the contains() method of the rectangle to check if the mouse position is inside it. The GameboyWidget uses the HoverCheck class to find out whether a certain Gameboy button was selected.

package com.vogella.rcp.custom.widgets.widget.gameboy;

import java.beans.PropertyChangeListener;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;

import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;

import com.vogella.rcp.custom.widgets.widget.listener.HoverCheck;

public class GameboyWidget extends Canvas {

    private static final int Y_OFFSET = 10;
    private static final int X_OFFSET = 10;

    private final ResourceManager resourceManager;

    private final Collection<GameboySelectionListener> gameboySelectionListeners = new ArrayList<>();

    private HoverCheck buttonAHoverCheck;
    private HoverCheck buttonBHoverCheck;

    private Image gameBoyImage;

    public GameboyWidget(Composite parent, int style) {
        super(parent, style);
        resourceManager = new LocalResourceManager(JFaceResources.getResources(), this);
        gameBoyImage = loadGameboyImage();

        setupButtonHoverChecks();

        addPaintListener(this::paint);

        addListener(SWT.MouseDown, event -> {
            if (buttonAHoverCheck.isHovering()) {
                fireGameboySelectionChanged(new GameboySelectionEvent(this, GameboySelectionEvent.BUTTON_A));
            } else if (buttonBHoverCheck.isHovering()) {
                fireGameboySelectionChanged(new GameboySelectionEvent(this, GameboySelectionEvent.BUTTON_B));
            }
        });
    }

    private void setupButtonHoverChecks() {
        Point buttonsSize = new Point(40, 40);
        PropertyChangeListener handCursor = event -> setCursor(
                Boolean.TRUE.equals(event.getNewValue()) ? getDisplay().getSystemCursor(SWT.CURSOR_HAND) : null);

        buttonAHoverCheck = new HoverCheck(this, new Point(266, 330), buttonsSize);
        buttonAHoverCheck.addHoverListener(handCursor);

        buttonBHoverCheck = new HoverCheck(this, new Point(211, 353), buttonsSize);
        buttonBHoverCheck.addHoverListener(handCursor);
    }

    protected void paint(PaintEvent event) {
        event.gc.drawImage(gameBoyImage, X_OFFSET, Y_OFFSET);
    }

    @Override
    public Point computeSize(int wHint, int hHint, boolean changed) {
        // the size of the Gameboy image plus the offset at which it is drawn
        Rectangle bounds = gameBoyImage.getBounds();
        return new Point(bounds.width + X_OFFSET, bounds.height + Y_OFFSET);
    }

    public void addGameboySelectionListener(GameboySelectionListener selectionListener) {
        gameboySelectionListeners.add(selectionListener);
    }

    public void removeGameboySelectionListener(GameboySelectionListener selectionListener) {
        gameboySelectionListeners.remove(selectionListener);
    }

    protected void fireGameboySelectionChanged(GameboySelectionEvent selectionEvent) {
        gameboySelectionListeners.forEach(listener -> listener.gameboyButtonSelected(selectionEvent));
    }

    private Image loadGameboyImage() {
        Bundle bundle = FrameworkUtil.getBundle(getClass());
        URL imgUrl = FileLocator.find(bundle, IPath.fromPortableString("images/gameboy.jpg"));
        return resourceManager.create(ImageDescriptor.createFromURL(imgUrl));
    }
}
Game Boy custom widget in a running application with the Application Model editor and console output

4. Using custom Regions for Widgets

To apply a custom shape for a Control the Region class can be used. A Region is derived from org.eclipse.swt.graphics.Resource, which means that it is necessary to dispose of it once it is no longer needed.

Region region = new Region();

// apply region shape

control.setRegion(region);

region.dispose();

4.1. Specifying a Region for a Control

The Control class offers a setRegion method, where a Region can be applied.

The most important methods of a Region are add, intersect and subtract where the shape can be defined by either a rectangle or an array of points.

Here is an example where the Shell gets a shape of a crown:

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class CrownRegion
{

  public static void main(String[] args)
  {
    final Display display = new Display();
    // Shell must be created with style SWT.NO_TRIM
    final Shell shell = new Shell(display, SWT.NO_TRIM | SWT.ON_TOP);
    shell.setBackground(display.getSystemColor(SWT.COLOR_YELLOW));

    // Create a region that looks like a crown
    Region region = new Region();
    region.add(0, 0, 300, 150);
    // subtract for the spikes of the crown
    region.subtract(new int[] { 2, 0, 75, 75, 150, 0, 225, 75, 298, 0 });
    shell.setRegion(region);
    Rectangle size = region.getBounds();
    shell.setSize(size.width * 2, size.height);

    // center the shell on the display
    Rectangle bounds = display.getBounds();
    shell.setLocation(bounds.width / 2 - 150, bounds.height / 2 + 75);

    shell.open();

    while (!shell.isDisposed())
    {
      if (!display.readAndDispatch())
        display.sleep();
    }
    region.dispose();
    display.dispose();
  }
}
The x and y coordinates of the Region are relative to the Control’s position, not relative to the display as is the case for a Shell. That’s why the region rectangle (region.add(0, 0, 300, 150);) starts at 0, 0.

4.2. Adding controls to a "regionized" Shell

When adding controls to a shell, which has an applied Region, the area, which is not part of the region, should be considered.

For example, by simply adding a Button it usually won’t show up, because it would be rendered in the area between the spikes of the crown.

Therefore the layout data, e.g., GridData, can be used to specify an indent for the Control.

public class CrownRegionWithButton
{

  public static void main(String[] args)
  {
    final Display display = new Display();
    // Shell must be created with style SWT.NO_TRIM
    final Shell shell = new Shell(display, SWT.NO_TRIM | SWT.ON_TOP);
    GridLayoutFactory.fillDefaults().applyTo(shell);
    shell.setBackground(display.getSystemColor(SWT.COLOR_YELLOW));

    // Create a region that looks like a crown
    Region region = new Region();
    region.add(0, 0, 300, 150);
    // subtract for the spikes of the crown
    region.subtract(new int[] { 2, 0, 75, 75, 150, 0, 225, 75, 298, 0 });
    shell.setRegion(region);
    Rectangle size = region.getBounds();
    shell.setSize(size.width * 2, size.height);

    // center the shell on the display
    Rectangle bounds = display.getBounds();
    shell.setLocation(bounds.width / 2 - 150, bounds.height / 2 + 75);

    Button button = new Button(shell, SWT.PUSH);
    button.setText("Close");

    int halfSize = size.width / 2;
    int halfButtonSize = button.computeSize(SWT.DEFAULT, SWT.DEFAULT).x / 2;
    // Adding an indent to draw the close button in the middle
    GridDataFactory.fillDefaults().indent(halfSize - halfButtonSize, 75).applyTo(button);
    button.addListener(SWT.Selection, e -> shell.close());

    shell.open();

    while (!shell.isDisposed())
    {
      if (!display.readAndDispatch())
        display.sleep();
    }
    region.dispose();
    display.dispose();
  }
}

In this case an indent is added to the GridData of the Button.

The expected output is shown in the screenshot below.

crown close btn

4.3. Examples from SWT Snippets

The SWT snippets are always a nice source for more advanced examples like Snippet 134:

/*******************************************************************************
 * Copyright (c) 2000, 2016 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.snippets;

/*
 * Shell example snippet: create a non-rectangular window
 *
 * For a list of all SWT example snippets see
 * https://www.eclipse.org/swt/snippets/
 *
 * @since 3.0
 */
import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.widgets.*;

public class Snippet134 {

static int[] circle(int r, int offsetX, int offsetY) {
    int[] polygon = new int[8 * r + 4];
    //x^2 + y^2 = r^2
    for (int i = 0; i < 2 * r + 1; i++) {
        int x = i - r;
        int y = (int)Math.sqrt(r*r - x*x);
        polygon[2*i] = offsetX + x;
        polygon[2*i+1] = offsetY + y;
        polygon[8*r - 2*i - 2] = offsetX + x;
        polygon[8*r - 2*i - 1] = offsetY - y;
    }
    return polygon;
}

public static void main(String[] args) {
    final Display display = new Display();
    //Shell must be created with style SWT.NO_TRIM
    final Shell shell = new Shell(display, SWT.NO_TRIM | SWT.ON_TOP);
    shell.setBackground(display.getSystemColor(SWT.COLOR_RED));
    //define a region that looks like a key hole
    Region region = new Region();
    region.add(circle(67, 67, 67));
    region.subtract(circle(20, 67, 50));
    region.subtract(new int[]{67, 50, 55, 105, 79, 105});
    //define the shape of the shell using setRegion
    shell.setRegion(region);
    Rectangle size = region.getBounds();
    shell.setSize(size.width, size.height);
    //add ability to move shell around
    Listener l = new Listener() {
        /** The x/y of the MouseDown, relative to top-left of the shell. */
        Point origin;
        @Override
        public void handleEvent(Event e) {
            switch (e.type) {
                case SWT.MouseDown:
                    Point point = shell.toDisplay(e.x, e.y);
                    Point loc = shell.getLocation();
                    origin = new Point(point.x - loc.x, point.y - loc.y);
                    break;
                case SWT.MouseUp:
                    origin = null;
                    break;
                case SWT.MouseMove:
                    if (origin != null) {
                        Point p = display.map(shell, null, e.x, e.y);
                        shell.setLocation(p.x - origin.x, p.y - origin.y);
                    }
                    break;
            }
        }
    };
    shell.addListener(SWT.MouseDown, l);
    shell.addListener(SWT.MouseUp, l);
    shell.addListener(SWT.MouseMove, l);
    //add ability to close shell
    Button b = new Button(shell, SWT.PUSH);
    b.setBackground(shell.getBackground());
    b.setText("close");
    b.pack();
    b.setLocation(10, 68);
    b.addListener(SWT.Selection, e -> shell.close());
    shell.open();
    while (!shell.isDisposed()) {
        if (!display.readAndDispatch())
            display.sleep();
    }
    region.dispose();
    display.dispose();
}
}

And as mentioned before the setRegion method belongs to the Control class, which means that a Region can also be applied for other Controls like a Button:

/*******************************************************************************
 * Copyright (c) 2000, 2016 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.swt.snippets;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

/*
 * Region on a control: create a non-rectangular button
 *
 * For a list of all SWT example snippets see
 * https://www.eclipse.org/swt/snippets/
 *
 * @since 3.4
 */

public class Snippet294 {

    static int[] circle(int r, int offsetX, int offsetY) {
        int[] polygon = new int[8 * r + 4];
        // x^2 + y^2 = r^2
        for (int i = 0; i < 2 * r + 1; i++) {
            int x = i - r;
            int y = (int)Math.sqrt(r*r - x*x);
            polygon[2*i] = offsetX + x;
            polygon[2*i+1] = offsetY + y;
            polygon[8*r - 2*i - 2] = offsetX + x;
            polygon[8*r - 2*i - 1] = offsetY - y;
        }
        return polygon;
    }

    public static void main(String[] args) {
        final Display display = new Display();

        final Shell shell = new Shell(display);
        shell.setText("Regions on a Control");
        shell.setLayout(new FillLayout());
        shell.setBackground(display.getSystemColor(SWT.COLOR_DARK_RED));

        Button b2 = new Button(shell, SWT.PUSH);
        b2.setText("Button with Regions");

        // define a region that looks like a circle with two holes in ot
        Region region = new Region();
        region.add(circle(67, 87, 77));
        region.subtract(circle(20, 87, 47));
        region.subtract(circle(20, 87, 113));

        // define the shape of the button using setRegion
        b2.setRegion(region);
        b2.setLocation(100,50);

        b2.addListener(SWT.Selection, e -> shell.close());

        shell.setSize(200,200);
        shell.open();

        while (!shell.isDisposed()) {
            if (!display.readAndDispatch())
                display.sleep();
        }
        region.dispose();
        display.dispose();
    }

}

5. Custom composite with highlights

The following custom composite highlights itself by changing its color when the mouse enters or leaves its area.

package com.vogella.swt.widgets;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseTrackAdapter;
import org.eclipse.swt.events.MouseTrackListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;

public class HighlightComposite extends Composite {

    private final Color regularColor;
    private final Color highlightColor;

    private final MouseTrackListener mouseTracker = new MouseTrackAdapter() {
        @Override
        public void mouseEnter(MouseEvent e) {
            setBackground(highlightColor);
        }

        @Override
        public void mouseExit(MouseEvent e) {
            setBackground(regularColor);
        }
    };

    public HighlightComposite(Composite parent, int style) {
        super(parent, style);
        regularColor = getDisplay().getSystemColor(SWT.COLOR_GREEN);
        highlightColor = getDisplay().getSystemColor(SWT.COLOR_RED);
        setBackground(regularColor);

        GridLayout layout = new GridLayout(2, false);
        layout.marginHeight = 5;
        layout.marginWidth = 5;
        layout.horizontalSpacing = 0;
        setLayout(layout);

        addMouseTrackListener(mouseTracker);
    }

    /**
     * Also highlights the composite while the mouse is over one of its children.
     */
    public void trackChildren() {
        checkWidget();
        for (Control child : getChildren()) {
            child.addMouseTrackListener(mouseTracker);
        }
    }
}

The usage of this new composite is demonstrated by the following code. As for other widgets, the parent defines the layout data of the composite, not the composite itself.

@PostConstruct
public void createControls(Composite parent) {
    parent.setLayout(new GridLayout(1, false));
    HighlightComposite highlightComposite = new HighlightComposite(parent, SWT.NONE);
    highlightComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));

    Text modelWidget = new Text(highlightComposite, SWT.SINGLE | SWT.LEAD | SWT.BORDER);
    modelWidget.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
    modelWidget.setText("Hello");
    highlightComposite.trackChildren();
}

In this example, the color of the Composite changes when the mouse enters.

Playground part with a text field showing Hello inside the green frame of the composite
Playground part with the red frame of the composite around the text field while the mouse is over it

6. SWT Widget online resources

Home Tutorials Training Consulting Books Company Contact us


Get more...