本文档说明了如何使用 UiBinder(在 GWT 2.0 中引入)从 XML 标记构建小部件和 DOM 结构。它不涵盖绑定器的本地化功能 - 请参阅 国际化 - UiBinder 中有关这些功能的信息。

  1. 概述
  2. Hello World
  3. Hello Composite World
  4. 使用面板
  5. HTML 实体
  6. 事件处理程序的简单绑定
  7. 使用需要构造函数参数的小部件
  8. Hello Stylish World
  9. 以编程方式访问内联样式
  10. 使用 UiBinder 的外部资源
  11. 共享资源实例
  12. Hello Text Resources
  13. Hello HTML Resources
  14. 对同一个小部件应用不同的 XML 模板
  15. LazyPanel 和 LazyDomElement
  16. 为单元格渲染 HTML
  17. 使用 UiBinder 处理单元格事件
  18. 获取渲染的元素
  19. 使用 UiRenderers 访问样式

概述

从本质上讲,GWT 应用程序就是一个网页。当您布局网页时,编写 HTML 和 CSS 是最自然的方式来完成工作。UiBinder 框架允许您完全做到这一点:将您的应用程序构建为 HTML 页面,并在其中散布 GWT 小部件。

除了比通过代码构建 UI 更自然、更简洁之外,UiBinder 还可以使您的应用程序更高效。浏览器在通过将大量的 HTML 字符串塞入 innerHTML 属性来构建 DOM 结构方面比通过一堆 API 调用更出色。UiBinder 自然地利用了这一点,结果是,构建应用程序最令人愉悦的方式也是构建应用程序的最佳方式。

UiBinder

  • 有助于提高生产力和可维护性 - 易于从头开始创建 UI 或在模板之间复制/粘贴;
  • 使与 UI 设计师的协作更加容易,他们对 XML、HTML 和 CSS 比 Java 源代码更熟悉;
  • 在开发过程中提供从 HTML 模拟到真实交互式 UI 的平滑过渡;
  • 鼓励将 UI 的美观(声明式 XML 模板)与它的编程行为(Java 类)干净地分离;
  • 对从 Java 源代码到 XML 以及反之的交叉引用进行彻底的编译时检查;
  • 提供对国际化的直接支持,该支持与 GWT 的 i18n 功能 配合良好;以及
  • 鼓励更有效地使用浏览器资源,方法是方便使用轻量级 HTML 元素,而不是更重量级的窗口部件和面板。

但是,当您了解 UiBinder 是什么时,您也应该了解它不是什么。它不是一个渲染器,或者至少这不是它的重点。它的标记中没有循环、条件、if 语句,以及仅有的非常有限的表达式语言。UiBinder 允许您布局用户界面。最终,它仍然由小部件或其他控制器本身将数据行转换为 HTML 行。

本页的其余部分将通过一系列典型的用例说明如何使用 UiBinder。您将看到如何布局 UI,如何为其设置样式,以及如何将事件处理程序附加到它。 国际化 - UiBinder 解释了如何对其进行国际化。

快速入门:如果您想立即进入代码,请查看 此补丁。它包括将久负盛名的邮件示例更改为使用 UiBinder 的工作内容。寻找像 Mail.java 和 Mail.ui.xml 这样的文件对。

Hello World

这是一个非常简单的 UiBinder 模板示例,它不包含任何小部件,只有 HTML。对于 Hello World 示例来说,这似乎是一个奇怪的选择,因为它不是在 GWT 中管理 UI 的典型方式。但是,它向我们展示了基本的要素,并提醒我们,您不必为了使用模板而支付小部件的“税”。

<!-- HelloWorld.ui.xml -->

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
  <div>
    Hello, <span ui:field='nameSpan'/>.
  </div>
</ui:UiBinder>

现在假设您需要以编程方式读取和写入 span 中的文本(带有 ui:field='nameSpan' 属性的 span)。您可能希望编写实际的 Java 代码来执行此类操作,因此 UiBinder 模板具有一个关联的拥有者类,它允许以编程方式访问模板中声明的 UI 结构。上面模板的拥有者类可能如下所示

