Locked History Actions

GWT/DevGuideUiPanels

Developer's Guide - Layout Using Panels

http://code.google.com/intl/ja/webtoolkit/doc/latest/DevGuideUiPanels.htmlの訳(2011/11/25時点、予定)

GWTにおけるパネルは他のUIにおけるlayoutに相当する。 最も大きな違いとしては、GWTのパネルはその子ウィジェットをレイアウトするのにHTML要素を使用する点である。

パネルはウィジェットや他のパネルを格納し、ブラウザ中でのUIインターフェースのレイアウト定義に用いられる。

基本的なパネル

RootPanel

RootPanelは他のウィジェットすべてが最終的にアタッチされるト最上位のパネルである。 RootPanel.get()は、HTMLドキュメント中の<body>要素をラップする単一のパネルを取得する。

RootPanel.get(String id)を使うことによって、ページ中のいかなる他要素をも取得することができる。

FlowPanel

FlowPanelは最も単純なパネルである。単一の<div>要素を作成し、そこに子を何の変更も無しに直接アタッチする。 子ウィジェットのレイアウトに自然なHTMLの流れを使いたい場合に、このクラスを使うこと。

HTMLPanel

このクラスはHTML構造を定義するのに単純な方法を提供する。 within which widgets will be embedded at defined points. 直接使うこともできるが、UiBinderテンプレートで使用するのが普通だ。

FormPanel

HTMLフォームの振る舞いを再現する必要のあるときにFormPanelを使うことができる(つまり、POSTリクエストを期待するサーバとの作用や、ブラウザにおけるデフォルトのキーボードの振る舞いを取得したい場合:訳注:?)。 このパネルに含まれるウィジェットは、<form>要素に含まれることになる。

ScrollPanel

他のパネル中にスクロール可能領域を作成したいときにScrollPanelを使う。 このパネルは、適切なスクロールが必要になる明示的なサイズを供給するレイアウトパネルとうまく協調する(後述する)。

PopupPanelとDialogBox

これれは、単純なポップアップとモーダルなダイアログを作る。 これらはブラウザ中の既存のコンテンツにオーバラップし、互いにスタックすることが可能である。

GridとFlexTable

これら二つのウィジェットは伝統的な<table>要素を作成する。 それに加えて、パネルとしても使用可能であるが、この場合はセル中に任意のウィジェットを格納することができる。

レイアウトパネル

GWT 2.0では多くの新パネルが道入されたが、これらは高速で予測可能なアプリケーションレベルのレイアウトの安定したベースとなるものである。 背景と詳細については、後述する"Design of the GWT 2.0 layout system"を参照してほしい。

レイアウトシステムの大部分は一連のパネルウィジェットで実現される。 これらのウィジェットは下層のレイアウトシステムを用いて、子ウィジェットを信頼できるやり方でポジショニングする。

RootLayoutPanel

このパネルは他のすべてのレイアウトパネルがアタッチされるルートコンテナの役割をするシングルトンである (詳細は後述するRequiresResizeProvidesResizeを参照のこと)。 このパネルはLayoutPanelを継承しており、任意の制約をつけた複数の子をポジショニングすることができる。

多くの場合、以下のスニペットに示すように、他のパネルのコンテナとしてRootLayoutPanelを使うことになる。 ここでは、ブラウザのクライアントエリアをDockLayoutPanelで覆っている。

DockLayoutPanel appPanel = new DockLayoutPanel(Unit.EM);
RootLayoutPanel.get().add(appPanel);

LayoutPanel

LayoutPanelは最も一般的なレイアウトメカニズムであり、他のレイアウトは多くの場合これを元にしている。

  • Its closest analog is AbsolutePanel, but it is significantly more general in that it allows its children to be positioned using arbitrary constraints, as in the following example:

Widget child0, child1, child2;
LayoutPanel p = new LayoutPanel();
p.add(child0); p.add(child1); p.add(child2);

