none
TreeViewのItemsSourceから作られたTreeViewItemにxamlから子を定義する方法について RRS feed

  • 質問

  • TreeViewのItemsSourceにコレクションをバインドして、できたノード全ての子に同じ名前・レイアウトの子ノードを追加したいと考えています。

    全て同じデザインなのでできればxaml内で定義したいのですが、実現できません。

    以下のように定義してみたのですが、

    <TreeView ItemsSource="{Binding PluginEntries}">
    	<TreeView.ItemTemplate>
            	<DataTemplate>
                    	<TreeViewItem Header="{Binding Name}">
                                <TreeViewItem Header="Test" />
                            </TreeViewItem>
    		</DataTemplate>
    	</TreeView.ItemTemplate>
    </TreeView>

    親もしくは子だけを選択することができず、親と子ノードが一緒に選択されてしまいます。

    個別に選択できるように設定する方法はありませんでしょうか。

    よろしくご教示ください。

    2012年10月1日 16:23

回答

  • >> さっそく試してみたのですが、要素が複数あるときにノードを展開していくと、2つ目以降のノードを展開するたびに

    >>それ以前のノードの子(サンプルではTestノード)が消えてしまいます。

    ・・・あう、本当ですねorz 何か前も同じような事やったような気が(汗)
    そういえばXAMLだけでItemsSourceを動的生成するのは、結局うまくいかなかったんですよね・・・

    >> 追加する子ノードは複数あり、それぞれに選択したときのアクションを個別に設定したいと考えています。

    そのアクションをどこに定義するかが鍵ですね。親ノードの元オブジェクトにそのアクション(Commandオブジェクトでも良いし単なる文字列でもOK)に対応する配列を持てるのであれば、HierarchicalDataTemplateだけで実現できますよ。

    こんな感じです。

    <Window x:Class="WpfApplication12.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <TreeView ItemsSource="{Binding PluginEntries}">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Path=Functions}">
                        <HierarchicalDataTemplate.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding}"/>
                            </DataTemplate>
                        </HierarchicalDataTemplate.ItemTemplate>
                        
                        <TextBlock Text="{Binding Path=Name}"/>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </Grid>
    </Window>
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel();
            }
        }


        class ViewModel
        {
            public List<Plugin> PluginEntries { get; set; }
    
            public ViewModel()
            {
                this.PluginEntries = new List<Plugin>();
                this.PluginEntries.Add(new Plugin { Name = "あああ", Functions = new List<string> { "機能1", "機能2" } });
                this.PluginEntries.Add(new Plugin { Name = "いいい", Functions = new List<string> { "機能3", "機能4" } });
                this.PluginEntries.Add(new Plugin { Name = "ううう", Functions = new List<string> { "機能5", "機能6" } });
            }
        }
    
        class Plugin
        {
            public string Name { get; set; }
            public List<string> Functions { get; set; }
        }

    ※多分こっちで正解、かな?

    経験上、TreeViewおよびHierarchicalDataTemplateは結構クセのある代物なので、XAMLだけでやろうとせずにViewModel等をうまく工夫して組み合わせてやる必要があると思います。
    というかこれ以上となると私はちょっとお手上げですねー(苦笑)

    • 回答としてマーク 夏洲 2012年10月4日 15:38
    2012年10月3日 16:06

