CLRでツリービューのノードを描画するには

解決


しろくま  2008-02-23 05:27:06  No: 67645  IP: 192.*.*.*

.NET2005  VISTAです。

CLRのツリービューについてお尋ねします。

たいへん初歩的なことなのですが、ノードを描画する場合、階層について、手元の参考書では、

treeView1->Nodes->Add(_T("親"));
treeView1->Nodes[0]->Nodes->Add(_T("子"));
treeView1->Nodes[0]->Nodes[0]->Nodes->Add(_T("孫"));

というように例示しています。

しかし、これですと、ツリーの構造があらかじめ分かっていないと、描画することができません。

ノードごとのツリーの深さと構造とをあらかじめデータとして持っていて、

if (・・・) {
  treeView1->Nodes[a]->Nodes->Add(_T("子"));
}
else if (・・・) {
  treeView1->Nodes[b]->Nodes[c]->Nodes->Add(_T("孫"));
}

というような方法もあるかなぁとは思ったのですが、分岐の数で階層の深さの限界が決まってしまうので、なんだかピンときません。

くぐってみましたが、さがし方が悪いのか、参考になるようなコードは見つかりませんでした。

なにか方法はあるのでしょうか。

編集 削除
επιστημη  2008-02-24 02:42:52  No: 67646  IP: 192.*.*.*

やり方次第でどうにでもなります。

たとえば

(降りる)
子1
(降りる)

(登る)
子1

なんてなデータを用意しておき、

TreeNode^ current;
TreeNode  node;
if ( 読んだデータ = (降りる) )
  current = node;
else if ( 読んだデータ = (昇る) )
  current = current->Parent;
else {
  node = gcnew TreeNode(読んだデータ);
  current->Nodes->Add(node);
}

ってやれば


 |
 +--子1
 |   |
 |   +-- 孫
 |
 +--子2

ってなるです。

編集 削除
しろくま  2008-02-24 13:21:35  No: 67647  IP: 192.*.*.*

επιστημη さん、ありがとうございます。

お話の意味は理解していると思うのですが、回帰自体がまだよく分かっていなくて、少し時間がかかりそうです。
チャレンジします。


実は。。。

お返事と違うことを書くのは、たいへん心苦しいのですが、
あちこち「くぐって」みました。

VBについて、

Dim NodeX As Node
Set NodeX = trvItem.Nodes.Add(, , "Root", "ルート")
Set NodeX = trvItem.Nodes.Add("Root", tvwChild, "Child1", "子ノード1")
Set NodeX = trvItem.Nodes.Add("Child1", tvwChild, "Child2", "子ノード2")
Set NodeX = trvItem.Nodes.Add("Child2", tvwChild, "Child3", "子ノード3")
Set NodeX = trvItem.Nodes.Add("Child3", tvwChild, "Child4", "子ノード4")

というコードを見つけました。
これを下記のようにおきかえたところ、

treeView1->BeginUpdate();
treeView1->Nodes->Add("Root", tvwChild, "Child1", "子ノード1");
treeView1->Nodes->Add("Child1", tvwChild, "Child2", "子ノード2");
treeView1->Nodes->Add("Child2", tvwChild, "Child3", "子ノード3");
treeView1->Nodes->Add("Child3", tvwChild, "Child4", "子ノード4");
treeView1->EndUpdate();

ビルドでエラーは出ないのですが、実行すると、
「'tvwChild' : 定義されていない識別子です。」
というエラーになります。
Nodes  Add  tvwChild  で検索をかけてみたのですが、役に立つ情報は得られませんでした。

「Set NodeX = trvItem.Nodes.Add(, , "Root", "ルート")」
をはずしたのは、これがあるとビルド時にエラーになるというレベルの話です。
第一引数と、第二引数が空欄なのがいけないようですが、どうしたらいいのか分かりません。


次に、C#のコードを見つけました。

treeView1.Nodes.("花").Nodes.Add("ヒマワリ");

「花」というノードの下に、「ヒマワリ」という子ノードをぶら下げる、というコードなのですが、