p.setWidgetLeftWidth(child0, 0, PCT, 50, PCT);  // Left panel
p.setWidgetRightWidth(child1, 0, PCT, 50, PCT); // Right panel

p.setWidgetLeftRight(child2, 5, EM, 5, EM);     // Center panel
p.setWidgetTopBottom(child2, 5, EM, 5, EM);

LayoutPanel example

DockLayoutPanel

DockLayoutPanel serves the same purpose as the existing DockPanel widget, except that it uses the layout system to achieve this structure without using tables, and in a predictable manner. You would often use to build application-level structure, as in the following example:

DockLayoutPanel p = new DockLayoutPanel(Unit.EM); p.addNorth(new HTML("header"), 2); p.addSouth(new HTML("footer"), 2); p.addWest(new HTML("navigation"), 10); p.add(new HTML(content));

DockLayoutPanel example

Note that DockLayoutPanel requires the use of consistent units for all children, specified in the constructor. It also requires that the size of each child widget (except the last, which consumes all remaining space) be specified explicitly along its primary axis. SplitLayoutPanel

The SplitLayoutPanel is identical to the DockLayoutPanel (and indeed extends it), except that it automatically creates a user-draggable splitter between each pair of child widgets. It also supports only the use of pixel units. Use this instead of HorizontalSplitPanel and VerticalSplitPanel.

SplitLayoutPanel p = new SplitLayoutPanel(); p.addWest(new HTML("navigation"), 128); p.addNorth(new HTML("list"), 384); p.add(new HTML("details"));

SplitLayoutPanel example StackLayoutPanel

StackLayoutPanel replaces the existing StackPanel (which does not work very well in standards mode). It displays one child widget at a time, each of which is associated with a single "header" widget. Clicking on a header widget shows its associated child widget.

StackLayoutPanel p = new StackLayoutPanel(Unit.EM); p.add(new HTML("this content"), new HTML("this"), 4); p.add(new HTML("that content"), new HTML("that"), 4); p.add(new HTML("the other content"), new HTML("the other"), 4);

StackLayoutPanel example

Note that, as with DockLayoutPanel, only a single unit type may be used on a given panel. The length value provided to the add() method specifies the size of the header widget, which must be of a fixed size. TabLayoutPanel

As with the existing TabPanel, TabLayoutPanel displays a row of clickable tabs. Each tab is associated with another child widget, which is shown when a user clicks on the tab.

TabLayoutPanel p = new TabLayoutPanel(1.5, Unit.EM); p.add(new HTML("this content"), "this"); p.add(new HTML("that content"), "that"); p.add(new HTML("the other content"), "the other");

TabLayoutPanel example

The length value provided to the TabLayoutPanel constructor specifies the height of the tab bar, which you must explicitly provide.

When should I not use layout panels?

The panels described above are best used for defining your application's outer structure — that is, the parts that are the least "document-like". You should continue to use basic widgets and HTML structure for those parts for which the HTML/CSS layout algorithm works well. In particular, consider using UiBinder templates to directly use HTML wherever that makes sense.

Animation

GWT2.0レイアウトシステムは、直接的でビルトインされたアニメーションのサポートを行なっている。 様々なユースケースをサポートするにはこれが必要である。 なぜなら、レイアウトcnostraintのセットについてのアニメーションはレイアウトシステムが適切に扱う必要があるからだ。

AnimatedLayoutを実装するパネル、例えばLayoutPanel,DockLayoutPanel,SplitLayoutPanelではその子ウィジェットを一つのconstraintセットから別のセットへアニメーションすることができる。典型的なやり方としては、アニメーション結果状態をセットアップしてanimate()を呼び出すというものである。 (後述する)レシピではいくつかの例を示す。

RequiresResizeとProvidesResize

GWT 2.0では二つの特徴的なインターフェース、RequiresResizeProvidesResizeが道入された。 これらは、ウィジェット階層にリサイズイベントを伝播させるために使用される。