public class HelloWorld {
  interface MyUiBinder extends UiBinder<DivElement, HelloWorld> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField SpanElement nameSpan;

  private DivElement root;

  public HelloWorld() {
    root = uiBinder.createAndBindUi(this);
  }

  public Element getElement() {
    return root;
  }

  public void setName(String name) { nameSpan.setInnerText(name); }
}

然后,您可以像使用任何其他 UI 代码块一样实例化并使用拥有者类。我们将在后面的示例中演示如何将小部件与 UiBinder 结合使用,但本示例使用直接 DOM 操作


HelloWorld helloWorld = new HelloWorld(); // Don't forget, this is DOM only; will not work with GWT widgets Document.get().getBody().appendChild(helloWorld.getElement()); helloWorld.setName("World");

UiBinder 实例是工厂,它们生成 UI 结构并将其粘合到拥有者 Java 类。UiBinder<U, O> 接口声明了两个参数类型

  • U 是 ui.xml 文件中声明的根元素类型,由 createAndBindUi 调用返回
  • O 是拥有者类型,其 @UiField 要填充。

(在本示例中,U 是 DivElement,O 是 HelloWorld。)

ui.xml 文件中声明的任何对象,包括任何 DOM 元素,都可以通过其字段名称在拥有者 Java 类中使用。在这里,标记中的 <span> 元素被赋予 ui:field 属性,该属性设置为 nameSpan。在 Java 代码中,具有相同名称的字段用 @UiField 注释标记。当运行 uiBinder.createAndBindUi(this) 时,该字段将填充相应的 SpanElement 实例。

我们的 HelloWorld 对象没有什么特别之处,它没有超类。但它也可以扩展 UIObject。或小部件。或 Composite。没有限制。但是,请注意,用 @UiField 标记的字段具有默认可见性。如果它们要由绑定器填充,则不能是私有的。

Hello Widget World

这是一个使用小部件的 UiBinder 模板示例

<!-- HelloWidgetWorld.ui.xml -->

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:g='urn:import:com.google.gwt.user.client.ui'>

  <g:HTMLPanel>
    Hello, <g:ListBox ui:field='listBox' visibleItemCount='1'/>.
  </g:HTMLPanel>

</ui:UiBinder>

public class HelloWidgetWorld extends Composite { interface MyUiBinder extends UiBinder<Widget, HelloWidgetWorld> {} private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); @UiField ListBox listBox; public HelloWidgetWorld(String... names) { // sets listBox initWidget(uiBinder.createAndBindUi(this)); for (String name : names) { listBox.addItem(name); } } } // Use: HelloWidgetWorld helloWorld = new HelloWidgetWorld("able", "baker", "charlie");

请注意,我们正在使用小部件,并且还在创建小部件。HelloWorldWidget 可以添加到任何面板类中。

为了在 ui.xml 模板文件中使用一组小部件,您需要将它们的包绑定到 XML 命名空间前缀。这就是根 <ui:uibinder> 元素的此属性中发生的事情:xmlns:g='urn:import:com.google.gwt.user.client.ui'。这意味着,com.google.gwt.user.client.ui 包中的每个类都可以用前缀为 g 且标记名称与其 Java 类名称匹配的元素来使用,例如 <g:ListBox>

请注意,g:ListBox 元素具有 visibleItemCount='1' 属性吗?这将变为对 ListBox#setVisibleItemCount(int) 的调用。小部件的每个遵循 JavaBean 样式约定来设置属性的方法都可以以这种方式使用。

请特别注意 HTMLPanel 实例的使用。HTMLPanel 擅长混合任意 HTML 和小部件,UiBinder 与 HTMLPanel 配合得很好。通常,当您想要在小部件层次结构中使用 HTML 标记时,您需要一个 HTMLPanel 实例或 HTML 小部件

使用面板

任何面板(理论上,任何实现 HasWidgets 接口的东西)都可以在模板文件中使用,并且可以在其中包含其他面板。

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:g='urn:import:com.google.gwt.user.client.ui'>

