Java SWT: wrapping syncExec and asyncExec to clean up code
Asked Answered
P

4

5

I have a Java Application using SWT as the toolkit, and I'm getting tired of all the ugly boiler plate code it takes to update a GUI element.

Just to set a disabled button to be enabled I have to go through something like this:

shell.getDisplay().asyncExec(new Runnable() {
    public void run() {
        buttonOk.setEnabled(true);
    }
});

I prefer keeping my source code as flat as I possibly can, but I need a whopping 3 indentation levels just to do something simple.

Is there some way I can wrap it? I would like a class like:

public class UIUpdater {
    public static void updateUI(Shell shell, *function_ptr*) {
        shell.getDisplay().asyncExec(new Runnable() {
           public void run() {
              //Execute function_ptr
           }
        });
    }
}

And can be used like so:

UIUpdater.updateUI(shell, buttonOk.setEnabled(true));

Something like this would be great for hiding that horrible mess SWT seems to think is necessary to do anything.

As I understand it, Java cannot do functions pointers. But Java 7 will have something called Closures which should be what I want. But in the meantime is there anything at all I can do to pass a function pointer or callback to another function to be executed?

As an aside, I'm starting to think it'd be worth the effort to redo this application in Swing, and I don't have to put up with this ugly crap and non-cross-platformyness of SWT.

Plaything answered 3/6, 2010 at 19:40 Comment(1)
I hope you know, that Swing is single threaded too. So you have to do the same as in SWT to update a GUI element from another thread. The only difference is that Swing does not throw an exception when you call Swting methods from the wrong thread. But you will see mayn weired bugs in your app when you make this wrong.Subfamily
H
4

I had a similar problem in my SWT code.

I wrote a base class that had nothing but asyncExec and syncExec methods. I had a method for every GUI method I wanted to call.

My non-GUI thread class extended the base class.

So in the non-GUI thread class, I'd have a call like setEnabled(shell, true)

In the base class, I'd define a public void setEnabled(Shell shell, boolean flag) method that would include the code in your first example.

Humbert answered 3/6, 2010 at 23:25 Comment(1)
This sounds like the only way this can be done without function pointers and/or closures.Plaything
F
2

I solved this some time ago in a closed source project. My API worked like this:

SWTUtils.asyncExec(display).on(this.getClass(), this).runInUIThread(123);

This call would run the method runInUIThread(.) on the instance this with the parameter 123. The clutter needed here is the parameter this.getClass().

Your example re-written:

SWTUtils.asyncExec(shell.getDisplay()).on(Button.class, buttonOk).setEnabled(true);

My implementation used CGLib to create a dynamic proxy for the given class. This proxy was returned by on(.), and the method call and its parameters were recorded and passed to display.asyncExec(.). I used Objenesis for instantiation of the proxy.

You might be surprised that there was no perceivable performance impact. Since the proxying was cached and the reflection API needed to call the actual method got very fast in java 1.6, I even used it for event listeners.

Obviously this is a rather insane solution ;) I cannot publish it, but if you are as pissed as I was by all these anonymous inner classes, you might want to go for it. Actually it's not too much code.

Frontispiece answered 6/6, 2010 at 19:38 Comment(0)
M
2

You say you want something like:

public class UIUpdater {
    public static void updateUI(Shell shell, *function_ptr*) {
        shell.getDisplay().asyncExec(new Runnable() {
           public void run() {
              //Execute function_ptr
           }
        });
    }
}

The fact that you can't have this is not a function of SWT, it's a function of Java as a programming language. Java's lack of higher-order functions and closures prevents you from creating a good DSL's that would hide that cruft for you, so the other answers you have received are as good as any unless you change languages.

Now, if you were using a language like Scala for example, I think the answer is much different. You'd probably create some kind of function mimicking a control structure that would ensure that your SWT calls would be executed on the main SWT display thread.

Something like:

val enabled = model.isEditable
Swt {
  text.setEnabled(enabled);
  composite.refresh();
}
Margaretmargareta answered 7/6, 2010 at 21:0 Comment(0)
P
0

With the Java8 functional interfaces you may extract the calls to the Display and the checks to Widget.isDisposed() into some helper methods:

public final class Widgets {

    public static void asyncExec(Viewer viewer, Runnable r) {
        if (viewer == null) return;
        asyncExec(viewer.getControl(), r);
    }

    public static <W extends Widget> void asyncExec(W widget, Consumer<W> consumer) {
        if (consumer != null) asyncExec(widget, () -> consumer.accept(widget));
    }

    public static void asyncExec(Widget widget, Runnable runnable) {
        exec(widget, runnable, (d, r) -> d.asyncExec(r));
    }

    public static void syncExec(Widget widget, Runnable runnable) {
        exec(widget, runnable, (d, r) -> d.syncExec(r));
    }

    public static <R, W extends Widget> R syncExec(W widget, Function<W, R> function, R defaultValue) {
        if (widget == null || widget.isDisposed()) return defaultValue;
        Display display = widget.getDisplay();
        AtomicReference<R> reference = new AtomicReference<>();
        display.syncExec(() -> {
            if (!widget.isDisposed()) reference.set(function.apply(widget));
        });
        R result = reference.get();
        return result != null ? result : defaultValue;
    }

    private static void exec(Widget widget, Runnable r, BiConsumer<Display, Runnable> func) {
        if (widget == null || widget.isDisposed()) return;
        Display display = widget.getDisplay();
        func.accept(display, () -> {
            if (!widget.isDisposed()) r.run();
        });
    }

}

You can then easily call the method on the Widget using a lambda expression:

Widgets.asyncExec(table, Table::redraw);
Rectangle bounds = Widgets.syncExec(table, Table::getBounds, defaultRectangle);
Postman answered 17/8, 2021 at 16:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.