ゲームエフェクトデザイナーのブログ | A Real-Time VFX Artist's Blog

About Making Materials on UE, Making Tools with C#, etc

C# TreeViewの親子階層内で指定のノードを選択する

TreeViewで指定のノードを楽に選択する方法が用意されていない感じがします。

FullPathを使う方法


ノードのプロパティ FullPath で、例えば選択中のノードのフルパスを取得できます。

そうしてあらかじめ保存しておいた FullPath 情報を利用して、後からまたそのノードを選択する際に FullPath が利用できます。

ただし、同階層に同じ名称のノードがあると破綻するようです。

なので FullPath が利用できるのは、Windowsエクスプローラーのように同階層に同じ名前を許さない条件下に限られそうです。

Index を使う方法


ノードのプロパティ Index を使って順番を記憶しておいて、それを使って選択する方法です。

しかしそもそもTreeViewの仕様上、ノード自身とその1つ上の親か1つ下の子供しかアクセスできないようで、階層を辿っていくには再帰処理を自前で組んでやる必要があります。

うーん面倒。。

単純に階層関係無しにノードをツリー最上段から順に数えていって何番目かをGETして、それを使って選択できたりすると非常に楽なんですが‥例えば下図で選択中のノードは「6つ目のノード」という感じで。

f:id:moko_03_25:20190613142627j:plain

仕方ないので、再帰処理で実現したいと思います。

まずは選択中のノードのIndexと、親ノードのIndexを取得してみます。

private void GetSelNodeIndex()
{
	// 選択中のノードを取得
	TreeNode tn = treeView1.SelectedNode;

	// 選択中のノードのIndexを取得
	int i = tn.Index;

	// 親のノードを取得
	TreeNode tnP = tn.Parent;
	int iP;
	// 親がある場合
	if (tnP != null)
	{
		// 親のノードのIndexを取得
		 iP = tnP.Index;

		MessageBox.Show("選択中のノードのIndex: " + i.ToString() + "\r\n" +
					"親ノードのIndex: " + iP.ToString());
	}
	else
	{
		MessageBox.Show("選択中のノードのIndex: " + i.ToString() + "\r\n" +
					"親ノードのIndex: " + "なし");
	}
}

// GET
private void Button1_Click(object sender, EventArgs e)
{
	GetSelNodeIndex();
}

親が存在しないと例外が発生するので対処しておきます。

実行してボタンを押すとこんな感じ。

f:id:moko_03_25:20190613143215j:plain

次のステップとして、こちらを再帰処理にしてindexをコレクションに格納します。

// 変数
List<int> listNodeId = new List<int>();

// 選択ノードのIndexを取得
private void GetSelNodeIndex()
{
	// リストとラベルを空にする
	listNodeId.Clear();
	label1.Text = "";

	// 選択ノードを取得
	TreeNode tn = treeView1.SelectedNode;

	// 選択ノードのIndexを取得してListに格納
	listNodeId.Add(tn.Index);

	// 親ノードを取得
	TreeNode tnP = tn.Parent;

	// 再帰処理
	GetSelNodeRecursive(tnP);

	// ラベルに表示(とりあえず雑に)
	foreach (var item in listNodeId)
	{
		label1.Text += item.ToString() + " ";
	}
}
// 再帰処理
private void GetSelNodeRecursive(TreeNode tnP)
{
	// 親がある場合
	if (tnP != null)
	{
		// 親ノードのIndexを取得してListの先頭に挿入
		listNodeId.Insert(0, tnP.Index);

		// さらに親に対して実行
		GetSelNodeRecursive(tnP.Parent);
	}
}

うまくいきました。

f:id:moko_03_25:20190613150654g:plain

次は、GETで保存しておいたノードを後から選択します。

先ほどと逆に再帰処理でリストから順に親から子へIndexを辿っていくようにします。

// 最終的に選択したいノードを宣言
TreeNode selNode;

// 選択ノードのIndexを取得
private void SetSelNodeIndex()
{
	// リストからトップ階層のノードを取得
	TreeNode tnP = treeView1.Nodes[listNodeId[0]];
	// 最終的に選択したいノードに一旦指定
	selNode = tnP;

	// リストのIndex指定用
	int i = 1;

	// リストに子のIndex情報がある場合
	if (listNodeId.Count > i)
	{
		SetSelNodeRecursive(tnP, i);
	}

	treeView1.Focus();
	treeView1.SelectedNode = selNode;
}
// 再帰処理
private void SetSelNodeRecursive(TreeNode tnP, int i)
{
	// リストから1つ下の子ノードを取得
	TreeNode tnC = tnP.Nodes[listNodeId[i]];
	// 最終的に選択したいノードに指定
	selNode = tnC;

	// カウントアップ
	i++;

	// リストに子のIndex情報がある場合
	if (listNodeId.Count > i)
	{
		SetSelNodeRecursive(tnC, i);
	}
}

これで当初やりたかった「指定したノードを選択する」ことができるようになりました!

f:id:moko_03_25:20190613153657g:plain


しかし‥もっと良い方法がある気がします。

ご存じの方いらっしゃったらご教授ください。。