Search code examples
c#wpfdata-bindingtreeview

WPF Dynamically Generated TreeView with Checkboxes Not Displaying


I am attempting to create a dynamically generated TreeView in a WPF project using MVVM. I have created a HierarchicalDataTemplate and a CheckableItem model that it will bind to. I have a CheckableItem type property on my ViewModel that I populate when the ViewModel is constructed. I then have the XAML use the template to create the TreeView, but nothing is displaying. I need the TreeView to contain Checkboxes and if I higher-level checkbox is checked, it should check all the lower-level ones. It should look something like this:

Example TreeView

I am not sure what is wrong that is causing it not to display.

ChekableItem class:

public class CheckableItem
    {
        public event PropertyChangedEventHandler PropertyChanged;
        private string _name;
        public string Name
        {
            get { return _name; }
            set 
            { 
                _name = value;
                OnPropertyChanged("Name");
            }
        }
        public ObservableCollection<CheckableItem> Children { get; set; }
        private Visibility _isChecked;
        public Visibility IsChecked
        {
            get { return _isChecked; }
            set
            {
                _isChecked = value;
                OnPropertyChanged("IsChecked");
                CheckChildren(value);               
            }
        }
        protected void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        private void CheckChildren(Visibility parentIsChecked)
        {
            foreach (CheckableItem child in Children)
            {
                child.IsChecked = parentIsChecked;
            }
        }

ViewModel:

    private CheckableItem miscellaneousImports;
    public CheckableItem MiscellaneousImports
    {
        get { return miscellaneousImports; }
        set
        {
            miscellaneousImports = value;
            OnPropertyChanged("MiscellaneousImports");
        }
    }
    private void LoadCheckableItems()
            {
                miscellaneousImports = new CheckableItem()
                {
                    Name = "Miscellaneous Imports"
                };
                miscellaneousImports.Children = new ObservableCollection<CheckableItem>();
                miscellaneousImports.Children.Add(new CheckableItem()
                {
                    Name = "GPO Import"
                });
            }
     protected void OnPropertyChanged(string propertyName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }

View XAML:

    <Window x:Class="CAVA_IAS.Views.CaImportView"
        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:models="clr-namespace:CAVA_IAS.Models"
        mc:Ignorable="d"
        Title="CaImportView" SizeToContent="WidthAndHeight">
    <Window.Resources>
        <HierarchicalDataTemplate DataType="{x:Type models:CheckableItem}" ItemsSource="{Binding 
            Children}">
            <StackPanel Orientation="Horizontal">
                <CheckBox IsChecked="{Binding IsChecked}"/>
                <TextBlock Text="{Binding Name}"/>
            </StackPanel>
        </HierarchicalDataTemplate>
    </Window.Resources>

    <StackPanel>
        <Label Content="Miscellaneous Imports" HorizontalAlignment="Center" />
        <ScrollViewer>
            <TreeView ItemsSource="{Binding MiscellaneousImports, Mode=TwoWay, 
                  UpdateSourceTrigger=PropertyChanged}" FontSize="10" Height="450"/>
        </ScrollViewer>
    </StackPanel>

Solution

  • Your problem is that the ItemsSource of the TreeView needs to be a list of some sort. Now you are binding it to a single CheckableItem.

    In your ViewModel, you should have something like this :

    private ObservableCollection<CheckableItem> miscellaneousImports;
    public ObservableCollection<CheckableItem> MiscellaneousImports
    {
        get { return miscellaneousImports; }
        set
        {
            miscellaneousImports = value;
            OnPropertyChanged("MiscellaneousImports");
        }
    }
    

    Or Bind to the Children property of CheckableItem in the Xaml, like so :

    <TreeView ItemsSource="{Binding MiscellaneousImports.Children, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="10" Height="450"/>