ひよっこ。

I want to…

Posts Tagged ‘gwt’

GoogleWebToolkitでTreeのkeyboard操作がうまくいかない その1

Posted by hikaruworld : 2011 11月 22

GWT1.7(orz..)で作ったアプリの一部でバグが出たらしく調べてたら、バグが仕様かわからなかったのでメモしておきます。
個人的な意見としては、TreeItemの展開状態と子となるTreeItemを持っていることがイコールではないと思っているので、
バグだと思っていますが。

再現手順

どんな現象が発生したかと言うと、

  1. 階層を持つツリーを構成
  2. 1つのみの子要素を持つTreeItemを展開する
  3. 2.の子要素を削除する
  4. 2.及び3.以外のTreeItemをマウスで選択し、キーボードで2.を選択するように上下に移動する
  5. 2.が選択できずにエラーとなる。

という感じ。
サンプルコードは以下。ちなみにGWT2.4でサンプル作ってますが同様に発生する模様。

package com.wordpress.prepro.gwt.sample.tree.client;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Tree;
import com.google.gwt.user.client.ui.TreeItem;

/**
 * 検証用のツリーモジュール。
 * HTML上には以下のid属性を定義しておくこと。
 * <pre>
 * <div id="buttonContainer"></div>
 * <div id="treeContainer"></div>
 * </pre>
 */
public class Tree_sample implements EntryPoint {
	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		final Tree tree = new Tree();
		tree.addSelectionHandler(new SelectionHandler<TreeItem>() {
			@Override
			public void onSelection(SelectionEvent<TreeItem> event) {
				System.out.println(event.getSelectedItem().getState());
			}
		});
		tree.addTextItem("Tree1");
		tree.addTextItem("Tree2");

		final TreeItem treeItem = new TreeItem("Tree3");
		tree.addItem(treeItem);
		final TreeItem childItem1 = new TreeItem("ChildTree1");
		treeItem.addItem(childItem1);
		
		Button removeButton = new Button("削除");
		// 選択した要素を削除する。
		removeButton.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				TreeItem parent = tree.getSelectedItem().getParentItem();
				tree.getSelectedItem().remove();
				parent.setState(parent.getChildCount() > 0);
				// イベントが発行されるのがいやであればこちらで。
				//parent.setState(parent.getChildCount() > 0, false);
			}
		});
		
		tree.addTextItem("Tree2");
		RootPanel.get("treeContainer").add(tree);
		RootPanel.get("buttonContainer").add(removeButton);
	}
}

スタックトレースはこんな感じでで出てました。

14:25:22.818 [ERROR] [tree_sample] Uncaught exception escaped

java.lang.NullPointerException: null
    at com.google.gwt.user.client.ui.Tree.findDeepestOpenChild(Tree.java:1012)
    at com.google.gwt.user.client.ui.Tree.findDeepestOpenChild(Tree.java:1015)
    at com.google.gwt.user.client.ui.Tree.moveSelectionUp(Tree.java:1252)
    at com.google.gwt.user.client.ui.Tree.keyboardNavigation(Tree.java:1103)
    at com.google.gwt.user.client.ui.Tree.onBrowserEvent(Tree.java:647)
    at com.google.gwt.user.client.DOM.dispatchEventImpl(DOM.java:1351)
    at com.google.gwt.user.client.DOM.dispatchEvent(DOM.java:1307)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:103)
    at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71)
    at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:172)
    at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessagesWhileWaitingForReturn(BrowserChannelServer.java:337)
    at com.google.gwt.dev.shell.BrowserChannelServer.invokeJavascript(BrowserChannelServer.java:218)
    at com.google.gwt.dev.shell.ModuleSpaceOOPHM.doInvoke(ModuleSpaceOOPHM.java:136)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNative(ModuleSpace.java:561)
    at com.google.gwt.dev.shell.ModuleSpace.invokeNativeObject(ModuleSpace.java:269)
    at com.google.gwt.dev.shell.JavaScriptHost.invokeNativeObject(JavaScriptHost.java:91)
    at com.google.gwt.core.client.impl.Impl.apply(Impl.java)
    at com.google.gwt.core.client.impl.Impl.entry0(Impl.java:213)
    at sun.reflect.GeneratedMethodAccessor30.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
    at java.lang.reflect.Method.invoke(Unknown Source)
    at com.google.gwt.dev.shell.MethodAdaptor.invoke(MethodAdaptor.java:103)
    at com.google.gwt.dev.shell.MethodDispatch.invoke(MethodDispatch.java:71)
    at com.google.gwt.dev.shell.OophmSessionHandler.invoke(OophmSessionHandler.java:172)
    at com.google.gwt.dev.shell.BrowserChannelServer.reactToMessages(BrowserChannelServer.java:292)
    at com.google.gwt.dev.shell.BrowserChannelServer.processConnection(BrowserChannelServer.java:546)
    at com.google.gwt.dev.shell.BrowserChannelServer.run(BrowserChannelServer.java:363)
    at java.lang.Thread.run(Unknown Source)

原因

