Eclipse RCP
20.09.2010 - 24.09.2010, Hamburg
git
07.10.2010 - 08.10.2010, Essen
Eclipse RCP

SWT Display und Threading, Eclipse Jobs

Snippet 1: Hello World

Anhand des SWT-Snippets “Hello World” betrachten wir im Folgenden einige grundlegenden Konzepte von SWT, die bisher im Verborgenen geblieben sind:

public static void main (String [] args) {
    Display display = new Display();
    Shell shell = new Shell(display, SWT.SHELL_TRIM);
    shell.open();
    while (!shell.isDisposed()) {
        if (!display.readAndDispatch())
            display.sleep();
    }
    display.dispose();
}

Display

Bevor mit SWT gearbeitet werden kann, muss grundsätzlich ein Display-Objekt erzeugt werden:

Display display = new Display();

In RCP-Anwendungen wird das Display durch den Aufruf der Methode PlatformUI.createDisplay() in der Applikationsklasse erzeugt.

Das Display-Objekt repräsentiert die Verbindung der Applikation zu dem UI-Layer des Betriebssystems. Sie finden hier auch Methoden zur Abfrage von Systeminformationen, z.B.:

display.getPrimaryMonitor().width;
display.getCursorLocation();

Um im Anwendungscode auf das Display zuzugreifen, kann ein beliebiges Widget verwendet werden. Dieses kennt das Display-Objekt:

// Über Widget
someWidget.getDisplay();

Steht kein Widget zur Verfügung, kann auch global auf das Display zugegriffen werden:

// Global
Display.getDefault();
// Aus dem UI-Thread
Display.getCurrent();

Farben, Fonts und Bilder

Systemfarben können in SWT über das Display abgefragt werden, eigene Farben können mit einem RGB-Objekt erzeugt werden:

Color systemColor = parent.getDisplay().getSystemColor(SWT.COLOR_RED);
RGB rgb = new RGB(200, 100, 0);
Color customColor = new Color(display, rgb);

Fonts werden in SWT von einem FontData-Objekt beschrieben, der konkrete Font wird durch Font repräsentiert:

FontData fontData = new FontData("Arial", 10, SWT.BOLD);
Font font = new Font(display, fontData);

Bilder werden durch ImageDescriptor-Objekte beschrieben. In RCP-Anwendungen können Bilder am einfachsten über die statische Methode getImageDescriptor auf dem Activator des Plugins verwendet werden. Die Bilddaten werden geladen, indem auf dem ImageDescriptor-Objekt createImage aufgerufen wird:

ImageDescriptor imageDescriptor = Activator.getImageDescriptor("/icons/someimage.png");
Image image = imageDescriptor.createImage();

Freigabe von Systemresourcen

In SWT binden die meisten Objekte Systemressourcen, die nicht von der Java Garbage Collection aufgeräumt werden können. Daher ist es wichtig, dass SWT-Objekte durch Aufruf der dispose-Methode wieder freigegeben werden.

Für Widgets gestaltet sich dies unkritisch, da der Aufruf von dispose auf einem Composite auch alle Kindelemente freigibt. In RCP-Anwendungen wird dies von der Workbench erledigt - sobald die Inhalte eines Views nicht mehr benötigt werden, werden diese freigegeben.

Vor allem Color, Font und Image-Objekte müssen manuell freigegeben werden, sobald sie nicht mehr benötigt werden. Denn diese Objekte werden nicht freigegeben, wenn ein verwendendes Control freigegeben wird. Dies liegt darin begründet, dass sie in verschiedenen Composites wiederverwendet werden könnten. Im Kapitel zu JFace werden wir dazu den ResourceManager betrachten, der diese Aufgabe automatisch erledigt.

Shell

Shell ist eine spezielle Composite-Klasse, die ein Fenster repräsentiert. Es wird mit einem Display erzeugt, wenn es sich um ein Hauptfenster der Applikation handelt, oder mit einem anderen Fenster, wenn es sich um ein untergeordnetes oder modales Fenster handelt:

public Shell(Display display, int style);
public Shell(Shell parent, int style);

In RCP-Anwendungen wird das Workbench-Fenster intern durch die Eclipse UI-Komponente erzeugt und befüllt. Eigene Shells verwendet man hier nur, um untergeordnete oder modale Fenster zusätzlich zum Workbench-Fenster zu erzeugen.