RequireResizeはonResize()という一つのメソッドを持つが、これは子のサイズが変更されたときには常に親ウィジェットに呼び出される。 ProvidesResizeは親ウィジェットがこの約束を守ることを示すためのタグインターフェースである。 この二つのインターフェースの目的は、RequiresResizeを実装するすべてのウィジェットとRootLayoutPanelとの間の破壊されていない階層を形成するためのものである(?)。 そして、RootLayoutPanelは(ブラウザウィンドウのリサイズのような)、階層中のウィジェットのサイズに影響するいかなる変化をも検出する。

いつonReize()を使うべきか

多くのウィジェットは、いつリサイズされたのかを知ろうとする必要はない。 たいていの場合は、ブラウザのレイアウトエンジンが適切に処理するからだ。 しかし、ウィジェットがそれを知る必要のある場面もありうる。 例えば、動的なアイテムリストを含むウィジェットが、それを表示するどれだけのスペースが存在するかに依存するような場合である。 コードを走らせるよりも、レイアウトエンジンに任せる方が、おおよそは速いので、他の選択肢が無い限りonResize()に頼るべきではない。

いつ、どうやってProvicesResizeを実装するか

ProvidesResizeを実装するパネルは、その子ウィジェットのうちのすべてのRequiresResizeを実装するものにリサイズイベントを伝播することが期待される。 この標準的な例としては、LayoutPanel.onResize()の実装を見て欲しい。 しかしながら、次項目に述べるように、多くのカスタムウィジェットはResizeCompositeを使って既存のレイアウトパネルを組み合わせることができる。

ResizeComposite

When creating a custom Composite widget that wrap a widget that implements RequiresResize, you should use ResizeComposite as its base class. This subclass of Composite automatically propagates resize events to its wrapped widget.

Moving to Standards Mode

The GWT 2.0 layout system is intended to work only in "standards mode". This means that you should always place the following declaration at the top of your HTML pages: <!DOCTYPE html> What won't work in standards mode?

As mentioned above, some of the existing GWT panels do not behave entirely as expected in standards mode. This stems primarily from differences between the way standards and quirks modes render tables. CellPanel (HorizontalPanel, VerticalPanel, DockPanel)

These panels all use table cells as their basic structural units. While they still work in standards mode, they will lay out their children somewhat differently. The main difference is that their children will not respect width and height properties (it is common to set children of CellPanels explicitly to 100% width and height). There are also differences in the way that the browser allocates space to individual table rows and columns that can lead to unexpected behavior in standards mode.

You should use DockLayoutPanel in place of DockPanel. VerticalPanel can usually be replaced by a simple FlowPanel (since block-level elements will naturally stack up vertically).

HorizontalPanel is a bit trickier. In some cases, you can simply replace it with a DockLayoutPanel, but that requires that you specify its childrens' widths explicitly. The most common alternative is to use FlowPanel, and to use the float: left; CSS property on its children. And of course, you can continue to use HorizontalPanel itself, as long as you take the caveats above into account. StackPanel

StackPanels do not work very well in standards mode. Because of the differences in table rendering mentioned above, StackPanel will almost certainly not do what you expect in standards mode, and you should replace them with StackLayoutPanel. SplitPanel (HorizontalSplitPanel and VerticalSplitPanel)

SplitPanels are very unpredictable in standards mode, and you should almost invariably replace them with SplitLayoutPanel.

GWT 2.0レイアウトシステムのデザイン

2.0以前においてはアプリケーションレベルのレイアウトハンドリングメカニズムは多くの問題があった。

  • 予測不能である。
  • しばしば不足分を補うために追加コードが必要になった
    例えば、ブラウザのクライアントエリアの内側をスクロールさせて埋める(?)には追加コード無しでは不可能であった。

  • 標準モードではすべてが動作するわけではない。

Their underlying motivation was sound — the intention was to let the browser's native layout engine do almost all of the work. But the above deficiencies can be crippling.

