【问题标题】:Java SWT: wrapping syncExec and asyncExec to clean up codeJava SWT:包装 syncExec 和 asyncExec 以清理代码
【发布时间】:2010-06-03 19:40:40
【问题描述】:

我有一个使用 SWT 作为工具包的 Java 应用程序,我已经厌倦了更新 GUI 元素所需的所有丑陋样板代码。

只是为了设置一个禁用的按钮启用我必须经历这样的事情:

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

我更喜欢让我的源代码尽可能地平坦,但我需要高达 3 个缩进级别才能做一些简单的事情。

有什么方法可以包装它吗?我想要这样的课程:

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

而且可以这样使用:

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

这样的东西对于隐藏 SWT 似乎认为有必要做任何事情的可怕混乱非常有用。

据我了解,Java 不能做函数指针。但是 Java 7 会有一个叫做 Closures 的东西,这应该是我想要的。但与此同时,我能做些什么来将函数指针或回调传递给另一个要执行的函数吗?

顺便说一句,我开始认为在 Swing 中重做这个应用程序是值得的,而且我不必忍受这种丑陋的废话和 SWT 的非跨平台性。

【问题讨论】:

  • 我希望你知道,Swing 也是单线程的。因此,您必须像在 SWT 中一样从另一个线程更新 GUI 元素。唯一的区别是当您从错误的线程调用 Swing 方法时,Swing 不会抛出异常。但是当你犯了这个错误时,你会在你的应用中看到很多奇怪的错误。

标签: java user-interface swt


【解决方案1】:

我在我的 SWT 代码中遇到了类似的问题。

我编写了一个只有 asyncExec 和 syncExec 方法的基类。对于我想调用的每个 GUI 方法,我都有一个方法。

我的非 GUI 线程类扩展了基类。

所以在非 GUI 线程类中,我会调用 setEnabled(shell, true)

在基类中,我将定义一个 public void setEnabled(Shell shell, boolean flag) 方法,该方法将包含您的第一个示例中的代码。

【讨论】:

  • 这听起来像是没有函数指针和/或闭包的唯一方法。
【解决方案2】:

我前段时间在一个闭源项目中解决了这个问题。我的 API 是这样工作的:

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

此调用将在带有参数123 的实例this 上运行方法runInUIThread(.)。这里需要的混乱是参数this.getClass()

你的例子被重写了:

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

我的实现使用 CGLib 为给定的类创建动态代理。这个代理是由on(.)返回的,方法调用和它的参数被记录下来并传递给display.asyncExec(.)。我使用 Objenesis 来实例化代理。

您可能会惊讶于没有明显的性能影响。由于代理被缓存并且调用实际方法所需的反射 API 在 java 1.6 中变得非常快,我什至将它用于事件侦听器。

显然,这是一个相当疯狂的解决方案;)我无法发布它,但如果您像我一样对所有这些匿名内部类感到愤怒,您可能想要这样做。其实代码也不多。

【讨论】:

    【解决方案3】:

    你说你想要这样的东西:

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

    您不能拥有它的事实不是 SWT 的功能,而是 Java 作为编程语言的功能。 Java 缺乏高阶函数和闭包使您无法创建一个很好的 DSL 来为您隐藏这些问题,因此除非您更改语言,否则您收到的其他答案与任何答案一样好。

    现在,如果您使用的是 Scala 之类的语言,我认为答案会大不相同。您可能会创建某种模仿控制结构的函数,以确保您的 SWT 调用将在主 SWT 显示线程上执行。

    类似:

    val enabled = model.isEditable
    Swt {
      text.setEnabled(enabled);
      composite.refresh();
    }
    

    【讨论】:

      【解决方案4】:

      使用 Java8 函数式接口,您可以将对 Display 的调用和对 Widget.isDisposed() 的检查提取到一些辅助方法中:

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

      然后您可以使用 lambda 表达式轻松调用 Widget 上的方法:

      Widgets.asyncExec(table, Table::redraw);
      Rectangle bounds = Widgets.syncExec(table, Table::getBounds, defaultRectangle);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-07-24
        • 1970-01-01
        • 1970-01-01
        • 2011-04-04
        相关资源
        最近更新 更多