ActiveX TreeView コントロールの DotNet TreeView コントロールへの変換

TreeView コントロールは、3ds Max のスクリプト ツールの中でももっとも広く使用される ActiveX コントロールです。TreeView コントロールを使用したスクリプトを DotNet TreeView コントロールを使用するように変換するプロセスは、比較的単純です。

ここでは、「チュートリアル - TreeView ActiveX コントロールを使用したシーン ブラウザの開発 - 第 1 部」のトピックで紹介されている単純なスクリプトを変換の一例として取り上げてみましょう。

次のスクリプトは、元の ActiveX コードをコメント アウトして緑色のフォントで示し、新たにコメント付きの DotNet コードを追加したものです。2 つのコードを比較してみましょう。

スクリプトの変換 - 第 1 部

    macroScript SceneTreeView category:"DotNet"
    (
    rollout treeview_rollout "TreeView Scene Browser"
    (
    /* ActiveXVersion:
    fn initTreeView tv =
    (
    tv.Indentation = 28*15
    tv.LineStyle = #tvwRootLines
    )
    */

    fn initTreeView tv =
    (
    tv.Indent= 28 --property name is different and value is in pixels
    --the default style already has root lines.
    )

    /* ActiveXVersion:
    fn addChildren tv theNode theChildren =
    (
    for c in theChildren do
    (
    newNode = tv.Nodes.add theNode.index 4 "" c.name 0
    addChildren tv newNode c.children
    )
    )
    */

    fn addChildren theNode theChildren =
    (
    for c in theChildren do
    (
    newNode = theNode.Nodes.add c.name --add to the parent!
    newNode.tag = dotNetMXSValue c --.tag can contain a MXS value
    addChildren newNode c.children --recursive call for new node
    )
    )

    /* ActiveX Version:
    fn fillInTreeView tv =
    (
    theRoot = tv.Nodes.add()
    theRoot.text = "WORLD ROOT"
    rootNodes = for o in objects where o.parent == undefined collect o
    addChildren tv theRoot rootNodes
    )
    */

    fn fillInTreeView tv =
    (
    theRoot = tv.Nodes.add "WORLD ROOT" --add parent node
    rootNodes = for o in objects where o.parent == undefined collect o
    addChildren theRoot rootNodes --no need to pass theTreeView
    )

    /*ActiveX Version:
    activeXControl tv "MSComctlLib.TreeCtrl" width:190 height:290 align:#center
    */

    dotNetControl tv "TreeView" width: 190 height:290 align:#center
    spinner spn_indent "Indentation" range:[0,100,28] type:#integer fieldwidth:40

    /*ActiveX Version:
    on tv nodeClick theNode do try(select (getNodeByName theNode.text))catch()
    */

    on tv Click arg do
    (
    --First get theTreeView node below the mouse cursor
    --The arg argument has properties .x and .y with the current pos.
    --Use showProperties arg to see what is available...
    --We use the TreeView method GetNodeAt to see what was clicked:
    hitNode = tv.GetNodeAt (dotNetObject "System.Drawing.Point" arg.x arg.y)
    if hitNode != undefined do --if a TreeView node was clicked,
    try(select hitNode.tag.value)catch(max select none)
    --...we try to select the object stored as value in the .tag
    )
    --We change the .indentation to .indent:
    on spn_indent changed val do tv.indent = val
    --The rest of the script does not require changes:
    on treeview_rollout open do
    (
    initTreeView tv
    fillInTreeView tv
    )
    )
    try(destroyDialog treeview_rollout)catch()
    createDialog treeview_rollout200 320
    )

続いて、「チュートリアル - TreeView ActiveX コントロールを使用したシーン ブラウザの開発 - 第 2 部」トピックで紹介されている TreeView のサンプルの後半を見てみましょう。この例では、ActiveX の ImageList コントロールを DotNet の ImageList オブジェクトに変換し、TreeView にアイコンを追加し、ノードのカラーをシーン オブジェクトのワイヤフレーム カラーに設定し、オブジェクトの表示状態を制御するためのチェックボックスを追加しています。

