C#ツリービューを試す2「ノードを検索して展開する」

コンピュータ
以前ツリービューでフォルダ構造を表現するサンプルを作成しました。エクスプローラーの左側のような動作をするように作成しましたが、機能的に外部から指定のフォルダーに移動する機能も必要では?と思い試作してみました。
C#ツリービューを試す
WinFormのツリービューのサンプルを作成します。 ソースコード using System.Diagnostics; namespace TreeViewSample01; public partial class Form1 : For...
namespace TreeSample2;

public partial class Form1 : Form
{
    // ノードからローカルパスを取得
    static string NodeToPath(TreeNode node, string pathText = "")
    {
        if (node.Parent == null)
            return pathText;
        
        if (node.Parent.Parent == null)
            return (node.Text + ":/" + pathText);

        return NodeToPath(node.Parent, (node.Text + "/" + pathText));
    }
    // ローカルパスからノードを取得
    static TreeNode? PathToNode(ref TreeView tree, string pathText)
    {
        var dir = pathText;
        if (File.Exists(pathText)) {
            dir = Path.GetDirectoryName(dir);
        };
        if (dir is null) return null;

        var dirs = dir.Split('\\');
        string tmpDir = "";
        TreeNode node = tree.TopNode;
        foreach(var d in dirs) {
            if (tmpDir == "") {
                tmpDir = d;
            } else {
                tmpDir = Path.Join(tmpDir, d);
            }

            var n = node.Nodes.Find(tmpDir, false);
            if (n.Length == 0) {
                // 見つからなかった
                node = node.Nodes.Add(d);
                node.Name = tmpDir;
            } else {
                // 見つかった
                node = n[0];
            }
        }

        return node;
    }
    public Form1()
    {
        InitializeComponent();

        // ツリービューの生成、登録
        TreeView treeView = new()
        {
            Dock = DockStyle.Fill,
        };

        // ルートノードの追加
        var rootNode = new TreeNode("MyComputer");
        treeView.Nodes.Add(rootNode);

        // ルートノードの下にドライブの一覧を追加
        foreach(string str in Directory.GetLogicalDrives())
        {
            // ドライブ名を切り出し
            var driveName = str[0..1];
            // ドライブの情報を取得
            var drive = new System.IO.DriveInfo(driveName);
            // 特定のドライブを排除
            switch (drive.DriveType) {
                case System.IO.DriveType.Fixed: // 固定ドライブ(HDD,SSD)
                case System.IO.DriveType.Network:   // ネットワークドライブ
                case System.IO.DriveType.Removable: // リムーバブル(USBメモリー)
                    var n = rootNode.Nodes.Add(driveName);
                    n.Name = driveName + ":";
                    break;
            };
        }
        // 選択アイテムの変更イベント
        treeView.AfterSelect += (s, e) =>
        {
            // Nullの場合何もせずに戻る。
            if (e.Node is null) return;

            // ルートの場合何もしない
            var node = e.Node;
            if (node.Parent is null) return;
            
            this.Text = node.Name;
            // ノードからパス変換
            string path = NodeToPath(node);
            
            // 子ノードの要素数が0より大きい場合
            if (node.Nodes.Count > 0) return;

            // ディレクトリ名をノードに追加
            foreach(var dir in Directory.EnumerateDirectories(path))
            {
                // 不可視属性を排除
                var attr = File.GetAttributes(dir);
                if ((attr&FileAttributes.Hidden) == FileAttributes.Hidden) continue;

                // ディレクトリを追加
                var dirName = Path.GetFileNameWithoutExtension(dir);
                var n = node.Nodes.Add(dirName);
                n.Name = dir;
            }
            // ノードを展開
            node.Expand();
        };

        // メニューバー
        ToolStrip menuBar = new()
        {
            Dock = DockStyle.Top,
        };

        // メニュー項目実行
        ToolStripMenuItem miGoHome = new()
        {
            Text = "ホーム",
        };
        miGoHome.Click += (sender, e) =>
        {
            var node = PathToNode(ref treeView, @"C:\Users\karet");
            if (node is null) return;
            treeView.SelectedNode = node;
        };
        menuBar.Items.Add(miGoHome);

        // コントロールの追加
        Controls.AddRange(new Control[]
        {
            treeView,
            menuBar
        });
    }//Form1
}//class

ユーザー関数PathToNode()でパス文字列からフォルダ構造をたどりながらノードをして最後のノードを返します。
ノードが既にある場合はパスに対応するノードを返す関数となります。

フォルダ構造はどれだけのフォルダがあるか実行する環境次第ですので、全てのフォルダをあらかじめツリービューのノードとして作成することは出来ません。(作れるとは思いますが遅すぎて実用的では無いと思われます。)というわけで初期は最小限としてルートとなるノードとその下の階層、サンプルではドライブレターの一覧のみを作成し、ノード選択をきっかけにサブフォルダを取得展開する仕様となっています。

今回検索機能を設けようと思ったのですが、ツリービューの検索で使い勝手の良い機能を見つけることが出来なかったので、泥臭いコードを書くことに成りました。思いついたままコードを書いてしまったので、もっと良い方法が見つかったら別の記事にしたいと思います。

コメント