  <g:HorizontalPanel>
    <g:Label>Keep your ducks</g:Label>
    <g:Label>in a row</g:Label>
  </g:HorizontalPanel>

</ui:UiBinder>

某些库存 GWT 小部件需要特殊的标记,您可以在其 javadoc 中找到相关说明。以下是 DockLayoutPanel 的工作方式


<g:DockLayoutPanel unit='EM'> <g:north size='5'> <g:Label>Top</g:Label> </g:north> <g:center> <g:Label>Body</g:Label> </g:center> <g:west size='10'> <g:HTML> <ul> <li>Sidebar</li> <li>Sidebar</li> <li>Sidebar</li> </ul> </g:HTML> </g:west> </g:DockLayoutPanel>

DockLayoutPanel 的子项被收集在组织元素中,例如 <g:north><g:center>。与模板中出现的几乎所有其他内容不同,它们不代表运行时对象。您不能为它们提供 ui:field 属性,因为您的 Java 类中没有东西可以放在该字段中。这就是为什么它们的名称不带大写字母,以提示您它们不是“真实”的。您会发现其他特殊非运行时元素遵循相同的约定。

需要注意的另一件事是,我们不能将 HTML 直接放在大多数面板中,而只能放在知道如何处理 HTML 的小部件中,具体来说,HTMLPanel,以及实现 HasHTML 接口的小部件(例如 <g:west> 下的侧边栏)。GWT 的未来版本可能会放弃此限制,但在此期间,您需要将 HTML 放置到了解 HTML 的小部件中。

HTML 实体

UiBinder 模板是 XML 文件,而 XML 不理解像 &amp;nbsp; 这样的实体。当您需要这样的字符时,您必须自己定义它们。为了方便起见,我们提供了一组定义,您可以通过适当地设置 DOCTYPE 来导入这些定义。

<!DOCTYPE ui:UiBinder SYSTEM "http://dl.google.com/gwt/DTD/xhtml.ent">

请注意,GWT 编译器实际上不会访问此 URL 来获取文件,因为它包含了该文件的副本。但是,您的 IDE 可能会获取它。

简单事件处理程序绑定

UiBinder 的目标之一是减少在 Java 代码中构建用户界面的繁琐操作,而 Java 中很少有东西比事件处理程序需要更多的枯燥乏味的样板代码。您编写的类似代码有多少次?


public class MyFoo extends Composite { Button button = new Button(); public MyFoo() { button.addClickHandler(new ClickHandler() { public void onClick(ClickEvent event) { handleClick(); } }); initWidget(button); } void handleClick() { Window.alert("Hello, AJAX"); } }

在 UiBinder 所有者类中,您可以使用 @UiHandler 注释来让所有这些匿名类无稽之谈为您编写。

public class MyFoo extends Composite {
  @UiField Button button;

  public MyFoo() {
    initWidget(button);
  }

  @UiHandler("button")
  void handleClick(ClickEvent e) {
    Window.alert("Hello, AJAX");
  }
}

但是,至少目前存在一个限制:您只能将 @UiHandler 与小部件对象抛出的事件一起使用,而不能与 DOM 元素一起使用。也就是说,<g:Button>,而不是 <button>

使用需要构造函数参数的小部件

模板中声明的每个小部件都是通过调用 GWT.create() 来创建的。在大多数情况下,这意味着它们必须是默认可实例化的;也就是说,它们必须提供一个零参数构造函数。但是,有一些方法可以解决这个问题。除了在 共享资源实例 下描述的 @UiFactory@UiField(provided = true) 机制外,您还可以使用 @UiConstructor 注释标记您自己的小部件。

假设您有一个需要构造函数参数的现有小部件

public CricketScores(String... teamNames) {...}

您在模板中使用它

<!-- UserDashboard.ui.xml -->

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:g='urn:import:com.google.gwt.user.client.ui'
    xmlns:my='urn:import:com.my.app.widgets' >

  <g:HTMLPanel>
    <my:WeatherReport ui:field='weather'/>

    <my:Stocks ui:field='stocks'/>
    <my:CricketScores ui:field='scores' />
  </g:HTMLPanel>
</ui:UiBinder>

public class UserDashboard extends Composite { interface MyUiBinder extends UiBinder<Widget, UserDashboard> {} private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class); public UserDashboard() { initWidget(uiBinder.createAndBindUi(this)); } }

导致错误

