7

我正在学习 GWT,并试图围绕所有 UI 选项。我试图弄清楚何时/何地/如何使用小部件、UIBinder、GWT 设计器和自定义小部件。具体来说:

  • GWT Designer 生成什么作为输出?UIBinder XML?当您不想手动编码 UIBinder XML 时可以使用 GWT Designer 是否安全,但它们都服务于相同的确切目的?
  • 何时在小部件上使用 UIBinder?是不是 Widget 被翻译成 UIBinder XML,但其中包含更多代码(事件处理等)?在那种情况下,我会假设UIBinder XML 的优点是代码更少,因此性能更快?在两者之间进行选择时还应考虑其他因素吗?
  • 您是编写大量 UIBinder XML“片段”还是将它们全部打包到一个大的整体 XML 文件中?
  • com.google.gwt.user.client.ui.*对于制作我自己的自定义组件,扩展和扩展有什么区别com.google.gwt.user.client.ui.Composite
  • 分工:Widgets/UIBinder vs. Layouts vs CSS files:谁做什么?

对我来说,我觉得所有这些东西都做同样的事情,也许这只是 GWT 为您提供多种方式来完成演示的方式?如果没有,请在这些项目上纠正我。

4

3 回答 3

5
  • 当小部件非常简单时,我只在 UiBinder 上使用小部件。例如,我只是将两个小部件添加到Panel. 如果涉及任何 CSS,我总是使用 UiBinder,因为它更容易使用样式。

  • 小部件不会被翻译成 UiBinder XML。所有 GWT 代码都变成了添加和操作 DOM 元素的 JavaScript,因此所有内容都被转换为可执行语言,而不是模板系统。

  • 我写了很多 UiBinder 片段。我尝试遵循您可以在网络上找到的关于抽象和组合的良好规则。

  • 如果您有任何重要的逻辑,MVP 模式是必须的,因为使用 JUnit 测试无 GWT 的演示者非常快速和容易,而 GWT 测试的开销要大得多,而且速度要慢得多。

  • 我喜欢在 CSS 文件中保留尽可能多的样式,因为分离关注点是一种标准的好习惯,您可以压缩和捆绑您的 CSS 文件,以及许多其他原因,这些原因与您在普通 HTML 页面中放置 CSS 的原因相同在单独的文件中,而不是直接在页面上。

  • 我从不使用 GWT 设计器。我总是更喜欢干净的代码,而不是通常由任何 UI 代码生成器吐出的疯狂垃圾。

  • 99% 的情况下,我的小部件会扩展Composite,因为要么我正在使用 UiBinder,要么我正在向Panel. 即使我只有一个小部件,我发现扩展Composite并将我的一个小部件添加到SimplePanel. 我很少扩展Widget,因为你必须调用来Document.get().createFooElement()创建一个 DOM Element,但我通常发现Widgets,可以添加到Panels,比Elements更容易和更安全

我如何构建小部件

每个小部件都实现了一个扩展接口IsWidget。每个想要使用的人都Widget应该依赖于接口,而不是底层类。这提供了一个单一的、无 JSNI 的抽象。

如果小部件非常简单,我将有一个扩展Composite和实现接口的类。小部件将非常简单并向 a 添加一些项目,Panel或者它将使用 UiBinder。

如果小部件有我想测试的重要逻辑,我使用 MVP 模式。将有一个实现小部件的“公共”接口的演示者类,一个扩展IsWidget演示者所依赖的视图接口,以及一个实现视图接口的视图小部件。

为小部件提供单个“公共”接口的一个好处是,如果逻辑变得复杂,您可以从使用单个Composite类实现接口更改为使用 MVP,并且使用小部件的人根本不需要更改。

我使用 Gin 将所有接口和实现连接在一起。

例子

这最好用一些代码来解释。假设我有一个要在多个页面上使用的图表,所以我决定为它制作一个可重复使用的小部件。在显示之前处理 RPC 响应有一些重要的逻辑,所以我想对它进行彻底的单元测试。我会用这样的东西:

public interface FinancialChart extends IsWidget {
  void setTickerSymbol(String tickerSymbol);
}

class FinancialChartPresenter extends Composite implements FinancialChart {
  private final FinancialChartView view;      
  private final DataServiceAsync service;

  @Inject(FinancialChartView view, DataServiceAsync service) {
    this.view = view;
    this.service = service;
  }

  @Override public Widget asWidget() {
    return view.asWidget();
  }