treeView1->BeginUpdate();
treeView1->Nodes->Add("子ノード1");
treeView1->Nodes->Add("子ノード2");
treeView1->Nodes->Add("子ノード3");
treeView1->Nodes["子ノード3"]->Nodes->Add("子ノード4");
treeView1->EndUpdate();

としてみると、ビルドは通りますが、実行すると、
NullReferenceException はハンドルされませんでした。
オブジェクトインスタンスの作成には、newキーワードを使用します。
というダイアログボックスみたいなのが出ます。


気を悪くなさったら許して頂きたいのですが、VC++のマネージコードでも、こうした形で
ノードをつけることは、できないでしようか。。。

編集 削除
επιστημη  2008-02-24 18:05:18  No: 67648  IP: 192.*.*.*

できます。マニュアルを読みましょう。

編集 削除
そだ  2008-02-24 18:52:41  No: 67649  IP: 192.*.*.*

細かいんですが
>お話の意味は理解していると思うのですが、回帰自体がまだよく
再帰関数のことですよね

>VC++のマネージコードでも
MSDN見た感じだとこんなかなぁ
treeView1->Nodes->Add(gcnew String(_T("子ノード1")));
System名前空間が若干気になりますが・・・普通入ってるような

編集 削除
επιστημη  2008-02-24 21:44:01  No: 67650  IP: 192.*.*.*

> treeView1->Nodes->Add(gcnew String(_T("子ノード1")));
> System名前空間が若干気になりますが・・・普通入ってるような

問題はそこじゃない。文字列リテラルはSystem::Stringに暗黙変換されます。

検索サイトを探し回るのは構いませんが、なぜマニュアルを読まないの?

 treeView1->Nodes->Add("1","親");
 treeView1->Nodes["1"]->Nodes->Add("1.1","長男");
 treeView1->Nodes["1"]->Nodes->Add("1.2","二男");
 treeView1->Nodes["1"]->Nodes["1.1"]->Nodes->Add("1.1.1","初孫");

↑さっくりできちゃいましたょ?

編集 削除
επιστημη  2008-02-24 22:03:39  No: 67651  IP: 192.*.*.*

> 細かいんですが
>> お話の意味は理解していると思うのですが、回帰自体がまだよく
> 再帰関数のことですよね

再帰関数になんかなってませんよ。
データ構造が再帰的なだけ。
※ 当然ですわね、TeeeNodeの子もまたTreeNodeなんだから。

編集 削除
しろくま  2008-02-25 03:22:49  No: 67652  IP: 192.*.*.*

επιστημηさん、そださん、ありがとうございます。

επιστημηさんがご提示のコード、試しました。

「回帰」は、許されるなら、笑って見逃してやってください。
申し訳ありません。

実は、説明が不十分だったと反省したのですが、ツリー構造の深さにかかわらずに描画できるコードを探しています。

treeView1->Nodes->Add("1","親");
treeView1->Nodes["1"]->Nodes->Add("1.1","長男");
treeView1->Nodes["1"]->Nodes->Add("1.2","二男");
treeView1->Nodes["1"]->Nodes["1.1"]->Nodes->Add("1.1.1","初孫");

ですと、やはり階層が多くなるごとに、Nodes->が増えてゆきます。

試しに、
treeView1->Nodes["1"]->Nodes["1.1"]->Nodes->Add("1.1.1","初孫");

treeView1->Nodes["1.1"]->Nodes->Add("1.1.1","初孫");
に置き換えてみたのですが、ビルドは通るものの、実行はできませんでした。

やはり「再帰」的にたどってゆくしかないのでしょうか。


> なぜマニュアルを読まないの?

読んではいるのですが。。。
そもそも、私には、Add が見つかりません。
改めて「treeView Nodes Add」で検索して、επιστημηさんがご提示の
コード例は見つかりましたが、そこまでです。
(正直に言うと、質問を投稿する前にもこのページには行っていましたが、コードは見過ごしていました。)
「treeView メンバ」で検索しても、「Nodes」までです。
「Add」だけで検索しても、
TreeNodeCollection..Add メソッド は見つかりますが、感動を呼ぶようなことはあまり書いてないようです。

