本文档说明了如何使用 UiBinder(在 GWT 2.0 中引入)从 XML 标记构建小部件和 DOM 结构。它不涵盖绑定器的本地化功能 - 请参阅 国际化 - UiBinder 中有关这些功能的信息。
- 概述
- Hello World
- Hello Composite World
- 使用面板
- HTML 实体
- 事件处理程序的简单绑定
- 使用需要构造函数参数的小部件
- Hello Stylish World
- 以编程方式访问内联样式
- 使用 UiBinder 的外部资源
- 共享资源实例
- Hello Text Resources
- Hello HTML Resources
- 对同一个小部件应用不同的 XML 模板
- LazyPanel 和 LazyDomElement
- 为单元格渲染 HTML
- 使用 UiBinder 处理单元格事件
- 获取渲染的元素
- 使用 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 不理解像 &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 类,enabled
和 disabled
。
现在看看 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”。也就是说,对于名为 someName
的 ui: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);
}