  @Override public void setTickerSymbol(String tickerSymbol) {
    service.getData(tickerSymbol, new AsyncCallback<FinancialData>() {
      @Override public void onFailure(Throwable t) {
        // handle error
      }

      @Override public void onSuccess(FinancialData data) {
        SimpleData simpleData = // do some parsing with presentation-specific
          // logic, e.g. make dramatic increases or decreases in price have a
          // a different color so they stand out.  End up with something simple
          // that's essentially some (x, y) points that the dumb view can plot
          // along with a label and color for each point.
        view.drawGraph(simpleData);
      }
  }
}

interface FinancialChartView extends IsWidget {
  void drawGraph(SimpleData simpleData);
}

class FinancialChartWidget extends Composite implements FinancialChartView {
  @Override public void drawGraph(SimpleData simpleData) {
    // plot the points on a chart.  set labels.  etc.
  }
}

class SomethingWithFinancialChartWidget extends Composite
    implements SomethingWithFinancialChart {
  interface Binder extends UiBinder<Widget, SomethingWithFinancialChartWidget> {}

  @UiField(provided = true) final FinancialChart chart;

  @Inject SomethingWithFinancialChartWidget(Binder binder, FinancialChart chart) {
    this.chart = chart;
    initWidget(binder.createAndBindUi(this));
  }
}

// In SomethingWithFinancialChartWidget.ui.xml
<ui:HTMLPanel>
  <!-- lots of stuff -->
  <mynamespace:FinancialChart ui:field="chart" />
  <!-- lots more stuff -->
</ui:HTMLPanel>

class MyPackagesGinModule extends AbstractGinModule {
  @Override protected void configure() {
    bind(FinancialChart.class).to(FinancialChartPresenter.class);
    bind(FinancialChartView.class).to(FinancialChartWidget.class);
  }
}

这允许我为 JSNI 编写非常简单、彻底和快速的 JUnit 测试,FinancialViewPresenter因为它没有需要 JSNI 的 GWT 依赖项,它必须作为慢得多的 GWT 测试用例的一部分在浏览器中运行。您可以创建一个模拟FinancialChartView.

这里要注意的一件事是,由于SomethingWithFinancialChartWidget依赖于 interface FinancialChart,它不能实例化该对象,因为它只是一个接口。这就是为什么在 .java 代码中chart设置的原因。Gin 设置从接口到具体类的绑定,以便它可以为 的构造函数提供实现,然后设置为 UiBinder 提供所需的对象。@UiField(provided = true)SomethingWithFinancialChartWidgetFinancialChart@InjectSomethingWithFinancialChartWidgetthis.chart

FinancialChart在 MVP 中为所有接口和实现创建了许多文件,但抽象是绝对值得的,因为它们使演示者的单元测试变得容易,并允许您更改在此示例中的顶级接口的实现方式,例如,从单个Composite类更改为 MVP,无需客户端更改。

我确信有一些实现细节可能不是很清楚,或者我掩盖了一些事情,例如 GWT 测试,所以请发表评论,我可以编辑我的答案以更新和澄清。

于 2012-09-07T14:19:29.813 回答
2

让我们与您的问题保持距离,并立即回答整体问题。但首先,我最喜欢的标语:

没有魔法!

UiBinder 是一个接受 XML 定义并从中生成 Java 代码的工具。也就是说(对于通过调用构造的所有内容都是如此GWT.create()没有魔法!)您可以用Java代码手动编写相同的内容。换句话说,UiBinder 只是一个让你更有效率的工具,它需要更少的代码来实现相同的目标。但是由于它生成的代码你可以手工编写,它不会在运行时提供更好的性能(也不会更糟);然而,它更容易使用导致更好性能的模式,即HTMLPanel:在纯 Java 中维护HTMLPanel基于代码的代码是一场噩梦,而与 UiBinder 完全相反。

UiBinder 将:

  • 生成一个隐含ClientBundle的,CssResource每个<ui:style>隐含的ImageResource,每个隐含的,每个<ui:image>隐含DataResource<ui:data>
  • Messages为 I18N out of<ui:msg>等生成一个隐式接口<ui:ph>
  • 根据XML 中的属性和Java 代码中的注释,将对象注入到伙伴对象(称为所有者并作为参数传递给createAndBindUi方法)的字段中;或者可能从伙伴对象中检索它们,如果注释有ui:field@UiFieldprovided=true
  • 使用各种专门的解析器,或带@UiConstructor注释的构造函数,或@UiFactory伙伴对象的带注释的方法来创建对象,或者作为最后的手段使用GWT.create()调用
  • 合作伙伴对象的bind @UiHandler-annotated 方法作为 XML 模板中小部件的事件处理程序(基于ui:field属性)
  • 最后但同样重要的是:将所有这些构造的对象组装在一起,并设置它们的一些属性(在 XML 中的属性之外)。

UiBinder 最常见的用途是让我上面谈到的那些对象成为小部件。组装它们意味着将小部件添加到其他容器小部件中。
这就是为什么您通常会在 a 中看到 UiBinder 用于Composite为方法提供值的原因initWidget():UiBinder 将创建并组合所有需要的小部件,并返回最顶层的小部件,用作复合小部件的根。

你已经明白了:你不会在 widgets 上使用 UiBinder,而且 widgets 也不会被翻译成 UiBinder XML(实际上恰恰相反)。

现在回答您更具体的问题:

您是编写大量 UIBinder XML“片段”还是将它们全部打包到一个大的整体 XML 文件中?

UiBinder 是一种实现细节。大多数时候,您将使用 UiBinder 构建(可重用)小部件(组合);它不会传到外面的世界,它只会看到一个小部件,而不是“用 UiBinder 构建的东西”。
这意味着您将拥有许多中小型 UiBinder 模板(通常,您的应用程序中的每个屏幕至少一个,或者更一般的规则,每个复杂的 UI 组件一个)。

为了制作我自己的自定义组件,扩展 com.google.gwt.user.client.ui.* 与 com.google.gwt.user.client.ui.Composite 有什么区别?

Ümit 回答了那个问题,以及文档:https ://developers.google.com/web-toolkit/doc/latest/DevGuideUiCustomWidgets

分工:Widgets/UIBinder vs. Layouts vs CSS files:谁做什么?

不幸的是,没有简单的答案。这取决于。

这取决于您正在构建的应用程序类型以及您想要的 UI 类型。对于应用程序的“全局布局”,您将使用几个RootPanel或一个 bigHTMLPanel并使用 HTML 和 CSS 进行布局(在 HTML 主机页面中,或在 UiBinder 模板中作为应用程序的外壳),或者您将使用所谓的布局面板

对于更本地化的布局(在复合小部件内),我建议使用HTMLPanelor FlowPanel; 或可能的布局面板。如果您需要处理滚动事件,或者如果您需要在容器小部件内滚动,则使用 a ScrollPanel,否则只需overflow: scroll在 CSS 中使用。

有权衡。你必须做实验。最后,使用你认为对你有好处的东西。没有一种万能的。


最后,虽然它是最常见的用例,但请注意 UiBinder 可以在没有小部件的情况下使用,例如创建非复合小部件(或UiObjects,但它们的用例很少;您会看到它们用于MenuItems 和TreeItems例如),使用setElement()而不是initWidget(),或创建自定义 Cells
实际上,两者都没有太多用例Composite:您可以简单地实现IsWidget(返回 的结果createAndBindUi,调用一次并缓存在私有字段中),它应该可以在任何预期/接受小部件的地方工作。

于 2012-09-07T15:48:27.717 回答
1
  1. 你是对的。GWT Designer 输出UIBinderXML 代码。UIBinderXML 代码只是一种转换为实际Java代码的声明性语法(就像您在 java 类中创建元素一样)。
  2. UIBinder 和 Widgets 在某种程度上无法比较。您可以将 UiBinder 与普通 Java 代码一起使用来创建小部件,但您可以仅使用 Java 代码实现具有相同功能的相同小部件。UiBinder只是使它更具可读性,因为它是声明性的,并且您最终不会实例化很多 UI 元素(即TextBox textbox = new TextBox();container.add(textBox)
  3. 我会UiBinder为组件和小部件使用单独的片段,然后将它们放入父 UIBinder 文件(即Presenter)中。然而,将每一个微小的组件(即表单的标签 + 文本框)放入单独的 UIBinder 片段中可能没有意义。你必须找到一个很好的平衡点(尽管在这个例子中,如果你使用 UiBinder 广泛使用它,你可以创建一个 Label + TextBox 组件)。
  4. 如果您创建一个包含现有组件的新组件,请使用Composite. 如果您创建了一个独特的新小部件或组件,而该小部件或组件无法使用现有的构建,请使用WidgetIsWidget
  5. 我通常这样做:

.

  • 尽可能多地使用 UIBinder。例如LayoutPanels用于页面结构。
  • 创建您在整个应用程序中使用的主要样式ClientBundle和常用样式。CSSResource
  • <ui:style>当您需要只使用一次的特定样式时,将声明放在相应的 UiBinder 文件中。
  • 为可重用组件创建小部件(同样您可以遵循 3 点)。

最后,Michael Davidson 提出了一些关于MVP测试的好观点。我强烈建议关注他们。

于 2012-09-07T14:36:56.357 回答