GWT表达层:谁做了什么?

时间:2012-09-07 12:37:26

标签: java gwt widget uibinder gwt-designer

我正在学习GWT并尝试围绕所有UI选项。我正在尝试了解何时/何地/如何使用Widgets,UIBinder,GWT Designer和自定义小部件。具体做法是:

  • GWT Designer生成什么作为输出? UIBinder XML?可以肯定地说,当您不想手动编写UIBinder XML代码时可以使用GWT Designer,但它们是否具有相同的用途?
  • 何时使用UIBinder而非Widgets? Widgets是否被翻译成UIBinder XML,但是它们中有更多的代码(事件处理等)?在这种情况下,我会假设 UIBinder XML的优势是代码更少,因此性能更快?在两者之间进行选择时应考虑的其他因素吗?
  • 您是否编写了大量UIBinder XML“代码段”或将它们全部打包成一个大的单片XML文件?
  • 为了制作我自己的自定义组件,扩展com.google.gwt.user.client.ui.*com.google.gwt.user.client.ui.Composite之间的区别是什么?
  • 分工:Widgets / UIBinder与Layouts vs CSS文件:谁做了什么?

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

3 个答案:

答案 0 :(得分: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,但我通常会找到Widget s,可以添加到Panel 1}},比Element s

  • 更容易和更好地工作

我如何构建小部件

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

如果小部件非常简单,我将有一个扩展Composite的类并实现该接口。小部件将非常简单,并向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);
  }
}

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

这里需要注意的一点是,由于SomethingWithFinancialChartWidget取决于接口FinancialChart,因此它无法实例化该对象,因为它只是一个接口。这就是chart@UiField(provided = true)的Java代码中设置为SomethingWithFinancialChartWidget的原因。 Gin设置从FinancialChart接口到具体类的绑定,以便它可以为@Inject的{​​{1}}构造函数提供实现,然后设置SomethingWithFinancialChartWidget为UiBinder提供它需要的对象。

为MVP中的所有接口和实现创建了许多文件,但抽象绝对值得,因为它们可以轻松地对演示者进行单元测试,并允许您更改顶级接口{{1}在这个例子中,例如,实现了从单个this.chart类更改为MVP,没有客户需要更改。

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

答案 1 :(得分:2)

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

  
    

没有魔法!

  

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

UiBinder会:

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

UiBinder最常见的用途是我上面谈到的那些对象是小部件。然后组装它们意味着将小部件添加到其他容器小部件中 这就是为什么你通常会在ui:field内部使用UiBinder来为Composite方法提供价值的原因:UiBinder会创建并整理所需的所有小部件并返回最顶层的小部件,以便使用作为复合小部件的根。

你已经理解了:你不会使用UiBinder而不是小部件,并且小部件不会翻译成UiBinder XML (实际上恰恰相反)。< / p>

现在提出更具体的问题:

  

您是否编写了大量UIBinder XML“代码段”或将它们全部打包成一个大的单片XML文件?

UiBinder是一种实现细节。大多数情况下,您将使用UiBinder构建(可重用)小部件(复合);它不会发生在外面的世界,它只能看到一个小部件,而不是“用UiBinder构建的东西” 这意味着您将拥有大量的小型/中型UiBinder模板(通常在您的应用中每个屏幕一个,或者更为一般的规则,每个复杂的UI组件一个< / em>的)。

  

为了制作我自己的自定义组件,扩展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与Layouts vs CSS文件:谁做了什么?

遗憾的是,没有一个简单的答案。 取决于。

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

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

有权衡。你必须做实验。最后,使用您认为对您有益的内容。没有一个适合所有人的。


最后,尽管它是最常见的用例,但请注意,UiBinder可以在没有小部件的情况下使用,例如create non-composite widgets(或overflow: scroll s,但是它们的用例非常少;你是'我会看到它们用于UiObjectMenuItem,例如),使用TreeItem而非setElement()create custom Cells
实际上,initWidget()的用例也不多:您可以简单地实现Composite(返回IsWidget的结果,调用一次并缓存在私有字段中)它应该在预期/接受小部件的任何地方工作。

答案 2 :(得分:1)

  1. 你是对的。 GWT Designer输出UIBinder XML代码。 UIBinder XML代码只是一种声明性语法,可转换为实际的Java代码(就像在java类中创建元素一样)。
  2. UIBinder和Widgets在某种程度上无法比较。您可以使用UiBinder和普通Java代码一起创建窗口小部件,但是您可以使用相同的功能仅使用Java代码实现相同的窗口小部件。 UiBinder只是让它更具可读性,因为它是声明性的,你最终不会实例化很多UI元素(即TextBox textbox = new TextBox();container.add(textBox)
  3. 我会为组件和小部件使用单独的UiBinder代码段,然后将它们放入父UIBinder文件(即Presenter)。但是,将每个小组件(即表单的Label + TextBox)放入单独的UIBinder片段中可能没有意义。你必须找到一个很好的平衡(虽然在这个例子中你可以创建一个Label + TextBox组件,如果你使用UiBinder广泛使用它。)
  4. 如果您创建一个由现有组件组成的新组件,请使用Composite。如果您使用WidgetIsWidget创建无法使用现有窗口小部件或组件构建的唯一新窗口小部件或组件,则使用LayoutPanels
  5. 我通常这样做:
    • 尽可能多地使用UIBinder。例如,对页面结构使用ClientBundle
    • 为您在整个应用中使用的常见样式创建主CSSResource<ui:style>
    • 当您需要仅使用一次的特定样式时,将MVP声明放在相应的UiBinder文件中。
    • 为可重用组件创建小部件(再次,您可以按照3点)。

    最后Michael Davidson提出了一些关于{{1}}和测试的好点。 我强烈建议你遵循它们。