ツリー コントロール

ツリー コントロールでは、階層情報のアクセスを管理します。ツリー コントロールを使用する一般的なアプリケーションには、このドキュメンテーション・ビューアの [目次] タブに表示されるような目次、Windows® エクスプローラなどのコンピュータのファイルやディレクトリをブラウズするツールなどが含まれます。

概要

ツリー コントロールを実装する Curl® API の基本クラスは、TreeControl です。TreeControl には、コントロールがアクセスするデータの構造を定義する TreeModel が含まれています。そして TreeModel には、ツリーのルート ノードである TreeNode が含まれています。このルート TreeNode には子ノードが含まれ、さらに子ノードには別の子ノードが含まれます。コントロールにおけるノードの外観は、TreeItem クラスで定義されます。
次の各セクションでは、ツリー コントロール API の主なコンポーネントについて説明します。

ツリー モデル

ツリー モデルは、ツリーからアクセスされるデータを格納し、そのデータの構造を定義します。また、TreeModel には、ツリーのルート ノードである TreeNode が 1 つ含まれています。TreeNode は、ツリー内の対応するノードからアクセスできるデータを格納し、他のノードを追加することによってツリーの構造を定義します。

ツリー アイテム

ツリー アイテムは、ツリー コントロールにおけるツリー ノードの表示を制御します。TreeControl がトラバースするツリーの構造と内容が TreeNode で定義される場合は、TreeItem によってツリー内のノードの外観が決定されます。

ツリー コントロール

TreeControl は、アプレットに表示される実際のコントロールを定義します。TreeControl コンストラクタには少なくとも TreeModel を渡す必要があります。コンストラクタでは、コントロールのアイテムを作成する TreeControl.tree-item-creation-proc と、複数の選択を可能にするかどうかを決定する SelectionPolicy を指定することもできます。コントロールには、全体の外観を制御するローカル オプションも用意されています。
次の例では、単純なツリー コントロールを示しています。次の基本機能に注目してください。
加えて、非ローカル オプション の 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 エクスプローラなどのファイル システム ビューアで見つけたファイルに対する操作です。TreeControlSelectionContext から GuiEventTarget を継承するため、ツリー コントロールはイベントを受信して処理できます。次の各セクションでは、ツリー コントロールとツリー アイテムが受信する重要なイベントについて説明します。

CurrentNodeChanged

CurrentNodeChanged は、特にツリー コントロール API 用に定義された GUI イベントであり、現在のノードが変更されたときに TreeControl に警告します。次の例では、CurrentNodeChanged を使用して、対応するノードが選択されるとすぐに旗のイメージを表示します。
また、この例では、make-model.scurl という名前のサポート ファイルがインクルートされています。このファイルには、コントロールのデータ モデルを提供するプロシージャ build-model が定義されています。データ モデルの作成については、「ノードの追加」のセクションで説明されています。そのセクションの例では、build-model を定義するコードが示されています。
この例には、make-item.scurl というサポート ファイルもインクルードされています。このファイルには、tree-item-creation-proc でコントロールのアイテムの作成に使用されるツリー アイテム クラス FlagTreeItem が定義されています。TreeItem のサブクラス化については、「DefaultTreeItemのカスタマイズ」のセクションで説明されています。そのセクションの例では、FlagTreeItem を定義するコードが示されています。
この例のデータは、「Record Grid(レコード グリッド)」および「Record Form(レコード フォーム)」の章で使用されているのと同じ海上信号旗のデータ セットです。

例: 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
}

ActionSelectionChanged

ActionSelectionChanged は、さまざまな状況で使用される汎用の GUI イベントです。TreeControl においてノードに対するユーザー操作に応答するのに役立ちます。次の例では、両方のイベントを使用して、対応するノードがダブルクリックされるとすぐに旗のイメージを表示します。
イベント ハンドラが TreeControl に対して定義されていることに注意してください。ツリーのリーフ ノードがダブルクリックされた場合は、TreeControlAction イベントが送信されます。その後、Action のイベント ハンドラによって対応する旗が表示されます。この例でも、別のノードが選択されたときに旗の表示をクリアするためのシグナルとして SelectionChanged イベントを使用しています。
TreeControl は、SelectionContext を継承し、さらに GuiEventTarget を継承するため、イベントを処理できます。

例: 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 のサブクラスで定義されています。TreeItem のサブクラス化の詳細については、「DefaultTreeItem のカスタマイズ」を参照してください。on-pointer-press イベント ハンドラは、ノードのツリー アイテム ラベルがクリックされたときにノードを開きます。この例では TreeItem.toggle-node メソッドを使用してノードを展開しています。

例: 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 オブジェクトは、コントロール内のノードを操作する多数のメソッドを提供します。次のいくつかのセクションでこれらのメソッドの一部を説明します。

ノードの展開と折りたたみ

次の例では、TreeControl.expand-nodeTreeControl.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
}

ノードの選択と選択解除

次の例では、TreeControl.select-nodesTreeControl.deselect-nodes を使用して、現在選択されているノードの親ノードを選択したり選択を解除したりします。

例: 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
}

前のノードと次のノード

次の例では、TreeControl.previous-nodeTreeControl.next-node を使用して、現在のノードをプログラムによってツリーの上下に移動します。

例: 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
}

ノードの検索

次の例では、TreeControl.walk-nodes を使用して、選択された文字に対応する旗を検索します。この例は、ユーザーが文字を選択する DropdownListValueChanged ハンドラで walk-nodes を呼び出しています。
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
}

TreeModel の操作

TreeModel は、ツリー コントロールにおけるデータの構造を定義します。これにはツールのルートである TreeNode が 1 つ含まれます。TreeModel を変更するには、その中の TreeNode を変更します。ノードを追加、削除、または並べ替えるには、Sequence-of インターフェイスが提供するメソッドを使用できます。

