2013年09月15日

WPFサンプル:階層構造をもったデータをTreeViewにバインドする

   このエントリーをはてなブックマークに追加 Clip to Evernote
まずはサンプルプログラムの実行時のスクリーンショットを。

treeview

TreeViewにデータを表示するということは、そのデータが階層構造を持っているということですから、 TreeViewのバインドするデータは、階層構造をもったクラスということになります。ここでは、Categoryというクラスを定義することとします。

public class Category : ObservableCollection<Category> {
    public Category() {
    }
    public Category(string name) {
        Name = name;
    }
    public string Name { get; set; }
 
    public Category Children { get; set; }
}

Categoryクラスには、Nameプロパティと、Childrenプロパティがあります。Nameはカテゴリの名前を、Childrenは、そのサブカテゴリを表します。
なお、Category自身を、ObservableCollection<Category> を継承したクラスとしており 複数のCategoryを要素として保持できるコレクションクラスとなっています。
そして、Childrenプロパティが自分自身のクラスであるCategory型なので、 階層的にデータを保持できるということです。

ここでは、さらに、OSAndBrowserというクラスを定義しています。
このクラスには、Categoryクラスをメンバーとして保持し、 このCategoryオブジェクトの初期化を行っています。

public class OSAndBrowser {
    public Category Categories { get; set; }
    public OSAndBrowser() {
        Categories = new Category() {
            new Category("OS") {
                Children = new Category {
                    new Category("Windows") {
                        Children = new Category {
                            new Category("Windows 8"),
                            new Category("Windows 7"),
                            new Category("Windows Vista"),
                            new Category("Windows XP"),
                        }
                    },
                    new Category("Mac OS X"),
                    new Category("Linux")
                }
            },
            new Category("ブラウザ") {
                Children = new Category {
                    new Category("Internet Explorer") {
                        Children = new Category {
                            new Category("IE 11.0"),
                            new Category("IE 10.0"),
                            new Category("IE 9.0"),
                            new Category("IE 8.0"),
                            new Category("IE 7.0"),
                        }
                    },
                    new Category("Firefox"),
                    new Category("Chrome"),
                    new Category("Opera"),
                    new Category("Safari"),
                }
            }
        };
    }
}

初期化のコードを見れば、Categoryがデータを階層的に保持している様子が 分かると思います。

では、XAMLです。

<Window x:Class="TreeViewSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:my="clr-namespace:TreeViewSample"
        Title="MainWindow" Height="400" Width="500" >
    <Window.Resources>
        <my:OSAndBrowser x:Key="OSAndBrowser" />
        <HierarchicalDataTemplate x:Key="treeViewTemplate"
                                  ItemsSource="{Binding Path=Children}">
            <TextBlock Text="{Binding Path=Name}" />
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Window.DataContext>
        <Binding Mode="OneWay" Source="{StaticResource OSAndBrowser}"/>
    </Window.DataContext>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="5*" />
            <ColumnDefinition Width="4*" />
        </Grid.ColumnDefinitions>
       
        <TreeView HorizontalAlignment="Stretch" Margin="7"  Name="treeView1"
                  VerticalAlignment="Stretch"
                  ItemTemplate="{StaticResource treeViewTemplate}"
                  ItemsSource="{Binding Categories}" />
        <TextBlock Grid.Column="1"  Height="23"  HorizontalAlignment="Left"
                   Margin="7" Name="textBlock1"  VerticalAlignment="Top"
                   Text="{Binding SelectedItem.Name, ElementName=treeView1,
                                  Mode=OneWay}" />
    </Grid>
</Window>

WindowのDataContextには、Window.Resourcesで定義した OSAndBrowserオブジェクトを設定しています。
TreeViewのItemsSourceプロパティには、OSAndBrowserオブジェクトのCategoriesプロパティをバインドしています。
これだけでは、TreeViewにどの要素を表示したらよいのかが分かりませんので、 ItemTemplate と HierarchicalDataTemplateを使い、表示する項目がNameプロパティであること、 階層構造として、Childrenプロパティを使うことを指定しています。
ウィンドウ右側のTextBlockでは、TreeViewで選択された項目の名前をバインドしています。



WPFサンプル・目次


この記事へのコメント
コードの中で、わからないところがあります。

xamlコードで、なぜ,my名前空間を設定しているのか。
windows.resourceで、
<my:OSAndBrowser <my:OSAndBrowser x:Key="OSAndBrowser" />="OSAndBrowser" />
は何をしているのか。x:Keyディレクティブが何をしているのか、何のために必要なのかがよくわかりません。

これは、WPFを学ぶ上で、根本的なところのように思えるのですが、現状、理解できる説明に出会ったことが無く、おまじない的に使用しています。しかし、このページのサンプルのような使い方だとお手上げです。プログラム自体が何をしているのかわからないのです。わかりやすい説明や考え方はないのでしょうか。
Posted by kukekko at 2015年04月30日 18:06
>なぜ,my名前空間を設定しているのか。
C#側で定義したクラスをXAMLで参照するためです。
そうしないと、XAMLが独自に定義した型を理解できません。

>x:Keyディレクティブが何をしているのか
このリソースに、x:Keyで名前を付けて、DataContext で参照できるようにしています。ともに同じ名前にしたのでわかりにくかったかもしれませんね。
リソースについては、以下のページからたどれる、リソースの利用に関連する記事を読んでください。
http://gushwell.ldblog.jp/archives/52313900.html
もし、XAMLについてのい知識が不足していると感じるならば、いきなり難しXAMLを理解しようとするのではなく、一連の記事の最初から、ひとつづつ理解していくほうが、近道だと思います。
Posted by gushwell at 2015年05月01日 08:59
なるほど、わかりました。
xmlns:my="clr-namespace:TreeViewSample"
これは、C#コードの名前空間を指定していたのですね。納得です。

今まで、xaml内で使うクラスは、そのビハインドコードに記述する必要があるのだと思っていました。そこで、xamlとそのビハインドコードだけ(*.xaml.cs)を使っていました。確かに、他のコードファイル内のクラスを使う方法がないとおかしいですね。

紹介されたリンクから「リソースの利用」下の5つのページを見ましたが、このあたりの説明は、無かったように思いました。

プロジェクト内で、複数のコードファイルを使ったり、分割する指針は、今回のスコープの話も含めて、入門書では触れられていないことが多いのではないのでは?と思っています。

ぼんやりとしていて、納得できなかったことが1つ解決できました。
ありがとうございました。
Posted by kukekko at 2015年05月01日 14:10
 

この記事へのトラックバックURL

http://trackback.blogsys.jp/livedoor/gushwell/52334146
この記事へのトラックバック
動機 WPFでキャラクターのボーン(関節)階層情報の一覧ツールを作る際に、TreeViewをちゃんとデータバインドしたかったので、調べてみました サンプルコード 体の関節をTreeViewで階層的に表示する、という例題を元に説明をしてきます。全体のコードはGitHubにアップしてい
TreeViewにデータバインディング【Studio Kanehira】at 2014年09月09日 01:24