ske
ske

Reputation: 5284

TreeView's child nodes are unable to reflect changes in binding

Only the top level nodes can reflect the changes behind but their nodes can't. Any ideas? Thanks! In the following code, it should add a new manager and a new employee in the same time .

Models

public class Employee
{
    public Guid EmployeeId { get; set; }
    public string EmployeeName { get; set; }
}

public class Manager
{
    public Guid ManagerId { get; set; }
    public string ManagerName { get; set; }
    public IEnumerable<Employee> Employees { get; set; }
}

ViewModels

public class TreeWindowViewModel : INotifyPropertyChanged
{
    private List<Employee> employeeList = new List<Employee>();
    private List<Manager> managerList = new List<Manager>();

    public TreeWindowViewModel()
    {
        //Create fake data for testing
        employeeList.Add(new Employee() { EmployeeId = Guid.NewGuid(), EmployeeName = "Adam" });
        employeeList.Add(new Employee() { EmployeeId = Guid.NewGuid(), EmployeeName = "Benson" });
        employeeList.Add(new Employee() { EmployeeId = Guid.NewGuid(), EmployeeName = "Cooker" });
        employeeList.Add(new Employee() { EmployeeId = Guid.NewGuid(), EmployeeName = "Denny" });
        employeeList.Add(new Employee() { EmployeeId = Guid.NewGuid(), EmployeeName = "Ellis" });

        //Create ObservableCollection object
        employees = new ObservableCollection<Employee>(employeeList);

        //Create 2 managers and make 
        Manager manager1 = new Manager()
        {
            ManagerId = Guid.NewGuid(),
            ManagerName = "Frank",
            Employees = new List<Employee>(employeeList)
        };

        Manager manager2 = new Manager()
        {
            ManagerId = Guid.NewGuid(),
            ManagerName = "Green",
            Employees = new List<Employee>(employeeList)
        };

        managerList.Add(manager1);
        managerList.Add(manager2);

        managers = new ObservableCollection<Manager>(managerList);

    }

    private ObservableCollection<Manager> managers;
    public ObservableCollection<Manager> Managers
    {
        get
        {
            return managers;
        }
        set
        {
            managers = value;

            OnPropertyChanged(new PropertyChangedEventArgs("Managers"));
        }
    }

    private ObservableCollection<Employee> employees;
    public ObservableCollection<Employee> Employees
    {
        get
        {
            return employees;
        }
        set
        {
            employees = value;

            OnPropertyChanged(new PropertyChangedEventArgs("Employees"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, e);
        }
    }
}

XAML

    <Window x:Class="BindingTests.TreeWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:m="clr-namespace:BindingTests.Models"
        Title="TreeWindow" Height="300" Width="300">
    <Window.Resources>
        <HierarchicalDataTemplate ItemsSource="{Binding Path=Employees}" DataType="{x:Type m:Manager}">
            <TextBlock Text="{Binding Path=ManagerName}"/>
        </HierarchicalDataTemplate>
        <HierarchicalDataTemplate DataType="{x:Type m:Employee}">
            <TextBlock Text="{Binding Path=EmployeeName}"/>
        </HierarchicalDataTemplate>
    </Window.Resources>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <TreeView ItemsSource="{Binding Managers, Mode=OneWay, NotifyOnSourceUpdated=True}" Grid.Column="0">
            <TreeView.Resources>
                <Style TargetType="Label">
                    <Setter Property="Margin" Value="0"/>
                    <Setter Property="Padding" Value="0"/>
                </Style>
                <Style TargetType="TreeViewItem">
                    <Setter Property="Margin" Value="0"/>
                    <Setter Property="Padding" Value="0"/>
                </Style>
            </TreeView.Resources>
        </TreeView>
        <StackPanel Grid.Column="1">
            <Button Content="Click me to add a manager" Click="Button_Click" Height="30"/>
        </StackPanel>
    </Grid>
</Window>

XAML.CS

    namespace BindingTests
    {
    /// <summary>
    /// Interaction logic for TreeWindow.xaml
    /// </summary>
    public partial class TreeWindow : Window
    {
        public TreeWindow()
        {
            InitializeComponent();

            this.DataContext = new TreeWindowViewModel();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            TreeWindowViewModel ds = this.DataContext as TreeWindowViewModel;
            if (ds != null)
            {
                ds.Managers.Add(new Manager() { ManagerId = Guid.NewGuid(), ManagerName = "New Manager" });
                ds.Employees.Add(new Employee() { EmployeeId = Guid.NewGuid(), EmployeeName = "New Employee"});
            }
        }
    }
}

Initial UI

enter image description here

Upvotes: 1

Views: 295

Answers (1)

Kcvin
Kcvin

Reputation: 5163

The main reason you're not getting expected results is because you're adding the Employee to public ObservableCollection<Employee> Employees, and no where on the UI is that bound. What you're really seeing your initial list of Managers bound to the TreeView. The <HierarchicalDataTemplate ItemsSource="{Binding Path=Employees}" ... is actually looking at Manager.Employees, so in your button click, if you did:

if (ds != null)
{
    ds.Managers.Add(new Manager() { ManagerId = Guid.NewGuid(), ManagerName = "New Manager" });
    ds.Managers[0].Employees.Add(new Employee() { EmployeeId = Guid.NewGuid(), EmployeeName = "New Employee"});
}

then you would see the "New Employee" added under manager "Frank." So I think your code is okay, its just how you add it that is causing problems.

Your next question will probably be, "Well how am I supposed to know who to add an employee to?" and that's where you will want to create a public object SelectedItem in your TreeWindowViewModel and bind that to a dependency property that you could add to TreeView, since TreeView's SelectedItem is a read-only non-DP. You would then be able to call something like:

if(ds.SelectedItem is Manager)
{
    (ds.SelectedItem as Manager).Employees.Add( ... );
}

but now we are getting out of scope, and might raise a new SO question in the future for you.

Upvotes: 1

Related Questions