ツリー コントロールでは、階層情報のアクセスを管理します。ツリー コントロールを使用する一般的なアプリケーションには、このドキュメンテーション・ビューアの [目次] タブに表示されるような目次、Windows® エクスプローラなどのコンピュータのファイルやディレクトリをブラウズするツールなどが含まれます。
次の各セクションでは、ツリー コントロール API の主なコンポーネントについて説明します。
ツリー モデルは、ツリーからアクセスされるデータを格納し、そのデータの構造を定義します。また、
TreeModel には、ツリーのルート ノードである
TreeNode が 1 つ含まれています。
TreeNode は、ツリー内の対応するノードからアクセスできるデータを格納し、他のノードを追加することによってツリーの構造を定義します。
次の例では、単純なツリー コントロールを示しています。次の基本機能に注目してください。
加えて、非ローカル オプション の
tree-connector-color はアイテムをつなげる線の色をコントロールします。これらのオプションを追加したり、オプションの値を変更したり、その効果に注意し画ならコントロールを試してみてください。
例:
単純なツリー コントロール |
 |
{TreeControl
data-model =
{TreeModel
root =
{TreeNode node-data="Food",
{TreeNode node-data="Fruit",
{TreeNode node-data="Apples",
{TreeNode node-data="Macintosh"},
{TreeNode node-data="Cortland"},
{TreeNode node-data="Gala"},
{TreeNode node-data="Delicious"}
},
{TreeNode node-data="Oranges"}
},
{TreeNode node-data="Vegetables",
{TreeNode node-data="Squash"},
{TreeNode node-data="Tomatoes"},
{TreeNode node-data="Cucumbers"}
}
}
}
}
| |
ユーザーによるデータのナビゲートや情報の検索を可能にするには、ツリー コントロール構造にデータを格納します。ユーザーは通常、見つけたデータに対して何らかの操作を実行する必要があります。一般的な例は、Window エクスプローラなどのファイル システム ビューアで見つけたファイルに対する操作です。
TreeControl は
SelectionContext から
GuiEventTarget を継承するため、ツリー コントロールはイベントを受信して処理できます。次の各セクションでは、ツリー コントロールとツリー アイテムが受信する重要なイベントについて説明します。
また、この例では、
make-model.scurl という名前のサポート ファイルがインクルートされています。このファイルには、コントロールのデータ モデルを提供するプロシージャ
build-model が定義されています。データ モデルの作成については、「
ノードの追加」のセクションで説明されています。そのセクションの例では、
build-model を定義するコードが示されています。
この例には、
make-item.scurl というサポート ファイルもインクルードされています。このファイルには、
tree-item-creation-proc でコントロールのアイテムの作成に使用されるツリー アイテム クラス
FlagTreeItem が定義されています。
TreeItem のサブクラス化については、「
DefaultTreeItemのカスタマイズ」のセクションで説明されています。そのセクションの例では、
FlagTreeItem を定義するコードが示されています。
例:
CurrentNodeChanged |
 |
{include "../../default/support/make-model.scurl"}
{include "../../default/support/make-item.scurl"}
{let flag-view:Frame = {Frame}}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
},
{on CurrentNodeChanged at tc:TreeControl do
{if-non-null flag-view.child then
{flag-view.remove-child}
}
{if not tc.current-node.has-children? then
{flag-view.add
{image
source = {url tc.current-node.node-data},
width = 51px,
height = 41px
}
}
}
}
}
}
{HBox
spacing = 2pt,
valign = "top",
tree,
flag-view
}
| |
例:
Action および SelectionChangedへの応答 |
 |
{include "../../default/support/make-model.scurl"}
{include "../../default/support/make-item.scurl"}
{let flag-view:Frame = {Frame}}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
},
{on Action at tc:TreeControl do
{if not tc.current-node.has-children? then
{flag-view.add
{image
source = {url tc.current-node.node-data},
width = 51px,
height = 41px
}
}
}
},
{on SelectionChanged do
{flag-view.remove-child}
}
}
}
{HBox
valign = "top",
tree,
flag-view
}
| |
例:
TreeItem に対する PointerRelease |
 |
{include "../../default/support/make-model-node.scurl"}
{define-class package FlagTreeItem {inherits DefaultTreeItem}
{constructor package {default node:TreeNode}
let icon:DefaultTreeIcon =
{if not node.has-children? then
{DefaultPixmapTreeIcon
border-width = 1px,
width = 16px,
height = 16px,
{Pixmap.from-url {url node.node-data}}
}
else
{DefaultFrameTreeIcon
border-width = 1px,
width = 16px,
height = 16px,
background = FillPattern.silver
}
}
{construct-super node, icon = icon}
}
{method public open {on-pointer-release
pr:PointerRelease
}:void
{super.on-pointer-release pr}
{if self.node.has-children? and
self.current? and
not self.expanded?
then
{self.toggle-node}
}
}
}
{let flag-view:Frame = {Frame}}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
},
{on CurrentNodeChanged at tc:TreeControl do
{if-non-null flag-view.child then
{flag-view.remove-child}
}
{if-non-null tc.current-node then
{if not tc.current-node.has-children? then
{flag-view.add
{image
source = {url tc.current-node.node-data},
width = 51px,
height = 41px
}
}
}
}
}
}
}
{HBox
spacing = 2pt,
valign = "top",
tree,
flag-view
}
| |
TreeControl オブジェクトは、コントロール内のノードを操作する多数のメソッドを提供します。次のいくつかのセクションでこれらのメソッドの一部を説明します。
次の例では、
TreeControl.expand-node と
TreeControl.collapse-node を使用して、コントロール内のノードを展開したり折りたたんだりします。最初の 2 つのボタンはルート ノードに対して機能し、次の 2 つのボタンは現在のノードに対して機能します。プロパティ
expand-descendants? と
collapse-descendants? は、指定したノードの子孫も展開したり折りたたんだりするかどうかを制御します。値を
false に変更してコード例を試してください。子孫ノードがそのまま変更されないことに注意してください。
collapse-ancestors?
= true または
expand-ancestors? = true を設定するコードも追加して、祖先ノードに対する効果を確認してください。
例:
expand-node および collapse-node の使用 |
 |
{let foods:TreeModel =
{TreeModel
root =
{TreeNode node-data="Food",
{TreeNode node-data="Fruit",
{TreeNode node-data="Apples",
{TreeNode node-data="Macintosh"},
{TreeNode node-data="Cortland"},
{TreeNode node-data="Gala"},
{TreeNode node-data="Delicious"}
},
{TreeNode node-data="Oranges"}
},
{TreeNode node-data="Vegetables",
{TreeNode node-data="Squash"},
{TreeNode node-data="Tomatoes"},
{TreeNode node-data="Cucumbers"}
}
}
}
}
{let tree:TreeControl =
{TreeControl data-model = foods}
}
{VBox
{HBox
{CommandButton
label = "Collapse All",
{on Action do
{if-non-null root = tree.data-model.root then
{tree.collapse-node
root,
collapse-descendants? = true
}
}
}
},
{CommandButton
label = "Expand All",
{on Action do
{if-non-null root = tree.data-model.root then
{tree.expand-node
root,
expand-descendants? = true
}
}
}
},
{CommandButton
label = "Collapse Current",
{on Action do
{if-non-null current = tree.current-node then
{tree.collapse-node
current,
collapse-descendants? = true
}
}
}
},
{CommandButton
label = "Expand Current",
{on Action do
{if-non-null current = tree.current-node then
{tree.expand-node
current,
expand-descendants? = true
}
}
}
}
},
tree
}
| |
例:
select-nodes および deselect-nodes |
 |
{let foods:TreeModel =
{TreeModel
root =
{TreeNode node-data="Food",
{TreeNode node-data="Fruit",
{TreeNode node-data="Apples",
{TreeNode node-data="Macintosh"},
{TreeNode node-data="Cortland"},
{TreeNode node-data="Gala"},
{TreeNode node-data="Delicious"}
},
{TreeNode node-data="Oranges"}
},
{TreeNode node-data="Vegetables",
{TreeNode node-data="Squash"},
{TreeNode node-data="Tomatoes"},
{TreeNode node-data="Cucumbers"}
}
}
}
}
{let tree:TreeControl =
{TreeControl
data-model = foods
}
}
{VBox
{CommandButton
label = "Select My Parent",
takes-focus? = false,
{on Action do
{if-non-null current = tree.current-node then
{if-non-null parent = current.parent then
{tree.select-nodes parent}
{if {popup-message cancel? = true, "Deselect?"} == Dialog.ok then
{tree.deselect-nodes parent}
}
{return}
}
}
{popup-message "Pick a node with a parent."}
}
},
tree
}
| |
例:
next-node および previous-node |
 |
{let foods:TreeModel =
{TreeModel
root =
{TreeNode node-data="Food",
{TreeNode node-data="Fruit",
{TreeNode node-data="Apples",
{TreeNode node-data="Macintosh"},
{TreeNode node-data="Cortland"},
{TreeNode node-data="Gala"},
{TreeNode node-data="Delicious"}
},
{TreeNode node-data="Oranges"}
},
{TreeNode node-data="Vegetables",
{TreeNode node-data="Squash"},
{TreeNode node-data="Tomatoes"},
{TreeNode node-data="Cucumbers"}
}
}
}
}
{let tree:TreeControl =
{TreeControl
selection-policy = SelectionPolicy.disabled,
data-model = foods
}
}
{VBox
{HBox
{CommandButton
label = "Push Current Forward",
takes-focus? = false,
{on Action do
let cursor:#TreeNode = tree.current-node
{if cursor == null then
set cursor = tree.data-model.root
}
{if-non-null cursor then
set tree.current-node =
{tree.next-node cursor, skip-hidden-nodes? = false}
}
{if tree.current-node == null then
set tree.current-node = tree.data-model.root
}
{tree.become-active}
}
},
{CommandButton
label = "Push Current Backward",
takes-focus? = false,
{on Action do
let cursor:#TreeNode = tree.current-node
{if cursor == null then
set cursor = tree.data-model.root
}
{if-non-null cursor then
set tree.current-node =
{tree.previous-node cursor, skip-hidden-nodes? = false}
}
{if tree.current-node == null then
set tree.current-node = tree.data-model.root
}
{tree.become-active}
}
}
},
tree
}
| |
ImageTreeItem というカスタム
TreeItem を定義するコードも含まれています。プログラムを簡単にするために、ツリー アイテムを定義するコードは、前の例のサポート ファイルからインクルードされています。ここで示されているのは、ツリー コントロール ラベルの実証するためです。このカスタム ツリー アイテムでは、ノード ラベルとして旗のイメージを使用しています。ラベルが関連付けられているノードがツリー内の現在のノードである場合にそのラベルを変更するために、
TreeItem.current? プロパティも使用しています。現在のノードを除くすべてのノードのラベル イメージは、イメージ フィルタ
blend で変更されます。
例:
walk-nodes |
 |
{include "../../default/support/make-model.scurl"}
{define-class package ImageTreeItem {inherits DefaultTreeItem}
{constructor package {default node:TreeNode}
{construct-super node}
}
{method public open {get-label}:any
{if self.node.has-children? then
{return {super.get-label}}
}
let fp:FillPattern = {FillPattern.from-url {url self.node.node-data}}
{if self.current? then
{return
{Frame
background = fp,
width = 25px,
height = 20px
}
}
else
let pxmp:Pixmap = {fp.to-Pixmap}
let mask:Pixmap = {Pixmap
pxmp.width,
pxmp.height,
initial-value = {Pixel.create 1, 1, 1}
}
{return
{Frame
background = {IMAGEFILTER.blend pxmp, mask, 30%},
border-width = 1px,
width = 25px,
height = 20px
}
}
}
}
}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {ImageTreeItem node}}
}
}
}
{VBox
{DropdownList
width = 2cm,
"",
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m",
"n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
value = "",
{on ValueFinished at dl:DropdownList do
let flag-letter:String = dl.value
set tree.current-node =
{tree.walk-nodes
{proc {node:TreeNode}:bool
{return
not node.has-children? and
{node.node-data.find-string flag-letter & "-"} > 0
}
},
enter-collapsed-nodes? = true
}
}
},
tree
}
| |
ツリー コントロール API には、ツリー モデルにノードを追加するためのメソッドがいくつか用意されています。
次の例では、
TreeNode.append を使用して、レコード セットから選択されたデータをツリー ノードに追加することによってモデルを構築しています。このプロセスは、データのソースがディスク上のファイルまたはデータベースであるような実際のアプリケーションをシミュレーションします。
例:
ツリー ノードの追加 |
 |
{let maritime-signal-flags:RecordSet =
{evaluate {url "../../default/support/flag-data.scurl"}}
}
{define-proc {build-model}:TreeModel
let colors:TreeNode =
{TreeNode
node-data = maritime-signal-flags.fields["colors"].caption
}
{for x:int=1 to 4 do
let recs:{Array-of Record} =
{maritime-signal-flags.select
filter = {RecordData colors = x}
}
let n:TreeNode = {TreeNode node-data = x}
{for r:Record in recs do
{n.append
{TreeNode
node-data =
{image
source = {url r["flag"]},
width = 25px,
height = 20px,
border-width = 1px
}
}
}
}
{colors.append n}
}
{return
{TreeModel
root =
{TreeNode
node-data = "Maritime Signal Flags",
colors
}
}
}
}
{let tree:TreeControl =
{TreeControl data-model = {build-model}}
}
{value tree}
| |
例:
ツリー ノードの挿入 |
 |
{include "../../default/support/make-item.scurl"}
{let maritime-signal-flags:RecordSet =
{evaluate {url "../../default/support/flag-data.scurl"}}
}
{define-proc {build-model}:TreeModel
let colors:TreeNode =
{TreeNode
node-data = maritime-signal-flags.fields["colors"].caption
}
{for x:int=1 to 4 do
let recs:{Array-of Record} =
{maritime-signal-flags.select
filter = {RecordData colors = x}
}
let n:TreeNode = {TreeNode node-data = x & "-color"}
{for r:Record in recs do
{n.insert {TreeNode node-data = r["flag"]}, 0}
}
{colors.insert n, 0}
}
{return
{TreeModel
root =
{TreeNode
node-data = "Maritime Signal Flags",
colors
}
}
}
}
{let tree:TreeControl =
{TreeControl data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
}
}
}
{value tree}
| |
次の例では、
TreeNode.splice を使用して、既存ノードにノード グループを追加しています。レコード セット内のレコード グループからノードを作成するのではなく、ツリー ノードの配列を作成して、その配列を 1 つの親ノードに連結しています。
例:
ツリー ノードの連結 |
 |
{include "../../default/support/make-item.scurl"}
{let maritime-signal-flags:RecordSet =
{evaluate {url "../../default/support/flag-data.scurl"}}
}
{define-proc {build-model}:TreeModel
let colors:TreeNode =
{TreeNode
node-data = maritime-signal-flags.fields["colors"].caption
}
{for x:int = 1 to 4 do
let recs:{Array-of Record} =
{maritime-signal-flags.select
filter = {RecordData colors = x}
}
let n:{Array-of TreeNode} = {new {Array-of TreeNode}}
{for r:Record in recs do
{n.append
{TreeNode
node-data =
{image
source = {url r["flag"]},
width = 25px,
height = 20px,
border-width = 1px
}
}
}
}
{colors.splice n, 0}
}
{return
{TreeModel
root =
{TreeNode
node-data = "Maritime Signal Flags",
colors
}
}
}
}
{let tree:TreeControl =
{TreeControl data-model = {build-model}}
}
{value tree}
| |
例:
clear および remove |
 |
{include "../../default/support/make-model-pix.scurl"}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
selection-policy = SelectionPolicy.disabled,
{on CurrentNodeChanged do
let enabled?:bool =
{if-non-null tree.current-node then
tree.current-node.has-children?
else
false
}
set cb-1.enabled? = enabled?
set cb-all.enabled? = enabled?
}
}
}
{let cb-1:CommandButton =
{CommandButton
label = "Remove First Child",
takes-focus? = false,
enabled? = false,
{on Action do
{if tree.current-node != null and
tree.current-node.size > 0
then
{tree.current-node.remove 0}
}
}
}
}
{let cb-all:CommandButton =
{CommandButton
label = "Remove All Children",
enabled? = false,
takes-focus? = false,
{on Action do
{if tree.current-node != null then
{tree.current-node.clear}
}
}
}
}
{VBox
{HBox
cb-1,
cb-all
},
tree
}
| |
例:
reverse および sort |
 |
{define-class package FoodTreeItem {inherits DefaultTreeItem}
{constructor package {default node:TreeNode}
{construct-super node}
}
{method public open {on-context-menu-event ev:ContextMenuEvent}:void
{if not ev.consumed? and self.enabled? then
let mp:MenuPane =
{MenuPane
{MenuAction
label = "Reverse My Children",
enabled? = self.node.has-children?,
{on Action do
{self.node.reverse}
}
},
{MenuAction
label = "Sort My Children",
enabled? = self.node.has-children?,
{on Action do
{self.node.sort
comparison-proc =
{proc {x:TreeNode, y:TreeNode}:bool
{return {String-leq? x.node-data, y.node-data}}
}
}
}
}
}
{ev.consume}
{mp.popup self, ev.x, ev.y}
{return}
}
{super.on-context-menu-event ev}
}
}
{let foods:TreeModel =
{TreeModel
root =
{TreeNode node-data="Food",
{TreeNode node-data="Fruit",
{TreeNode node-data="Apples",
{TreeNode node-data="Macintosh"},
{TreeNode node-data="Cortland"},
{TreeNode node-data="Gala"},
{TreeNode node-data="Delicious"}
},
{TreeNode node-data="Oranges"}
},
{TreeNode node-data="Vegetables",
{TreeNode node-data="Squash"},
{TreeNode node-data="Tomatoes"},
{TreeNode node-data="Cucumbers"}
}
}
}
}
{TreeControl
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FoodTreeItem node}}
},
data-model = foods
}
| |
ツリー コントロールの既定の動作では、ノード データは単純なテキスト ラベルで提供されます。また、現在のノードはラベルを囲む境界線で示され、選択されたノードは背景と色を変更することによって示されます。
ツリー コントロールのビジュアルな外観を変更するには、カスタマイズした
TreeItem を作成する必要があります。通常は、
DefaultTreeItem のサブクラスを定義するのが最善の方法です。このクラスには、ツリー コントロールの外観をカスタマイズするためにオーバーライドできるいくつかのメソッドが定義されているからです。重要ないくつかのメソッドを次に説明します。
例:
カスタム ラベル |
 |
{include "../../default/support/make-model.scurl"}
{define-class package FlagTreeItem {inherits DefaultTreeItem}
{constructor package {default node:TreeNode}
{construct-super node}
}
{method public open {get-label}:any
{if self.node.has-children? then
{return {super.get-label}}
else
let u:Url = {url self.node.node-data}
let name:String = u.pathname-tail
set name = {name.substr 0, 1}
{return "letter-" & {name.to-upper-clone}}
}
}
}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
}
}
}
{value tree}
| |
このバージョンの FlagTreeItem は、16 x 16 ピクセルのサイズの旗のイメージをアイコンとして使用します。これらの小さいバージョンの旗は、ツリー コントロール アイコンとしてラベルの左側に表示されます。
例:
アイコンの追加 |
 |
{include "../../default/support/make-model.scurl"}
{define-class package FlagTreeItem {inherits DefaultTreeItem}
{constructor package {default node:TreeNode}
let icon:DefaultTreeIcon =
{if not node.has-children? then
{DefaultPixmapTreeIcon
border-width = 1px,
width = 16px,
height = 16px,
{Pixmap.from-url {url node.node-data}}
}
else
{DefaultFrameTreeIcon
border-width = 1px,
width = 16px,
height = 16px,
background = FillPattern.silver
}
}
{construct-super node, icon = icon}
}
{method public open {get-label}:any
{if self.node.has-children? then
{return {super.get-label}}
else
let u:Url = {url self.node.node-data}
let name:String = u.pathname-tail
set name = {name.substr 0, 1}
{return "letter-" & {name.to-upper-clone}}
}
}
}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
}
}
}
{value tree}
| |
前の例では、ノード データからアイコンを生成できます。ノードの属性に基づいてノードにアイコンを関連付けることが必要になる場合もよくあります。一般的な例は、ファイル システム ビューアで別のファイル タイプにアイコンを関連付ける場合です。
次の例で、この状況をシミュレーションします。
例:
ファイル ビューアのシミュレーション |
 |
{define-class package MyItem {inherits DefaultTreeItem}
let private folder-map:Pixmap =
{Pixmap.from-url {url "../../default/images/folder.gif"} }
let private a-map:Pixmap = {Pixmap.from-url {url "../../default/images/A.gif"} }
let private b-map:Pixmap = {Pixmap.from-url {url "../../default/images/B.gif"} }
let private c-map:Pixmap = {Pixmap.from-url {url "../../default/images/C.gif"} }
let private d-map:Pixmap = {Pixmap.from-url {url "../../default/images/D.gif"} }
let private q-map:Pixmap = {Pixmap.from-url {url "../../default/images/Q.gif"} }
{constructor package {default node:TreeNode}
let str:String = node.node-data
let map:Pixmap =
{switch {str.tail str.size - 2}
case ".a" do MyItem.a-map
case ".b" do MyItem.b-map
case ".c" do MyItem.c-map
case ".d" do MyItem.d-map
else MyItem.q-map
}
{construct-super
node,
icon =
{DefaultPixmapTreeIcon
map,
parent-pixmap = MyItem.folder-map
}
}
}
}
{TreeControl
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {MyItem node}}
},
data-model =
{TreeModel
root =
{TreeNode node-data="Folder",
{TreeNode node-data="file1.c"},
{TreeNode node-data="file3.a"},
{TreeNode node-data="file4.g"},
{TreeNode node-data="file3.c"},
{TreeNode node-data="file0.c"},
{TreeNode node-data="file5.g"},
{TreeNode node-data="file1.b"},
{TreeNode node-data="file9.d"},
{TreeNode node-data="file2.e"},
{TreeNode node-data="file7.b"},
{TreeNode node-data="file8.a"}
}
}
}
| |
左側のコントロールでは選択が無効になっているため、modify-for-current の結果しか確認できません。右側のコントロールでは複数の選択が有効になっているため、選択されたノードと現在のノードの違いが示されています。
例:
現在のノードと選択されたノードの外観の変更 |
 |
{include "../../default/support/make-model-node.scurl"}
{define-class package FlagTreeItem {inherits DefaultTreeItem}
{constructor package {default node:TreeNode}
{construct-super node}
}
{method protected {modify-for-current current?:bool}:void
{if current? then
set self.color = {FillPattern.get-red}
else
{unset self.color}
}
{return}
}
{method protected {modify-for-selected selected?:bool}:void
{if selected? then
set self.background = {FillPattern.get-pink}
else
{unset self.background}
}
{return}
}
}
{HBox
{VBox
"No Selection",
{TreeControl
data-model = {build-model},
selection-policy = SelectionPolicy.disabled,
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
}
}
},
{VBox
"Multiple Selection",
{TreeControl
data-model = {build-model},
selection-policy = SelectionPolicy.multiple,
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return {FlagTreeItem node}}
}
}
}
}
| |
ツリー コントロール API は、親ノードが開かれたときのみ子ノードを生成する
DefaultTreeNode の抽象サブクラスである
LazyTreeNode を提供します。このクラスを使用することにより、ツリーが非常に大きかったり、深かったりする場合にコードをより効率的に短い時間でロードすることができます。しかし非常に大きかったり、構造が深いツリー コントロールでは、ユーザーの操作が効率的に行えませんので、通常は上記のような状況を起こさないようにツリーを作成します。
次の例では、
MyLazyTreeNode と呼ばれる
LazyTreeNode のサブクラスを定義します。このクラスはコンストラクタに渡された文字列の配列から子ノードを作成します。例ではその後レイジー ノードの配列を作成し、ツリー コントロールのルート ノードに追加します。
例:
レイジー ツリー ノードの使用 |
 |