[ERROR] com.my.app.widgets.CricketScores has no default (zero args)
constructor. To fix this, you can define a @UiFactory method on the
UiBinder's owner, or annotate a constructor of CricketScores with
@UiConstructor.

因此,您要么使 @UiFactory 方法......

public class UserDashboard extends Composite {
  interface MyUiBinder extends UiBinder<Widget, UserDashboard>;
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  private final String[] teamNames;

  public UserDashboard(String... teamNames) {
    this.teamNames = teamNames;
    initWidget(uiBinder.createAndBindUi(this));
  }

  /** Used by MyUiBinder to instantiate CricketScores */
  @UiFactory CricketScores makeCricketScores() { // method name is insignificant
    return new CricketScores(teamNames);
  }
}

......注释构造函数......


public @UiConstructor CricketScores(String teamNames) { this(teamNames.split("[, ]+")); }

<!-- UserDashboard.ui.xml --> <g:HTMLPanel xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:my='urn:import:com.my.app.widgets' > <my:WeatherReport ui:field='weather'/> <my:Stocks ui:field='stocks'/> <my:CricketScores ui:field='scores' teamNames='AUS, SAF, WA, QLD, VIC'/> </g:HTMLPanel>

......或填写用 @UiField(provided=true) 标记的字段

public class UserDashboard extends Composite {
  interface MyUiBinder extends UiBinder<Widget, UserDashboard>;
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField(provided=true)
  final CricketScores cricketScores; // cannot be private

  public UserDashboard(CricketScores cricketScores) {
    // DI fans take note!
    this.cricketScores = cricketScores;
    initWidget(uiBinder.createAndBindUi(this));
  }
}

Hello Stylish World

使用 <ui:style> 元素,您可以直接在需要的地方定义 UI 的 CSS。

注意<ui:style> 元素必须是根元素的直接子元素。其他资源元素(<ui:image><ui:data>)也是如此。

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>

  <ui:style>
    .pretty { background-color: Skyblue; }
  </ui:style>

  <div class='{style.pretty}'>
    Hello, <span ui:field='nameSpan'/>.
  </div>

</ui:UiBinder>

会为您生成一个 CssResource 接口以及一个 ClientBundle。这意味着如果您在尝试使用它时拼错了类名(例如 {style.prettty}),编译器会发出警告。此外,您的 CSS 类名将被混淆,从而保护它免受其他 CSS 块中类似类名的冲突——不再有全局 CSS 命名空间!

实际上,您可以在单个模板中利用这一点

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
  <ui:style>
    .pretty { background-color: Skyblue; }
  </ui:style>

  <ui:style field='otherStyle'>
    .pretty { background-color: Orange; }
  </ui:style>

  <div class='{style.pretty}'>
    Hello, <span class='{otherStyle.pretty}' ui:field='nameSpan'/>.
  </div>

</ui:UiBinder>

最后,您不必将 CSS 放在 ui.xml 文件中。大多数实际项目可能将 CSS 保留在单独的文件中。在下面给出的示例中,src 值相对于 ui.xml 文件的位置。

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>
  <ui:style src="MyUi.css" />
  <ui:style field='otherStyle' src="MyUiOtherStyle.css">

  <div class='{style.pretty}'>
    Hello, <span class='{otherStyle.pretty}' ui:field='nameSpan'/>.
  </div>
</ui:UiBinder>

您还可以设置小部件的样式,而不仅仅是 HTML。使用 styleName 属性来覆盖小部件默认的任何 CSS 样式(就像在代码中调用 setStyleName() 一样)。或者,要添加类名而不覆盖小部件内置的样式设置,请使用特殊的 addStyleNames 属性

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
      xmlns:g='urn:import:com.google.gwt.user.client.ui'>
  <ui:style>
    .hot { color: magenta; }
    .pretty { background-color: Skyblue; }
  </ui:style>

  <g:PushButton styleName='{style.pretty}'>This button doesn't look like one</g:PushButton>
  <g:PushButton addStyleNames='{style.pretty} {style.hot}'>Push my hot button!</g:PushButton>

</ui:UiBinder>

请注意,addStyleNames 是复数。

以编程方式访问内联样式