スクリプトの変換 - 第 2 部

    macroScript SceneTreeView category:"DotNet"
    (
    --We create an ImageList object to hold all icons for the TreeView
    --in the local scope of the MacroScript. In he case of ActiveX,
    --the ImageList had to be a UI control inside the rollout.
    --It is possible to create a local DotNet object inside the rollout
    --and pass the ImageList as parameter to the functions, but it is
    --more convoluted. In this version, all functions "see" the
    --ImageList DotNet object in the higher scope:
    ilTv = dotNetObject "System.Windows.Forms.ImageList"
    ilTv.imageSize = dotNetObject "System.Drawing.Size" 16 15
    rollout treeview_rollout "TreeView Scene Browser"
    (
    /* ActiveX Version:
    fn getIconFromBitmap thePath number =
    (
    tempBmp = openBitmap thePath
    iconBmp = bitmap 16 15
    for v = 0 to 14 do
    setPixels iconBmp [0,v] (getPixels tempBmp [(number-1)*16, v] 16)
    iconBmp.filename = getDir #image +"/_temp.bmp"
    save iconBmp
    close iconBmp
    close tempBmp
    getDir #image +"/_temp.bmp"
    )
    */
    --When creating icons for DotNet, it is necessary to create
    --one bitmap for each icon, because the DotNet image list will
    --lock the file for the duration of the script execution.
    --The original script used a single file to write temporarily
    --all icon bitmaps. This version writes a separate bitmap with the
    --name passed as third argument. The function only creates bitmaps
    --if they do not exist yet to avoid bitmap access errors.
    fn getIconFromBitmap thePath number iconFileName =
    (
    theFileName = getDir #image +"\\icon_"+ iconFileName +".bmp"
    if not doesFileExist theFileName do
    (
    tempBmp = openBitmap thePath
    iconBmp = bitmap 16 15
    for v = 0 to 14 do
    setPixels iconBmp [0,v] (getPixels tempBmp [(number-1)*16, v] 16)
    iconBmp.filename = theFileName
    save iconBmp
    close iconBmp
    close tempBmp
    )
    img = dotNetClass "System.Drawing.Image" --create an image
    ilTv.images.add (img.fromFile theFileName) --add to the list
    )
    /* ActiveX Version:
    fn initTreeView tv ilTv =
    (
    tv.Indentation = 28*15
    tv.LineStyle = #tvwRootLines
    tv.checkboxes = true
    tv.sorted = true
    ilTv.imagewidth = 16
    ilTv.imageheight = 15
    iconDir = (getDir #ui) + "\\icons"
    ilTv.listImages.add 1 #root (loadPicture (getIconFromBitmap (iconDir + "\\Standard_16i.bmp") 2))
    ilTv.listImages.add 2 #geomnode (loadPicture (getIconFromBitmap (iconDir + "\\Standard_16i.bmp") 1))
    ilTv.listImages.add 3 #light (loadPicture (getIconFromBitmap (iconDir + "\\Lights_16i.bmp") 3))
    ilTv.listImages.add 4 #camera (loadPicture (getIconFromBitmap (iconDir + "\\Cameras_16i.bmp") 2))
    ilTv.listImages.add 5 #helper (loadPicture (getIconFromBitmap (iconDir + "\\Helpers_16i.bmp") 1))
    tv.imageList = ilTv
    )
    */
    fn initTreeView tv =
    (
    tv.Indent= 28
    tv.CheckBoxes = true --same as in ActiveX
    --This is the icons directory.
    --We could also use the new colorman.resolveIconFolder() method
    --to get the path to the actual system icons...
    iconDir = (getDir #ui_ln) + "icons\\"
    --We call our function for each icon, this time also passing a
    --third argument with the icon name suffix.
    getIconFromBitmap (iconDir + "Standard_16i.bmp") 2 "Sphere"
    getIconFromBitmap (iconDir + "Standard_16i.bmp") 1 "Box"
    getIconFromBitmap (iconDir + "Lights_16i.bmp") 3 "Light"
    getIconFromBitmap (iconDir + "Cameras_16i.bmp") 2 "Camera"
    getIconFromBitmap (iconDir + "Helpers_16i.bmp") 1 "Helper"
    --At the end, we assign the ImageList to the TreeView.
    tv.imageList = ilTv
    )
    /* ActiveX Version:
    fn addChildren tv theNode theChildren =
    (
    for c in theChildren do
    (
    theIcon = case superclassof c of
    (
    GeometryClass: 2
    Light: 3
    Camera: 4
    Helper: 5
    Default:2
    )
    newNode = tv.Nodes.add theNode.index 4 "" c.name theIcon
    theNode.sorted = true
    newNode.checked = not c.isHidden
    newNode.forecolor = color c.wirecolor.b c.wirecolor.g c.wirecolor.r
    addChildren tv newNode c.children
    )
    )
    */
    fn addChildren theNode theChildren =
    (
    for c in theChildren do
    (
    newNode = theNode.Nodes.add c.name
    newNode.tag = dotNetMXSValue c
    --By default, all nodes will use icon 0 (the first one) unless
    --specified otherwise via the .iconIndex and .selectedIconIndex
    --properties.We set both of them to the icon corresponding to
    --the superclass of the scene object:
    newNode.imageIndex = newNode.selectedImageIndex = case superclassof c of
    (
    Default: 1
    Light: 2
    Camera: 3
    Helper: 4
    )
    newNode.checked = not c.isHidden--same as in ActiveX
    --For the color, we create a DotNet color class from the
    --wirecolor of the object and assign to the .forecolor of
    --the TreeView node:
    newNode.forecolor = (dotNetClass "System.Drawing.Color").fromARGB c.wirecolor.r c.wirecolor.g c.wirecolor.b
    addChildren newNode c.children
    )
    )
    --Since every node uses icon with index 0 unless specified otherwise
    --the Root Node will use the first icon by default.
    fn fillInTreeView tv =
    (
    theRoot = tv.Nodes.add "WORLD ROOT"
    rootNodes =for o in objects where o.parent == undefined collect o
    addChildren theRoot rootNodes
    )
    dotNetControl tv "TreeView" width:290 height:490 align:#center
    on tv Click arg do
    (
    hitNode = tv.GetNodeAt (dotNetObject "System.Drawing.Point" arg.x arg.y)
    if hitNode != undefined do
    try(select hitNode.tag.value)catch(max select none)
    )
    /*
    on tv NodeCheck theNode do
    (
    theSceneNode = (getNodeByName theNode.text)
    if theSceneNode != undefined do
    theSceneNode.isHidden = not theNode.checked
    )
    */
    --NOTE that in the case of ActiveX controls, we were using the name
    --displayed by the TreeView node. In the case of DotNet, we are
    --using the actual MAXScript value pointing at the scene object.
    --The arg argument provides a .node property containing the node
    --that was checked. We use the .tag property to access the
    --DotNetMXSValue stored in the TreeView node and then the .value to
    --get the MAXScript value stored in it. Then we set the isHidden
    --property of the scene object to the inverse of the checked state
    --of the TreeView node:
    on tv AfterCheck arg do
    try (arg.node.tag.value.isHidden = not arg.node.checked)catch()
    on treeview_rollout open do
    (
    initTreeView tv
    fillInTreeView tv
    )
    )
    try (destroyDialog treeview_rollout)catch()
    createDialog treeview_rollout 300 500
    )