|| Define a LazyTreeNode class that can display
|| a String array as its child nodes.
{define-class public MyLazyTreeNode {inherits LazyTreeNode}
field package inner-things:{Array-of String} =
{new {Array-of String}}
{constructor public {default
child-array:{Array-of String},
node-data:any = null
}
set self.inner-things = child-array
{construct-super node-data = node-data}
}
|| Create one child node per item in the array.
{method protected {compute-children
children:{Array-of TreeNode},
initializing?:bool
}:void
{children.clear}
{for one-element in self.inner-things do
{children.append
{DefaultTreeNode node-data = one-element}
}
}
}
|| More efficient than the standard method.
{getter public open {has-children?}:bool
{return self.inner-things.size > 0}
}
}
{value
|| Create an array of LazyTreeNodes with
|| quite a few children in each.
let constant lazy-nodes:{Array-of MyLazyTreeNode} =
{new {Array-of MyLazyTreeNode}}
{for i = 0 below 20 do
let constant string-array:{Array-of String} =
{new {Array-of String}}
{for x = 0 below 24 do
{string-array.append {String x}}
}
let constant mltn:MyLazyTreeNode =
{MyLazyTreeNode
string-array,
node-data = "This is lazy node " & {String i}
}
{lazy-nodes.append mltn}
}
|| Create a TreeControl with a root node.
let constant tc:TreeControl = {TreeControl}
let constant root-node:DefaultTreeNode =
{DefaultTreeNode node-data = "Root"}
set tc.data-model.root = root-node
|| Append all the lazy children.
{for one-node in lazy-nodes do
{root-node.append one-node}
}
tc
}
| |
TreeNode をサブクラス化する重要な目的の一つは、ノードに追加情報を格納して、ノードが
TreeItem サブクラス内のその情報またはツリー コントロールで動作しやすくなるようにアクセッサを提供することです。
次の例でこのテクニックを示します。最初に
DefaultTreeNode のサブクラスの
FlagTreeNode
クラスを作成します。この章で使用されている
TreeNode と同様に、
フラグを含む画像を含む
url を
node-data として保存します。
このサブクラスはこの url への型付きアクセスおよびフラグの音声名へのアクセスも提供します。
このクラスは既定のラベルを音声名に宣言します。
同じような状況として、アイコンに使用するイメージ ファイルがノード
データから取得できないためにそれを個別に格納する必要がある場合、
およびノード データ以外のパラメータ (たとえば修正時間やファイル ビューアのファイル サイズ)
によってノードをソートする必要がある場合などがあります。このテクニックはこの状況で役立ちます。
例:
FlagTreeNode |
 |
{let maritime-signal-flags:RecordSet =
{evaluate {url "../../default/support/flag-data.scurl"}}
}
{define-class public FlagTreeNode {inherits DefaultTreeNode}
field constant public phonetic-name:String
{constructor public {default
node-data:any = null,
phonetic-name:String = ""
}
set self.phonetic-name = phonetic-name
{construct-super node-data = node-data}
}
{getter public {url}:Url
{return {url self.node-data}}
}
{getter public {node-label}:any
{return self.phonetic-name}
}
}
{define-proc {build-model}:TreeModel
let colors:TreeNode =
{TreeNode
node-data =
maritime-signal-flags.fields["colors"].caption
}
{for x:int = 1 to 4 do
let recs:{Array-of Record} =
{maritime-signal-flags.select filter =
{RecordData colors = x}
}
let n:TreeNode = {TreeNode node-data = x & "-color"}
{for r:Record in recs do
{n.append
{FlagTreeNode
node-data = r["flag"],
phonetic-name = r["phonetic"]
}
}
}
{colors.append n}
}
{return
{TreeModel
root =
{TreeNode
node-data = "Maritime Signal Flags",
colors
}
}
}
}
{define-class package FlagTreeItem {inherits DefaultTreeItem}
{constructor package {default node:FlagTreeNode}
{construct-super
node,
icon =
{DefaultPixmapTreeIcon
{Pixmap.from-url node.url},
width = 16px,
height = 16px,
border-width = 1px
}
}
}
{getter public open {node}:FlagTreeNode
{return super.node asa FlagTreeNode}
}
}
{let flag-view:Frame = {Frame}}
{let tree:TreeControl =
{TreeControl
data-model = {build-model},
tree-item-creation-proc =
{proc {node:TreeNode}:TreeItem
{return
{type-switch node
case ftn:FlagTreeNode do
{FlagTreeItem ftn}
else
{DefaultTreeItem
node,
icon =
{DefaultFrameTreeIcon
background = FillPattern.silver,
width = 16px,
height = 16px,
border-width = 1px
}
}
}
}
},
{on CurrentNodeChanged at tc:TreeControl do
{if-non-null flag-view.child then
{flag-view.remove-child}
}
{type-switch tc.current-node
case ftn:FlagTreeNode do
{flag-view.add
{image
source = ftn.url,
width = 51px,
height = 41px
}
}
}
}
}
}
{HBox
spacing = 2pt,
valign = "top",
tree,
flag-view
}
| |
Copyright © 1998-2019 SCSK Corporation.
All rights reserved.
Curl, the Curl logo, Surge, and the Surge logo are trademarks of SCSK Corporation.
that are registered in the United States. Surge
Lab, the Surge Lab logo, and the Surge Lab Visual Layout Editor (VLE)
logo are trademarks of SCSK Corporation.