您的代码将需要访问模板使用的至少某些样式。例如,假设您的窗口小部件在启用或禁用时需要更改颜色

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'>

  <ui:style type='com.my.app.MyFoo.MyStyle'>
    .redBox { background-color:pink; border: 1px solid red; }
    .enabled { color:black; }
    .disabled { color:gray; }
  </ui:style>

  <div class='{style.redBox} {style.enabled}'>I'm a red box widget.</div>

</ui:UiBinder>

public class MyFoo extends Widget { interface MyStyle extends CssResource { String enabled(); String disabled(); } @UiField MyStyle style; /* ... */ void setEnabled(boolean enabled) { getElement().addClassName(enabled ? style.enabled() : style.disabled()); getElement().removeClassName(enabled ? style.disabled() : style.enabled()); } }

<ui:style> 元素有一个新的属性,type='com.my.app.MyFoo.MyStyle'。这意味着它需要实现该接口(在上面 MyFoo 小部件的 Java 源代码中定义)并提供它调用的两个 CSS 类,enableddisabled

现在看看 MyFoo.java 中的 @UiField MyStyle style; 字段。这使代码能够访问为 <ui:style> 块生成的 CssResource。setEnabled 方法使用该字段在窗口小部件打开和关闭时应用启用和禁用样式。

您可以在具有指定类型的样式块中自由定义任意数量的其他类,但您的代码只能访问接口要求的那些类。

使用外部资源

有时您的模板需要使用来自模板外部的样式或其他对象。使用 <ui:with> 元素使它们可用。

<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
    xmlns:g='urn:import:com.google.gwt.user.client.ui'>

  <ui:with field='res' type='com.my.app.widgets.logoname.Resources'/>

  <g:HTMLPanel>

    <g:Image resource='{res.logo}'/>

    <div class='{res.style.mainBlock}'>
      <div class='{res.style.userPictureSprite}'/>

      <div>
        Well hello there
        <span class='{res.style.nameSpan}' ui:field='nameSpan'/>
      </div>
    </div>

  </g:HTMLPanel>
</ui:UiBinder>

/** * Resources used by the entire application. */ public interface Resources extends ClientBundle { @Source("Style.css") Style style(); @Source("Logo.jpg") ImageResource logo(); public interface Style extends CssResource { String mainBlock(); String nameSpan(); Sprite userPictureSprite(); } }
// Within the owner class for the UiBinder template
@UiField Resources res;

...

res.style().ensureInjected();

with” 元素声明一个包含资源对象的字段,其方法可以被调用来填充属性值。在这种情况下,它将通过调用 GWT.create(Resources.class) 来实例化。(继续读下去,看看如何传递实例而不是让它为您创建。)

请注意,ui:with 资源没有要求必须实现 ClientBundle 接口;这只是一个例子。

如果您需要更多资源灵活性,可以使用 <ui:attributes> 元素在其上设置参数。任何 setter 或构造函数参数都可以通过这种方式在资源对象上调用,就像在模板中的任何其他对象上一样。在下面的示例中,请注意 FancyResources 对象如何接受对先前示例中声明的 Resource 的引用。

public class FancyResources {
  enum Style {
    MOBILE, DESKTOP
  }

  private final Resources baseResources;
  private final Style style;

  @UiConstructor
  public FancyResources(Resources baseResources, Style style) {
    this.baseResources = baseResources;
    this.style = style;
  }
}

<ui:with field='fancyRes' type='com.my.app.widgets.logoname.FancyResources'> <ui:attributes style="MOBILE" baseResources="{res}"/> </ui:with>

共享资源实例

您可以通过 <ui:with> 元素使资源对您的模板可用,但代价是让它们为您实例化。如果您希望代码负责查找或创建该资源,则有两种方法可以进行控制。您可以使用 @UiFactory 标记工厂方法,或者您可以自己填写字段并将其注释为 @UiField(provided = true)

以下是如何使用 @UiFactory 来提供 先前示例 中模板所需的 Resources 实例。

public class LogoNamePanel extends Composite {
  interface MyUiBinder extend UiBinder<Widget, LogoNamePanel> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField SpanElement nameSpan;
  final Resources resources;

