Search code examples
mvvmsilverlight-4.0mvvm-lightsilverlight-toolkit

How to create DataTemplate in TreeView Silverlight 4.0


I have some complex requirement to create a DataTemplate for a TreeView in Silverlight. This is using MVVM design pattern. here is what i want:

Package1 Child1 Child2 Package2 Child3 Child4

Code:

 Class Package
 {
   string _Name;
   ObservableCollection<Child> _Childs;
public ObservableCollection<Child> Childs{get{return _Childs; } set{_Childs=value;}}
public string PackageName{get{return _Name; } set{_Name=value;}}

  }

 class Child
 {
   string ChildName;
public bool IsEnabled{get;set;}
public string Id{get; set;}
  }

Using the above data contract i should create a tree view: Package Name being TreeItem header value. Each child item is a child box with Content is ChildName and The Enabled property of the checkbox is binded to IsEnabled of Child.

I have tried the following code but it is not showing any tree view at all.

 <sdk:TreeView x:Name="SelectedPackagesTV" ItemsSource="{Binding Packages, Mode=TwoWay}"
                              ItemTemplate="{StaticResource PackageTemplate}">
                    <sdk:TreeView.Resources>
                        <DataTemplate x:Key="FormsTemplate" >
                            <CheckBox Content="{Binding Id, Mode=TwoWay}" IsEnabled="{Binding IsEnabled}" >

                            </CheckBox>
                        </DataTemplate>
                        <sdk:HierarchicalDataTemplate x:Key="PackageTemplate" ItemsSource="{Binding Childs, Mode=TwoWay}" ItemTemplate="{StaticResource FormsTemplate}">
                            <TextBlock Text="{Binding Name, Mode=TwoWay}" />
                        </sdk:HierarchicalDataTemplate>                            
                    </sdk:TreeView.Resources>                        
                </sdk:TreeView>

NOTE: I am populating values to the PackagesCollection in the view model constructor. Will that be a problem? Let me know if I am missing anything and also suggest me best way to resolve this.

Thanks in advance.


Solution

  • Edit:

    The binding in the "PackageTemplate" is set to "Name" but the public property on the class is "PackageName".

    Here is a sample of creating the data and attaching it. I have tested this and it works.

    public partial class MainWindow : Window
    {
    
        public MainWindow()
        {
            InitializeComponent();
    
            this.DataContext = new ViewModel();
        }
    
        public class Package
        {
            public string Name { get; set; }
            public ObservableCollection<Child> Childs { get; set; }
        }
    
        public class Child
        {
            public string ChildName { get; set; }
            public bool IsEnabled { get; set; }
        }
    
        public class ViewModel  : INotifyPropertyChanged
        {
            private ObservableCollection<Package> _packages;
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            public string TestStr { get { return "testing123"; } }
    
            public ViewModel()
            {
                List<Package> list = new List<Package>() { new Package() { Name = "pkg1", Childs = new ObservableCollection<Child>(new List<Child>() { new Child() { ChildName = "Child1" } }) } };
                this.Packages = new ObservableCollection<Package>(list);
            }
    
            public ObservableCollection<Package> Packages
            {
                get
                {
                    return this._packages;
                }
                set
                {
                    this._packages = value;
                }
            }
    
            protected void OnPropertyChanged(string name)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(name));
                }
            }
        }
    }
    

    A few things:

    Your business objects should look like this:

    public class Package
        {
            public string Name { get; set; }
            public ObservableCollection<Child> Childs { get; set; }
        }
    
        public class Child
        {
            public string ChildName { get; set; }
            public bool IsEnabled { get; set; }
        }
    

    Binding can only work on public Properties, not private fields

    Your xaml can be corrected to:

    <sdk:TreeView ItemTemplate= "{StaticResoruce PackageTemplate}"  ItemsSource="{Binding Packages}">
    <sdk:TreeView.Resources>
        <sdk:HierarchicalDataTemplate x:Key="PackageTemplate" 
                                          ItemsSource="{Binding Childs,Mode=TwoWay}"
                                          ItemTemplate="{StaticResource ChildTemplate}">
            <TextBlock Text="{Binding Name,Mode=TwoWay}" />
        </sdk:HierarchicalDataTemplate>
    
        <sdk:DataTemplate x:Key="ChildTemplate" >
            <CheckBox  Content="{Binding ChildName,Mode=TwoWay}"
                           IsEnabled="{Binding IsEnabled}">
            </CheckBox>
        </sdk:DataTemplate>
    </sdk:TreeView.Resources>
    

    The templates need to be in an enclosing "Resources" bracket, either of the TreeView or the UserControl.

    Your PackageTemplate should have the ItemsSource "Childs" instead of Packages. You are binding the PackageTemplate to "PackageName" but the Package class states "Name".

    Not an error but because your ChildTemplate has no children of its own it only needs to be a DataTemplate, no hierarchy.

    Also

    ItemTemplate= {"StaticResoruce PackageTemplate"}
    

    Should be:

    ItemTemplate= "{StaticResource PackageTemplate}"