目指すもの

  • Perfectly predictable layout behavior. Precision layout should be possible.
    It should also work in the presence of CSS decorations (border, margin, and padding) with arbitrary units.

  • Work correctly in standards-mode.
  • Get the browser to do almost all of the work in its layout engine.
    Manual adjustments should occur only when strictly necessary.

  • Smooth, automatic animation.

目指さないもの

  • Work in quirks-mode.
  • Swing-style layout based on "preferred size". This is effectively intractable in the browser.
  • Take over all layout. This design is intended to handle coarse-grained "desktop-like" layout. The individual bits and pieces, such as form elements, buttons, tables, and text should still be laid out naturally.

レイアウトクラス

The Layout class contains all the underlying logic used by the layout system, along with all the implementation details used to normalize layout behavior on various browsers.

It is actually widget-agnostic, operating directly on DOM elements. Most applications will have no reason to work directly with this class, but it should prove useful to alternate widget library writers.

制約ベースのレイアウト

GWT 2.0のレイアウトシステムはCSSに備わっている単純な制約システム(constraint system)に基づいている。 これはleft, top, width, height, right, bottomというプロパティを仕様する。 多くのデベロッパはこれらのプロパティに親しんでいるだろうが、様々な形で組み合わせて単純な制約システムを形成できることはあまり知られていない。 次のCSSの例を見てみよう。

.parent {
  position: relative; /* to establish positioning context */
}

.child {
  position: absolute; left:1em; top:1em; right:1em; bottom:1em;
}

この例ではchildは自動的にparentのすべてのスペースを使う(consume)ことになるが、ただし縁から1emのスペースをとる。

Any two of these properties (on each axis) forms a valid constraint pair (three would be degenerate), producing lots of interesting possibilities. This is especially true when you consider various mixtures of relative units, such as "em" and "%".

レシピ

以下では一連の簡単なレシピ、様々な構造を作成したり、異なるシナリオを扱うものを示す。 可能な場合はレイアウトをUiBinderテンプレートとして示す。

基本的なアプリケーションレイアウト

以下のサンプルは単純なアプリケーションスタイルレイアウトである。 ヘッダ、左側のナビゲーション領域、スクローラブルなコンテンツ領域がある。

<g:DockLayoutPanel unit='EM'>
  <g:north size='4'>
    <g:Label>Header</g:Label>
  </g:north>

  <g:west size='16'>
    <g:Label>Navigation</g:Label>
  </g:west>

  <g:center>
    <g:ScrollPanel>
      <g:Label>Content Area</g:Label>
    </g:ScrollPanel>
  </g:center>
</g:DockLayoutPanel>

この構造はProvidesResizeを実装するコンテナに格納しなければならないが、多くの場合はRootLayoutPanelである。 以下のコードはこれをデモする。

interface Binder extends UiBinder<Widget, BasicApp> { }
private static final Binder binder = GWT.create(Binder.class);

public void onModuleLoad() {
  RootLayoutPanel.get().add(binder.createAndBindUi());
}

スプリッタ

SplitLayoutPanelDockLayoutPanelに似た働きをするが、ただしピクセル単位のみを受け入れる。 上で述べた基本的なアプリケーション構造では、ナビゲーションとコンテンツ領域の間をスプリッタとすることができる。

<g:DockLayoutPanel unit='EM'>
  <g:north size='4'>
    <g:Label>Header</g:Label>
  </g:north>

  <g:center>
    <g:SplitLayoutPanel>
      <g:west size='128'>
        <g:Label>Navigation</g:Label>
      </g:west>

      <g:center>
        <g:ScrollPanel>
          <g:Label>Content Area</g:Label>
        </g:ScrollPanel>
      </g:center>
    </g:SplitLayoutPanel>
  </g:center>
</g:DockLayoutPanel>

dockとsplitパネルをミックスさせており、ヘッダサイズはEM単位で指定されることに注意。

レイアウトアニメーション