  public LogoNamePanel(Resources resources) {
    this.resources = resources;
    initWidget(uiBinder.createAndBindUi(this));
  }

  public void setUserName(String userName) {
    nameSpan.setInnerText(userName);
  }

  @UiFactory /* this method could be static if you like */
  public Resources getResources() {
    return resources;
  }
}

模板中任何类型为 Resources 的字段都将通过调用 getResources 来实例化。如果您的工厂方法需要参数,则需要将这些参数作为属性提供。

您可以通过使用 @UiField(provided = true) 使事情更简洁,并获得更精细的控制。

public class LogoNamePanel extends Composite {
  interface MyUiBinder extends UiBinder<Widget, LogoNamePanel> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField SpanElement nameSpan;

  @UiField(provided = true)
  final Resources resources;

  public LogoNamePanel(Resources resources) {
    this.resources = resources;
    initWidget(uiBinder.createAndBindUi(this));
  }

  public void setUserName(String userName) {
    nameSpan.setInnerText(userName);
  }
}

Hello Text Resources

现在我们有了资源,让我们回顾一下文档顶部的 Hello world 示例。仅仅为了显示该名称而编写代码有点麻烦,尤其是如果您永远不会更改它时。相反,使用 <ui:text> 将其直接缝合到模板中。

<ui:with field='res' type='com.my.app.widgets.logoname.Resources'/>

<div>
  Hello, <ui:text from='{res.userName}'/>.
</div>

优化说明:如果该资源来自 GWT 编译器识别为只返回编译时常量的方法(任何静态最终 String),它将通过内联的魔力成为模板本身的一部分——不会进行额外的函数调用。

Hello Html Resources

像我们在前面的示例中那样只依赖文本是相当有限的。有时您需要一个花哨的标记来重复使用,它并不需要完整的窗口小部件处理。在这种情况下,使用 <ui:safehtml> 将任何 SafeHtml 缝合到任何 HTML 上下文中。

<ui:with field='res' type='com.my.app.widgets.logoname.Resources'/>

<div>
  Hello, <ui:safehtml from='{res.fancyUserName}'/>.
</div>

您还有另一个 HTML 选项。任何 SafeHtml 类都可以直接使用,与窗口小部件类似。


<div> Hello, <my:FancyUserNameRenderer style="MOBILE">World</my:FancyUserNameRenderer>. </div>

您可以通过这种方式实现这样的渲染器。(请注意这里对 SafeHtmlTemplates 的略微人为的使用,以防范 XSS 攻击。)

public class FancyUserNameRenderer implements SafeHtml, HasText {
  enum Style {
    MOBILE, DESKTOP
  }

  interface Templates extends SafeHtmlTemplates {
    @SafeHtmlTemplates.Template("<span class=\"mobile\">{0}</span>")
    SafeHtml mobile(String name);

    @SafeHtmlTemplates.Template("<div class=\"desktop\">{0}</div>")
    SafeHtml desktop(String name);
  }
  private static final Templates TEMPLATES = GWT.create(Templates.class);

  private final Style style;
  private String name;

  @UiConstructor
  public FancyResources(Style style) {
    this.style = style;
  }

  void setText(String text) {
    this.name = text;
  }

  @Override
  String asString() {
    switch (style) {
      Style.MOBILE: return TEMPLATES.mobile(name);
    }
    return Style.DESKTOP: return TEMPLATES.desktop(name);
  }
}

虽然这是一个很好的技术,但它有限。以这种方式使用的对象有点像单程票。它们的 SafeHtml.asString() 方法在渲染时调用(实际上在大多数情况下,由于内联,在编译时调用)。因此,如果您通过 @UiField 在您的 java 代码中访问一个,它将不会有任何对它创建的 DOM 结构的句柄。

将不同的 XML 模板应用于同一个窗口小部件

您是一位 MVP 开发人员。您有一个不错的视图接口,以及一个实现该接口的模板化窗口小部件。您如何才能使用几个不同的 XML 模板来表示同一个视图?

公平警告:这仅仅是为了展示如何使用不同的 ui.xml 文件与相同的代码一起使用。它不是在应用程序中实现主题的可靠模式,可能不是实现主题的最佳方法。