ノードの追加

ツリー コントロール API には、ツリー モデルにノードを追加するためのメソッドがいくつか用意されています。
次の例では、TreeNode.append を使用して、レコード セットから選択されたデータをツリー ノードに追加することによってモデルを構築しています。このプロセスは、データのソースがディスク上のファイルまたはデータベースであるような実際のアプリケーションをシミュレーションします。
この例では、ノード データの柔軟性も示しています。TreeNode.node-data のデータ型は any であるため、自在にデータを指定できます。この例では、ノード データは、Record に格納されているファイル名を持つイメージです。

例: ツリー ノードの追加
{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}
次の例では、TreeNode.insert を使用して各ノードを最初の既存ノードの前に追加し、ツリー モデル内のノードの順番を逆にしています。

例: ツリー ノードの挿入
{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}

ノードの削除

次の例では、TreeNode.removeTreeNode.clear を使用して、ノードをツリーから削除します。

例: 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
}

ノードの並べ替え

次の例では TreeNode.reverseTreeNode.sort を示しています。右クリックのコンテキスト メニューを使用すると、現在のノードの直属の子を昇順でソートしたり、それらの子の順序を逆にしたりできます。コンテキスト メニューがツリー コントロールのツリー アイテムを作成する FoodTreeItem で定義されていることに注意してください。カスタム ツリー アイテムの作成の詳細については、「DefaultTreeItemのカスタマイズ」を参照してください。また、この例では、ノードに子がない場合にコンテキスト メニューの項目を無効にするために TreeNode.has-children? の値を使用していることにも注意してください。

例: 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
}

DefaultTreeItem のカスタマイズ

ツリー コントロールの既定の動作では、ノード データは単純なテキスト ラベルで提供されます。また、現在のノードはラベルを囲む境界線で示され、選択されたノードは背景と色を変更することによって示されます。
ツリー コントロールのビジュアルな外観を変更するには、カスタマイズした TreeItem を作成する必要があります。通常は、DefaultTreeItem のサブクラスを定義するのが最善の方法です。このクラスには、ツリー コントロールの外観をカスタマイズするためにオーバーライドできるいくつかのメソッドが定義されているからです。重要ないくつかのメソッドを次に説明します。

ラベル

次の例では、FlagTreeItem という名前の DefaultTreeItem のサブクラスを定義します。このクラスは DefaultTreeItem.get-label メソッドをオーバーライドします。get-label は、子を持つすべてのノードのノード データを返すだけです。旗のイメージを表すリーフ ノードの場合は、その旗に関連付けられている文字をノード データに格納されている文字列から取得します。ツリー コントロールは、TreeControl.tree-item-creation-proc に、FlagTreeItem を使用するプロシージャを設定しています。

例: カスタム ラベル
{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"}
                 }
         }
 }

現在のノードと選択されたノードの外観の変更

次の例には、ツリー コントロールにおける選択されたアイテムと現在のアイテムの表示方法を変更するために、DefaultTreeItem.modify-for-current および DefaultTreeItem.modify-for-selected メソッドをオーバーライドする FlagTreeItem が含まれています。選択されたノードまたは現在のノードのラベルの外観を変更するには、これらのメソッドをオーバーライドする必要があります。別のラベルまたはアイコンを使用して現在の状態または選択された状態を示す場合は、DefaultTreeItem.get-label メソッドから適切なラベルまたはアイコンを返す TreeItem.current? および TreeItem.selected? プロパティを使用、または 適切な DefaultTreeItem.icon を設定する必要があります。TreeItem.current? の値に応じて別のラベルを返す例 walk-nodes を参照してください。
左側のコントロールでは選択が無効になっているため、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 のサブクラスの作成

TreeNode のサブクラスを作成することが必要になる場合もあります。DefaultTreeNode クラスは、子ノードを含む基本ノードの作成に必要な機能を実装するため、カスタム ツリー ノードの作成に最適な基本クラスです。
TreeNode をサブクラス化する重要な目的の一つは、ノードに追加情報を格納して、ノードが TreeItem サブクラス内のその情報またはツリー コントロールで動作しやすくなるようにアクセッサを提供することです。
次の例でこのテクニックを示します。最初に DefaultTreeNode のサブクラスの FlagTreeNode クラスを作成します。この章で使用されている TreeNode と同様に、 フラグを含む画像を含む urlnode-data として保存します。 このサブクラスはこの url への型付きアクセスおよびフラグの音声名へのアクセスも提供します。 このクラスは既定のラベルを音声名に宣言します。
TreeControl.tree-item-creation-proc を使用して、 TreeItem の特別なサブクラスである FlagTreeItem を作成します。 FlagTreeItemFlagTreeNode を表示するために使用します。 フラグ関連の情報に簡単にアクセスするため、FlagTreeItem クラスは TreeItem.node をオーバーライドして、常に FlagTreeNode と関連していることを指定します。
同じような状況として、アイコンに使用するイメージ ファイルがノード データから取得できないためにそれを個別に格納する必要がある場合、 およびノード データ以外のパラメータ (たとえば修正時間やファイル ビューアのファイル サイズ) によってノードをソートする必要がある場合などがあります。このテクニックはこの状況で役立ちます。

例: 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
}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       

ツリー コントロールの階層

SelectionContext — MultiUIControlFrame — Observer
TreeControl
FrameControlUI
TreeControlUI — SkinnableControlUI
SkinnableTreeControlUI
BaseFrame
TreeItem
DefaultTreeItem
Sequence-of TreeNode
TreeNode
DefaultTreeNode
LazyTreeNode
Observable
TreeModel
TreeModelChange
Selection
TreeSelection