きっとどこかに書いてあるのでしょうが。。。

編集 削除
επιστημη  2008-02-25 06:10:34  No: 67653  IP: 192.*.*.*

> やはり「再帰」的にたどってゆくしかないのでしょうか。

再帰しないやりかたはありますが、再帰的な構造には再帰的な処理がもっとも楽で単純です。

>「treeView メンバ」で検索しても、「Nodes」までです。

あぁ、調べ方を知らないんだ。

TreeView.Nodes の型は何と書いてありましたか?
TreeViewCollection のはず。
そこでTreeViewCollection のヘルプを開けば...

編集 削除
επιστημη  2008-02-25 09:32:23  No: 67654  IP: 192.*.*.*

> treeView1->Nodes->Add("1","親");
> treeView1->Nodes["1"]->Nodes->Add("1.1","長男");
> treeView1->Nodes["1"]->Nodes->Add("1.2","二男");
> treeView1->Nodes["1"]->Nodes["1.1"]->Nodes->Add("1.1.1","初孫");
> ですと、やはり階層が多くなるごとに、Nodes->が増えてゆきます。

ちょいちょいと書き替えてみましょ。

TreeNode^ current = nullptr;
TreeNode^ node = gcnew TreeNode("親");
treeView1->Nodes->Add(node);
current = node; // current は"親"
node = gcnew TreeNode("長男");
node->Nodes->Add(node); // "親"の下に"長男"
current = node; // current は"長男"
node = gcnew TreeNode("初孫");
current->Nodes->Add(node); // "長男"の下に"初孫"
current = current->Parent; // current は(階層を登って)"親"
node = gcnew TreeNode("次男");
node->Nodes->Add(node); // "親"の下に"次男"

ですと、階層が多くなるごとに、Nodes->が増えて"ゆきません"。

編集 削除
επιστημη  2008-02-25 09:46:28  No: 67655  IP: 192.*.*.*

で、こいつを一般化すると、だ。

    array<String^>^ data = { "ナミヘイ", 
                               "→", "サザエ", 
                                 "→", "タラ", 
                               "←", "カツオ", "ワカメ",
                           };
    TreeNode^ current = nullptr;
    TreeNode^ node;
    for each ( String^ text in data ) {
      if ( text == "→" ) {
        current = node;
      } else
      if ( text == "←" ) {
        current = current->Parent;
      } else {
        node = gcnew TreeNode(text);
        if ( current == nullptr ) treeView1->Nodes->Add(node);
        else current->Nodes->Add(node);
      }
    }

編集 削除
しろくま  2008-02-26 05:34:54  No: 67656  IP: 192.*.*.*

επιστημηさん、たいへんありがとうございました。

ご提示のコードを、実行しました。
ステップ実行して、手順を確認しました。
コードをいろいろ変化させて、応用を試してみます。

併せて、ヘルプの調べ方も、ありがとうございました。
「・・・Collection」は、なにかにつけ登場するので、気にはしていましたが、よく分っていませんでした。

編集 削除
επιστημη  2008-02-26 08:58:04  No: 67657  IP: 192.*.*.*

うーん...
つまり最初のレスでとっくに答が出てたわけじゃん?
そのものズバリのコードが提示されるまで手も足も出ないてのは
すっごく問題ですよ。

編集 削除
しろくま  2008-02-26 20:10:19  No: 67658  IP: 192.*.*.*

επιστημηさん、こんにちは。
ご回答、ありがとうございました。

問題は、相当にあります。
でも、現実に、自分では、解決にたどりつけなかったかもしれません。
ノードの親子関係を、配列の中で、どのように表現したらいいのか、考えていました。
回答をご提示頂いて、「どうだ、簡単だろう」と言われれぱ、シンプルではありますが、私にはあまり簡単でもありません。

編集 削除
επιστημη  2008-02-26 23:09:38  No: 67659  IP: 192.*.*.*