public class FooPickerController {
  public interface Display {
    HasText getTitleField();
    SourcesChangeEvents getPickerSelect();
  }

  public void setDisplay(FooPickerDisplay display) { ... }
}

public class FooPickerDisplay extends Composite
    implements FooPickerController.Display {

  @UiTemplate("RedFooPicker.ui.xml")
  interface RedBinder extends UiBinder<Widget, FooPickerDisplay> {}
  private static RedBinder redBinder = GWT.create(RedBinder.class);

  @UiTemplate("BlueFooPicker.ui.xml")
  interface BlueBinder extends UiBinder<Widget, FooPickerDisplay> {}
  private static BlueBinder blueBinder = GWT.create(BlueBinder.class);

  @UiField HasText titleField;
  @UiField SourcesChangeEvents pickerSelect;

  public HasText getTitleField() {
    return titleField;
  }
  public SourcesChangeEvents getPickerSelect() {
    return pickerSelect;
  }

  protected FooPickerDisplay(UiBinder<Widget, FooPickerDisplay> binder) {
    initWidget(binder.createAndBindUi(this));
  }

  public static FooPickerDisplay createRedPicker() {
    return new FooPickerDisplay(redBinder);
  }

  public static FooPickerDisplay createBluePicker() {
    return new FooPickerDisplay(blueBinder);
  }
}

LazyPanel 和 LazyDomElement

您正在尝试从您的应用程序中挤出最后一点性能。您标签面板中的某些窗口小部件需要一段时间才能启动,而且它们一开始甚至对您的用户不可见。您想利用 LazyPanel。但是您自己也感到懒惰:它是抽象的,您真的不想处理扩展。


<gwt:TabLayoutPanel barUnit='EM' barHeight='1.5'> <gwt:tab> <gwt:header>Summary</gwt:header> <gwt:LazyPanel> <my:SummaryPanel/> </gwt:LazyPanel> </gwt:tab> <gwt:tab> <gwt:header>Profile</gwt:header> <gwt:LazyPanel> <my:ProfilePanel/> </gwt:LazyPanel> </gwt:tab> <gwt:tab> <gwt:header>Reports</gwt:header> <gwt:LazyPanel> <my:ReportsPanel/> </gwt:LazyPanel> </gwt:tab> </gwt:TabLayoutPanel>

这有所帮助,但您还可以做更多。

在应用程序中的其他地方是一个模板,它有很多 dom 元素字段。您知道,当您的 ui 构建时,会对每个 dom 元素执行 getElementById() 调用。在一个大型页面中,这会累加起来。通过使用 LazyDomElement,您可以将这些调用延迟到真正需要的时候——如果有的话。

public class HelloWorld extends UIObject { // Could extend Widget instead
  interface MyUiBinder extends UiBinder<DivElement, HelloWorld> {}
  private static final MyUiBinder uiBinder = GWT.create(MyUiBinder.class);

  @UiField LazyDomElement<SpanElement> nameSpan;

  public HelloWorld() {
    // createAndBindUi initializes this.nameSpan
    setElement(uiBinder.createAndBindUi(this));
  }

  public void setName(String name) { nameSpan.get().setInnerText(name); }
}

为单元格渲染 HTML

单元格窗口小部件 需要生成 HTML 字符串,但是编写代码来连接字符串以形成正确的 HTML 很快就变得很老套了。UiBinder 允许您使用相同的模板来渲染该 HTML。


<!-- HelloWorldCell.ui.xml --> <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'> <ui:with field='name' type='java.lang.String'/> <div> Hello, <span><ui:text from='{name}'/></span>. </div> </ui:UiBinder>

<ui:with> 标签定义字段用作渲染模板的数据。这些模板只能包含 HTML 元素,不能包含窗口小部件或面板。

现在,定义一个 HelloWorldCell 窗口小部件。添加一个扩展 UiRenderer 接口(而不是 UiBinder)的接口。

public class HelloWorldCell extends AbstractCell<String> {
  interface MyUiRenderer extends UiRenderer {
    void render(SafeHtmlBuilder sb, String name);
  }
  private static MyUiRenderer renderer = GWT.create(MyUiRenderer.class);

