tags:

views:

156

answers:

3

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.

+2  A: 

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.

Gilbert Le Blanc
This sounds like the only way this can be done without function pointers and/or closures.
jonescb
+1  A: 

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.

the.duckman
+2  A: 

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();
}
arcticpenguin