Event Loop

In SWT-Anwendungen ist die Anwendung selbst dafür verantwortlich, die Event Loop auszuführen, d.h. bis zum Beenden der Applikation neue Ereignisse abzufragen und weiterzuleiten:

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

In einer RCP-Anwendung wird die Event Loop vom RCP-Framework ausgeführt, ausgelöst durch den Aufruf PlatformUI.createAndRunWorkbench in der Applikationsklasse.

Threading

Dem Thread, der das Display erzeugt hat und die Event Loop ausführt, kommt in SWT-Anwendungen eine besondere Rolle zu. Man bezeichnet ihn als “UI Thread”. Alle Events werden im UI Thread strikt nacheinander abgearbeitet. Kein Event kann verarbeitet werden, bis der vorhergehende Event Handler fertig abgearbeitet ist.

Daher sind Listener so zu implementieren, dass sie den UI Thread nur solange wie unbedingt notwendig in Anspruch nehmen. Längerlaufende Aktivitäten sollten in Hintergrund-Threads durchgeführt werden.

Wichtig ist zudem, dass der Zugriff auf SWT-Widgets nur dem UI Thread gestattet ist. Zugriffe auf SWT-Widgets außerhalb des UI-Threads führen zu einer SWTException:

Exception in thread "Thread-N"
org.eclipse.swt.SWTException: Invalid thread access

Mit den Display-Methoden (a)syncExec kann ein Runnable auf dem UI-Thread ausgeführt werden, z.B.:

display.asyncExec(new Runnable() {
    @Override
    public void run() {
        // Executed on UI Thread
    }
});

Die Methode syncExec wartet dabei auf die Abarbeitung des Runnable, d.h. der aufrufende Thread wird für diese Zeit blockiert.

Eclipse Job-Framework

In Eclipse RCP-Anwendungen gibt es mit dem Job-Framework eine praktische Alternative zur Verwendung von Threads bzw. Display.(a)syncExec. Um Aktivitäten im Hintergrund auszuführen, kann ein Job implementiert und durch den Aufruf von schedule dem Job-Scheduler zur Abarbeitung übergeben werden:

Job job = new Job("Test") {
    protected IStatus run(IProgressMonitor monitor) {
        // Long-running operation here
        return Status.OK_STATUS;
    }
};
job.schedule();

Um außerhalb der Event-Verarbeitung (z.B. aus Hintergrund- oder Timer-Threads) auf das UI zugreifen, kann analog UIJob verwendet werden:

UIJob uiJob = new UIJob("Update UI") {
    public IStatus runInUIThread(IProgressMonitor monitor) {
        // Update UI here
        return Status.OK_STATUS;
    }
};
uiJob.schedule();

Ein typisches Anwendungsszenario für das Job-Framework in RCP Anwendungen ist es, längerdauernde Aktivitäten als Job im Hintergrund auszuführen, so dass in der Zwischenzeit Ereignisse auf dem UI-Thread verarbeitet werden können. Die Job stellen nach Fertigstellung dann bei Bedarf einen UIJob ein, der die Benutzeroberfläche aktualisiert:

Job / UIJob Threading

Fortschrittsanzeige für Jobs

Die Eclipse Workbench kann eine Fortschrittsanzeige für laufende Jobs anzeigen:

Diese Anzeige muss lediglich im WorkbenchWindowAdvisor aktiviert werden:

configurer.setShowProgressIndicator(true);

Zusätzlich kann die aus Eclipse bekannte View zur Fortschrittsanzeige in RCP-Anwendungen eingebunden werden:

Dazu ist das progressView als View der Anwendung zu registrieren. Als Klasse wird die ExtensionFactory angegeben, die das entsprechende View erzeugt:

<extension point="org.eclipse.ui.views">
    <view
        id="org.eclipse.ui.views.ProgressView"
        class="org.eclipse.ui.ExtensionFactory:progressView"
        icon="/icons/pview.gif"
        allowMultiple="false"
        name="Progress"/>
</extension>

Weitere Informationen

Über Ihre Kommentare und Hinweise freue ich mich sehr:
Ralf Ebert | Eclipse RCP Buch | SWT Display und Threading, Eclipse Jobs