↓再帰を使わない例。これではいかがでしょ。

      array<String^>^ data = {
        // テキスト, 親テキスト
        "ナミヘイ", nullptr,
        "サザエ",   "ナミヘイ",
        "カツオ",   "ナミヘイ",
        "ワカメ",   "ナミヘイ",
        "タラ",     "サザエ",
      };
      Dictionary<String^,TreeNode^> dic;
      for ( int i = 0; i < 5*2; i += 2 ) {
        TreeNode^ node = gcnew TreeNode(data[i]);
        dic.Add(data[i],node);
        String^ key = data[i+1];
        if ( key == nullptr ) {
          treeView1->Nodes->Add(node);
        } else {
          dic[key]->Nodes->Add(node);
        }
      }

編集 削除
しろくま  2008-02-27 05:04:05  No: 67660  IP: 192.*.*.*

επιστημηさん、ありがとうこざいます。

また、わからなくなりました。

最後にご提示のコードのうち、まず、Dictionaryは、VC++では用意されていないようです。
ただ、こうしたものを使わなくても、下記で、通るような気がしたのです。

array<String^>^ data = {
    // テキスト, 親テキスト
    "ナミヘイ", nullptr,
    "サザエ",   "ナミヘイ",
    "カツオ",   "ナミヘイ",
    "ワカメ",   "ナミヘイ",
    "タラ",     "サザエ",
};
TreeNode^ node;
TreeNode^ current = nullptr;

for ( int i = 0; i < 5*2; i += 2 ) {
    node = gcnew TreeNode(data[i]);
    if ( i == 0 ) {
        treeView2->Nodes->Add(node);
    } else {
        current = gcnew TreeNode(data[i+1]);
        current->Nodes->Add(node);
    }
}

再帰のコードで、ステッブ実行すると、「さざえ」ノードを追加するとき、TreeNode^ current  は、
gcnew TreeNode("ナミヘイ");
になっています。
それで、
current->Nodes->Add(gcnew TreeNode("サザエ"));
で、
ナミヘイの子ノードに、サザエが追加されています。

ということは、再帰を使わなくても、
current = gcnew TreeNode(data[i+1]);
//(data[i+1]は"ナミヘイ")
current->Nodes->Add(node);
//(nodeは"ナミヘイ")
で、同じように、ナミヘイの子にサザエが追加されるはずだと思いました。

でも、実際には、ナミヘイしか表示されません。

重ね重ね申し訳ありませんが、アドバイスを頂けませんでしょうか。

編集 削除
しろくま  2008-02-27 05:07:01  No: 67661  IP: 192.*.*.*

すみません。
node は、「サザエ」です。
タイプミスしました。

編集 削除
επιστημη  2008-02-27 06:24:51  No: 67662  IP: 192.*.*.*

> 最後にご提示のコードのうち、まず、Dictionaryは、VC++では用意されていないようです。

名前空間 System::Collections::Generic にありますょ?

> ただ、こうしたものを使わなくても、下記で、通るような気がしたのです。
> ...
> でも、実際には、ナミヘイしか表示されません。

"通るような気がした"のが間違いだからです。

編集 削除
επιστημη  2008-02-27 06:42:10  No: 67663  IP: 192.*.*.*

> ということは、再帰を使わなくても、
> current = gcnew TreeNode(data[i+1]);
> current->Nodes->Add(node);
> で、同じように、ナミヘイの子にサザエが追加されるはずだと思いました。

このcurrentがTreeViewにAddされていませんから当然表示されませんね。
かといってTreeViewにAddされたとしたら、ナミヘイがたくさん表示されてしまいますけど。

編集 削除
しろくま  2008-02-28 05:50:14  No: 67664  IP: 192.*.*.*

επιστημηさん、ありがとうこざいます。

再帰を使わない例で、System::Collections::Generic  を追加することで、表示させることができました。

ヘルプで、Dictionaryの名前空間が  System::Collections::Generic  であることを知る手順も確かめました。

current = gcnew TreeNode(data[i+1]);
current->Nodes->Add(node);
がうまくいかない理由を含め、とても「理解した」とは言えない状態ですが、少し時間をかけて確かめてみます。

たいへんありがとうございました。

編集 削除