すべての返信

  • TreeViewは子コントロールの動的生成時にベースにTreeViewItemを用いるため、上記のコードだとTreeViewItemが2重に生成されることになります。
    こういう場合はHierarchicalDataTemplateを使うのですが、

    <TreeViewItem Header="Test"/>

    の部分が静的なので(それが目的かどうかは別ですが)、ItemsSourceにBindingできないため、ちょっと細工をしてみました。

            <TreeView xmlns:col="clr-namespace:System.Collections;assembly=mscorlib"
                      ItemsSource="{Binding PluginEntries}">
                <TreeView.Resources>
                    <CollectionViewSource x:Key="children">
                        <CollectionViewSource.Source>
                            <col:ArrayList>
                                <TreeViewItem Header="Test" />
                            </col:ArrayList>
                        </CollectionViewSource.Source>
                    </CollectionViewSource>
                </TreeView.Resources>
                
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Source={StaticResource children}}">
                        <TextBlock Text="{Binding Path=Name}"/>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
    

    TreeViewの方でCollectionViewSourceの静的リソースを定義しておいて、それを動的生成された各TreeViewItemの子ノードのItemsSourceに使っています。

    こうすることでHierarchicalDataTemplateを用いることができるようになります。

    ※というか、質問の内容を考えるとHierarchicalDataTemplateを使うだけで実現できそうな気が・・・(笑)

    以上、参考になれば幸いです。

    2012年10月2日 0:44
  • みっと様

    ご回答頂きありがとうございます。

    さっそく試してみたのですが、要素が複数あるときにノードを展開していくと、2つ目以降のノードを展開するたびに

    それ以前のノードの子(サンプルではTestノード)が消えてしまいます。

    リソースのインスタンスが一つしかないのが原因だと思い、x:Sharedを試してみたのですがダメでした。

    >※というか、質問の内容を考えるとHierarchicalDataTemplateを使うだけで実現できそうな気が・・・(笑)

    質問の仕方を考えているうちに色々書き漏れておりました。

    追加する子ノードは複数あり、それぞれに選択したときのアクションを個別に設定したいと考えています。

    このような場合でもHierarchicalDataTemplateだけで設定できますでしょうか。

    2012年10月2日 17:38
  • >> さっそく試してみたのですが、要素が複数あるときにノードを展開していくと、2つ目以降のノードを展開するたびに

    >>それ以前のノードの子(サンプルではTestノード)が消えてしまいます。

    ・・・あう、本当ですねorz 何か前も同じような事やったような気が(汗)
    そういえばXAMLだけでItemsSourceを動的生成するのは、結局うまくいかなかったんですよね・・・

    >> 追加する子ノードは複数あり、それぞれに選択したときのアクションを個別に設定したいと考えています。

    そのアクションをどこに定義するかが鍵ですね。親ノードの元オブジェクトにそのアクション(Commandオブジェクトでも良いし単なる文字列でもOK)に対応する配列を持てるのであれば、HierarchicalDataTemplateだけで実現できますよ。

    こんな感じです。

    <Window x:Class="WpfApplication12.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
        <Grid>
            <TreeView ItemsSource="{Binding PluginEntries}">
                <TreeView.ItemTemplate>
                    <HierarchicalDataTemplate ItemsSource="{Binding Path=Functions}">
                        <HierarchicalDataTemplate.ItemTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding}"/>
                            </DataTemplate>
                        </HierarchicalDataTemplate.ItemTemplate>
                        
                        <TextBlock Text="{Binding Path=Name}"/>
                    </HierarchicalDataTemplate>
                </TreeView.ItemTemplate>
            </TreeView>
        </Grid>
    </Window>
    
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
                this.DataContext = new ViewModel();
            }
        }


        class ViewModel
        {
            public List<Plugin> PluginEntries { get; set; }
    
            public ViewModel()
            {
                this.PluginEntries = new List<Plugin>();
                this.PluginEntries.Add(new Plugin { Name = "あああ", Functions = new List<string> { "機能1", "機能2" } });
                this.PluginEntries.Add(new Plugin { Name = "いいい", Functions = new List<string> { "機能3", "機能4" } });
                this.PluginEntries.Add(new Plugin { Name = "ううう", Functions = new List<string> { "機能5", "機能6" } });
            }
        }
    
        class Plugin
        {
            public string Name { get; set; }
            public List<string> Functions { get; set; }
        }

    ※多分こっちで正解、かな?

    経験上、TreeViewおよびHierarchicalDataTemplateは結構クセのある代物なので、XAMLだけでやろうとせずにViewModel等をうまく工夫して組み合わせてやる必要があると思います。
    というかこれ以上となると私はちょっとお手上げですねー(苦笑)

    • 回答としてマーク 夏洲 2012年10月4日 15:38
    2012年10月3日 16:06
  • みっと様

    >経験上、TreeViewおよびHierarchicalDataTemplateは結構クセのある代物なので、XAMLだけでやろうとせずにViewModel等をうまく工夫して組み合わせてやる必要があると思います。

    そうですか...。xamlだけでの定義はあきらめる方向で検討してみます。

    ご回答頂き、ありがとうございました。

    2012年10月4日 15:38