TreeItemは内部に自身が展開しているかどうかの状態を持っているのですが、
TreeItem#removeを利用して削除した場合、TreeItemの状態が維持されてしまうようです。
# 自分の使うメソッドが違うのかもしれません。。。

そのため、その状態のままキーボードを用いてツリー間の移動を行うと、
TreeItemに子要素が存在するとみなしてこのTreeItemの子要素を探索しに行ってしまい、
存在しないためnullが返されTreeItemの状態が取れずNullPointerExceptionが発生してしまいます。

以下TreeItemの該当箇所のソースです。

private TreeItem findDeepestOpenChild(TreeItem item) {
    if (!item.getState()) {
        return item;
    }
    return findDeepestOpenChild(item.getChild(item.getChildCount() - 1));
}

回避策

というわけで回避策です。

一番手っ取り早いのは上記ソースコードにパッチを当てることでしょう。
こんな感じに。

892c892
<     if (!item.getState()) {
---
>     if (!item.getState() || item.getChildCount() == 0) {

あるいはこんな感じにTreeItem#removeした場合に、展開状態を更新するようにしておきます。

Tree tree = new Tree();
// ....<中略>....

// 削除対象の親となるTreeItemを取得しておく
TreeItem parent = tree.getSelectedItem().getParentItem();
// TreeItemを削除
tree.getSelectedItem().remove();
// 親の展開状態を更新
// 既に展開されておりかつ子要素が存在する場合だけ展開状態を維持する
parent.setState(parent.getState() && parent.getChildCount() > 0);
// 展開状態のイベントをdispatchさせたくないようであればこちらを利用する。
// parent.setState(parent.getState() && parent.getChildCount() > 0, false);

ただこの回避策をとった場合、子要素が0件になった場合に必ず展開状態が閉じてしまうことになります。
子要素が0件でも展開状態を維持しておきたい場合は、これではダメです。

とりあえず自分は後者で回避しました。

以上です。

Posted in program | タグ: , | Leave a Comment »

dwt-dndを使う(導入編)

Posted by hikaruworld : 2010 11月 15

gwt-dndというgwtでドラッグ&ドロップを簡単に利用可能なライブラリがあります。
ちょっとこのライブラリを使っていくつかはまったので、簡単にメモを残しておきます。

インストール

といいつつ、まずはインストールからです。

ここから、バージョンにあったgwt-dnd.jarをダウンロードします。
gwt1.Xからgwt2.X系まで一通りそろっているので自分の様にgwt1.4系とか使ってる人でも大丈夫です。

# これを機にGwt1.Xの最新までバージョンアップしましたが…

ダウンロードしたjarファイルは、いつも通りgwtからパスを通します。

初期設定

以下は、初期設定です(Eclipseベース)。
gwt-dnd.jarはgwtのモジュール形式として配布されています。
jar内部にgwt-dnd.gwt.xmlことからもこれが伺い知れますね。

ドキュメントに関してはGettingStartedを読むのが一番分かりやすいですが、
自分は英語が苦手なので、備忘録として残しておきます。

設定ファイルに定義を追加

まず利用する場合は、利用しているgwtの定義ファイルhogehoge.gwt.xmlに以下の設定を追記します

<!-- Inherit gwt-dnd support                   -->
<inherits name='com.allen_sauer.gwt.dnd.gwt-dnd'/>

ここを参照

CSSの設定

必要に応じて、CSSの設定をします。
これはRootPanel.get()しかしなかったような場合など、高さが存在しない状態になります。
その場合に、あらかじめ100%確保しておいて、ドラッグするスペースを確保しておきます。

 HTML, BODY {
    height: 100%;
  }

なお、もし上記の設定をしていない状態でドラッグが行われ、移動できない場合は、以下のようなメッセージで警告されます。

gwt-dnd warning: boundary panel (= the BODY element) has zero height; dragging cannot occur inside an AbsolutePanel that has a height of zero pixels; you can often remedy this quite easily by adding the following line of CSS to your application’s stylesheet: BODY, HTML { height: 100%; }

Sample

とりあえず、サンプルに従って動かしてみます。
A Simple Example(Your first drag-n-drop project)そのままで簡単なドラッグ&ドロップのサンプルが確認できます。

package com.example.client;

import com.allen_sauer.gwt.dnd.client.PickupDragController;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.RootPanel;

public class Gwt_dnd_sample01 implements EntryPoint {
	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		// Drag管理用のコントローラを宣言
		// 第一引数がDrag領域のパネル?
                // 第二引数がtrueの場合に対象にdrop可能/falseの場合にdrop不可能
		PickupDragController dragController = new PickupDragController(RootPanel.get(), true);

		// ドラッグ可能なWidgetの登録
		Label dragLabel = new Label("ドラッグ対象");
		dragController.makeDraggable(dragLabel);

		// RootPanelに展開		
		RootPanel.get().add(dragLabel);
	}
}

PickupDragControllerはライブラリから提供されているDragControllerの中の一つです。

以上です。

Posted in program | タグ: , | Leave a Comment »