LayoutPanelでアニメーションを行うには、まずconstraintの初期セットを作成する必要がある。 その上でconstraintのターゲットセットにアニメーションする。 以下の例では、トップに位置する子ウィジェットから開始するが、しかし高さは与えられておらず、結果的に隠されている。 LayoutPanel.forceLayout()を呼び出すことにより、初期constraintを「修正する」ことになる。

panel.setWidgetTopHeight(child, 0, PX, 0, PX);
panel.forceLayout();

次に、ウィジェットに対して2emの高さを与え、明示的にLayoutPanel.animate(int)を呼び出し、 500ms以上のresizeを起動する。

panel.setWidgetTopHeight(child, 0, PX, 2, EM);
panel.animate(500);

この方法は、いかなるconstraintに対しても、いかなる子の数に対しても動作する。

RequiresResizeを実装するComposite

RequiresResizeを実装するウィジェットは、そのウィジェットのサイズが変更されたときにRequires.onResize()が呼び出されることを期待している。 もし、このようなウィジェットをCompositeにラッピングしているのであれば、その代わりにResizeCompositeを使い、呼び出しが正しく伝播されるようにする必要がある。

class MyWidget extends ResizeComposite {
  private LayoutPanel p = new LayoutPanel();

  public MyWidget() {
    initWidget(p);
  }
}

子ウィジェットの可視性

Layoutクラスは、それぞれの子要素をcontainer要素にラップすることによって正しく動作させなければならない。 このことは以下を含意している。つまり、LayoutPanel中のウィジェットに対して、UIObject.setVisible(boolean)を呼び出しても、期待する動きにはならない。たしかにウィジェットは不可視になるが、相変らずマウスイベントを受け取る(実際には、そのコンテナ要素がそれを行なっているのだが)。

これを回避するには、LayoutPanel.getWidgetContainerElement(Widget)を使って、直接コンテナ要素を取得し、その可視性を直接設定する必要がある。

LayoutPanel panel = ...;
Widget child;
panel.add(child);
UIObject.setVisible(panel.getWidgetContainerElement(child), false);

RootLayoutPanel無しにLayoutPanelを使う

多くの場合、レイアウトパネルはRootLayoutPanelに、直接あるいはProvidesResizeを実装する他のパネルを介してアタッチするものと思われる。

しかしながら、通常のウィジェット(例えばFlowPanelRootPanel)の中にレイアウトパネルを使いたい場合もありうる。 これらのケースでは、以下の例のようにパネルのサイズを明示的に設定しなくてはいけない。

LayoutPanel panel = new LayoutPanel();
RootPanel.get("someId").add(panel);
panel.setSize("20em", "10em");

ただし、RootLayoutPanelは、RootPanelのように任意の要素をラップするメカニズムは持たないことに注意。

Note that RootLayoutPanel provides no mechanism for wrapping an arbitrary element like RootPanel does. This is because it is impossible to know when an arbitrary element has been resized by the browser. If you want to resize a layout panel in an arbitrary element, you must do so manually.

This also applies to layout panels used in PopupPanel and DialogBox. The following example shows the use of a SplitLayoutPanel in a DialogBox:

SplitLayoutPanel split = new SplitLayoutPanel();
split.addWest(new HTML("west"), 128);
split.add(new HTML("center"));
split.setSize("20em", "10em");

DialogBox dialog = new DialogBox();
dialog.setText("caption");
dialog.add(split);
dialog.show();

テーブルとフレーム

<table>や<frame>要素を使って実装されているウィジェットはレイアウトによって供給される空間を自動的に埋めることはしない。 この動作を修正するには、ウィジェットの幅と高さを100%にする必要がある。 以下の例は、これをRichTextAreaについて行うこのであるが、これは<iframe>要素を使って実装されている。

<g:DockLayoutPanel unit='EM'>
  <g:north size='2'>
    <g:HTML>Header</g:HTML>
  </g:north>

  <g:south size='2'>
    <g:HTML>Footer</g:HTML>
  </g:south>

  <g:center>
    <g:RichTextArea width='100%' height='100%'/>
  </g:center>
</g:DockLayoutPanel>