  @Override
  public void render(Context context, String value, SafeHtmlBuilder builder) {
    renderer.render(builder, value);
  }
}

UiBinder 使用 MyUiRenderer.render() 中参数的名称来匹配在 <ui:with> 标签中定义的字段。根据需要使用多个字段来渲染您的数据。

使用 UiBinder 处理单元格事件

单元格事件要求您编写代码来确定接收事件的确切单元格,甚至更多。UiBinder 为您处理了其中的大部分工作。它将根据事件类型和接收事件的 HTML 元素,将事件路由到您的处理程序方法。

以 HelloWorldCell.ui.xml 模板为例,让我们处理“name” <span> 元素中的点击事件。

首先,向 <span> 添加一个 ui:field 属性。这允许生成的代码将 span 元素与模板中的其他元素区分开来。

<div>
  Hello, <span ui:field='nameSpan'><ui:text from='{name}'/></span>.
</div>

MyUiRenderer 添加一个 onBrowserEvent 方法。渲染器接口中的 onBrowserEvent 只要求定义前三个参数。之后的任何其他参数都是为了您的方便,并将逐字传递给处理程序。第一个参数是 UiRenderer 用于将事件从 onBrowserEvent 分派到 Cell Widget 对象中的方法。

interface MyUiRenderer extends UiRenderer {
  void render(SafeHtmlBuilder sb, String name);
  onBrowserEvent(HelloWorldCell o, NativeEvent e, Element p, String n);
}

AbstractCell 知道您将处理 click 事件。

public HelloWorldCell() {
  super("click");
}

让 Cell onBrowserEvent 将处理委托给 renderer

@Override
public void onBrowserEvent(Context context, Element parent, String value,
    NativeEvent event, ValueUpdater<String> updater) {
  renderer.onBrowserEvent(this, event, parent, value);
}

最后,向 HelloWorldCell 添加处理程序方法,并使用 @UiHandler({"nameSpan"}) 对其进行标记。第一个参数的类型,ClickEvent,将决定处理的事件类型。

@UiHandler({"nameSpan"})
void onNameGotPressed(ClickEvent event, Element parent, String name) {
  Window.alert(name + " was pressed!");
}

获取渲染的元素

一旦渲染了单元格,就可以检索和操作标记为 ui:field 的元素。这在您需要操作 DOM 元素时很有用。

interface MyUiRenderer extends UiRenderer {
  // ... snip ...
  SpanElement getNameSpan(Element parent);
  // ... snip ...
}

通过传递 Cell 窗口小部件接收到的父元素来使用 getter。这些 getter 的名称必须与放在模板上的 ui:field 标签匹配,前面加上“get”。也就是说,对于名为 someNameui:field,getter 应该是 getSomeName(Element parent)

@UiHandler({"nameSpan"})
void onNameGotPressed(ClickEvent event, Element parent, String name) {
  renderer.getNameSpan(parent).setInnerText(name + ", dude!");
}

使用 UiRenderers 访问样式

UiRenderer 允许您定义 getter 来获取在模板中定义的样式。只需定义一个没有参数且与样式名称匹配的 getter,并返回样式类型。


<ui:style field="myStyle" type="com.my.app.AStyle"> .red {color:#900;} .normal {color:#000;} </ui:style> <div> Hello, <span ui:field="nameSpan" class="{myStyle.normal}"> <ui:text from="{name}"/></span>. </div>

定义样式接口

public interface AStyle extends CssResource {
  String normal();
  String red();
}

在 UiRenderer 接口中定义样式 getter,在样式名称前添加“get”


interface MyUiRenderer extends UiRenderer { // ... snip ... AStyle getMyStyle(); // ... snip ... }

然后在您需要的地方使用样式。请注意,您需要使用 `red()` 访问器方法获取样式名称。GWT 编译器会混淆样式的实际名称,以防止与应用程序中其他类似名称的样式发生冲突。

@UiHandler({"nameSpan"})
void onNameGotPressed(ClickEvent event, Element parent, String name) {
  String redStyle = renderer.getMyStyle().red();
  renderer.getNameSpan(parent).replaceClass(redStyle);
}