0
votes

I am working on trying to learn and understand WPF TreeView and Data Binding in XAML. I cannot seem to grasp the concept of data binding and your help is much appreciated.

The ISSUE is nothing displays in the TreeView. I am mostly interested in understanding how to correctly bind data in WPF to objects for the implementation of a tree view.

Concept: consider a SubjectList with a set of Subjects. Each Subject only has ONE student for the sake of this example

Expected Output in TreeView

Maths
    Student 1
Science
    Student 2
Arts
    Student 3

My current XAML attempt is based on the tutorial read here http://blogs.microsoft.co.il/pavely/2014/07/12/data-binding-for-a-wpf-treeview/.

Following this I have the following XAML:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication1"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <TreeView x:Name="treeView" HorizontalAlignment="Left"
              Height="284" 
              Margin="18,10,0,0" 
              VerticalAlignment="Top" 
              Width="115"
              ItemsSource="{Binding SubjectList}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding SubjectList}" 
                                      DataType="{x:Type local:Subject}">
                <TreeViewItem Header="{Binding SubjectName}"/>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</StackPanel>

MainWindow function to add Student Objects into a StudentList:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        Student s1 = new Student("Alex", 1);
        Student s2 = new Student("Kevin", 2);
        Student s3 = new Student("Sina", 3);
        Student s4 = new Student("Evan", 4);

        Subject a1 = new Subject("Maths", 1);
        Subject a2 = new Subject("Science", 2);
        Subject a3 = new Subject("Arts", 3);

        a1.setStudent(s1);
        a2.setStudent(s2);
        a3.setStudent(s3);

        Subjects list = new Subjects();
        list.AddSubjects(a1);
        list.AddSubjects(a2);
        list.AddSubjects(a3);
        DataContext = list;
    }
}

Classes

class Subjects
    {
        private List<Subject> subjectList;
        public List<Subject> SubjectList { get; set;}


        public Subjects()
        {
            SubjectList = new List<Subject>();
        }

        public void AddSubjects(Subject s)
        {
            SubjectList.Add(s);
        }
    }

     class Subject
    {
        private String subjectName;
        private List<Student> studentList;
        //accessor methods
        public String SubjectName { get; set; }
        public List<Student> StudentList { get; }
        public Subject()
        {
        }

        public Subject(string name, int id)
        {
            SubjectName = name;
            StudentList = new List<Student>();

        }

        public void setStudent(Student aStudent)
            {
                StudentList.Add(aStudent);
            }
    }

    class Student
    {
        public Student()
        {
        }

        public Student(string name, int id)
        {
            StudentName = name;
            StudentID = id;
        }

        private String studentName;
        private int studentID;

        //accessor methods
        public String StudentName { get; set;}
        public int StudentID { get; set; }

    }

}

What am i doing wrong? Anyone who can point me in the right direction of better understanding the concept of data binding in WPF to a list of objects would be a massive help in my self study!

Update 1: setting DataContext = list; and removing the DataContext reference in XAML resolved the issue of having two View's defined.

Question 2:: I am still a bit confused with View's. I have added a List as a property of a Subject Class.

How would you retrieve the Student Name from the List of Student objects which are within a Subject Object in XAML? Do you require a View for any/every collection you wish to show in the TreeView? I wish to learn how these pieces all work together. Any further material or assistance greatly appreciated.

2

2 Answers

1
votes

You are creating two instances of your Subjects view model, one in XAML

<Window.DataContext>
     <local:Subjects/>
</Window.DataContext>

and one in code

Subjects list = new Subjects();

Adding items to the list instance in code behind won't add them to the instance in the DataContext.

Change your code like this:

var list = (Subjects)DataContext;
list.AddSubjects(a1);
...

Or remove the DataContext assignment from your XAML and write the code behind like this:

var list = new Subjects();
list.AddSubjects(a1);
...
DataContext = list;

That said, it may make sense to use ObservableCollection instead of List to notify about collection changes, e.g. while adding or removing subjects.

0
votes

So I believe your confusion is rooted in the concept of binding so I will give a crack at trying to clarify it for you.

Take this code you posted:

    <Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication1"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <TreeView x:Name="treeView" HorizontalAlignment="Left"
              Height="284" 
              Margin="18,10,0,0" 
              VerticalAlignment="Top" 
              Width="115"
              ItemsSource="{Binding SubjectList}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding SubjectList}" 
                                      DataType="{x:Type local:Subject}">
                <TreeViewItem Header="{Binding SubjectName}"/>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</StackPanel>

In your code behind you set the datacontext to an instance of Subjects. That means the window's datacontext is Subjects.

Now as you go deeper into the xaml you reach the stackpanel. This inherits the same datacontext so the stackpanel's datacontext is still Subjects.

Now you get to the treeview. The treeview's datacontext is still Subjects which is why you are able to bind the ItemsSource property to SubjectList, a property on the Subjects class.

Now when you get to the TreeView.ItemTemplate, the datacontext is a single item from SubjectList, aka an instance of Subject.

The HierachicalDataTemplate is still using the same context of an instance of Subject. You can't bind the HierarchicalDataTemplate's ItemsSource to SubjectList because the Subject class does not have that property.

You can bind to StudentList though since the Subject class does have that property.

If you were to bind the ItemsSource to StudentList you could then inside the HierarchicalDataTemplate put a TextBox with the binding to subject name since inside of the HierachicalDataTemplate has the DataContext of a singular StudentItem

So finally the working code would look like this:

<Window x:Class="WpfApplication1.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:local="clr-namespace:WpfApplication1"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525">
<StackPanel>
    <TreeView x:Name="treeView" HorizontalAlignment="Left"
              Height="284" 
              Margin="18,10,0,0" 
              VerticalAlignment="Top" 
              Width="115"
              ItemsSource="{Binding SubjectList}">
        <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding StudentList}">
                <TextBox Text="{Binding StudentName}"/>
                <TextBox Text=" "/>
                <TextBox Text="{Binding ID}"/>
            </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
    </TreeView>
</StackPanel>

So all in all you were very close, but I hope this helps you understand what is going on when you go deeper into the bindings. If you are confused about anything leave a comment and I will try to make it clearer