Tree View Checkbox issue

abijith gowda 61 Reputation points
2020-04-30T19:22:11.69+00:00

Hi All,
I am working on a WPF C# application which has a tree view. The tree view is populated by a set of model classes. Each element on the tree view except for the root node has a checkbox. The issue now is that when i check a checkbox, it is selecting all the checkboxes under the same parent level.
Instead i would want it to select all the child nodes under the parent node.

For instance:
RootNode
Parent1 Node
Child1
Child2
Parent2 Node
Child3
Child4

and so on.
The current behavior is such that if i check the checkbox beside Parent1 Node, it is selecting Parent2 Node as well and no child elements are selected.
Instead i would want it to check the checkboxes of Child1 and Child2 if i check the Parent1 Node.

Can anyone please help me on this??7894-checkbox-issue.txt
Thank you in advance!!

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,671 questions
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-05-01T12:58:18.797+00:00

    Hi, at first I recommend to change the XAML an then include additional logic in ViewModel. Try demo.

    XAML:

    <Window x:Class="Window016"
            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:WpfApp1.WpfApp016"
            xmlns:mod="clr-namespace:WpfApp1.WpfApp016"
            mc:Ignorable="d"
            Title="Window016" Height="450" Width="800">
      <Window.Resources>
         <local:ViewModel x:Key="vm"/>
       <Style x:Key="HighlightingTextStyle" TargetType="Label">
          <Setter Property="FontStyle" Value="Italic"/>
        </Style>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <TreeView ItemsSource="{Binding TreeViewDataCollection}">
          <TreeView.Resources>
            <HierarchicalDataTemplate ItemsSource="{Binding HostGroupName}" DataType="{x:Type mod:RootNameList}">
              <StackPanel Orientation="Horizontal">
                <Label Style="{StaticResource HighlightingTextStyle}" Content="{Binding RootSubnet}"/>
              </StackPanel>
              </HierarchicalDataTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding Host}" DataType="{x:Type mod:HostGroup}">
              <StackPanel Orientation="Horizontal">
                <CheckBox IsChecked="{Binding IsParentChecked}" IsThreeState="True"/>
                <Label Style="{StaticResource HighlightingTextStyle}" Content="{Binding GroupName}" Tag="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
              </StackPanel>
              </HierarchicalDataTemplate>
            <HierarchicalDataTemplate DataType="{x:Type mod:HostName}">
              <StackPanel Orientation="Horizontal">
                <CheckBox IsChecked="{Binding IsChildChecked}"/>
                <Label Style="{StaticResource HighlightingTextStyle}" Content="{Binding Host}" Tag="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
              </StackPanel>
            </HierarchicalDataTemplate>
          </TreeView.Resources>
        </TreeView>
      </Grid>
    </Window>
    

    Code:

    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices
    
    Namespace WpfApp016
    
      Public Class ViewModel
    
        Public Sub New()
          For i = 1 To 10
            Dim rnl As New RootNameList With {.RootSubnet = $"RootSubnet{i}"}
            For k = 1 To 10
              Dim hg As New HostGroup With {.GroupName = $"GroupName {k + i * 10}"}
              rnl.HostGroupName.Add(hg)
              For l = 1 To 10
                Dim h As New HostName() With {.Host = $"Host {l}"}
                AddHandler h.PropertyChanged, AddressOf hg.h_PropertyChanged
                hg.Host.Add(h)
              Next
            Next
            col.Add(rnl)
          Next
          cvs.Source = col
        End Sub
    
        Private cvs As New CollectionViewSource
        Private col As New AsyncObservableCollection(Of RootNameList)
        Public ReadOnly Property TreeViewDataCollection As ICollectionView
          Get
            Return cvs.View
          End Get
        End Property
      End Class
    
      Public Class RootNameList
        Public Property RootSubnet As String
        Public Property HostGroupName As New List(Of HostGroup)
      End Class
    
      Public Class HostGroup
        Implements INotifyPropertyChanged
    
        Private _IsParentChecked As Boolean? = False
        Public Property IsParentChecked As Boolean?
          Get
            Return Me._IsParentChecked
          End Get
          Set(value As Boolean?)
            If Not value.HasValue Then value = False
            Me._IsParentChecked = value
            For Each h In Host
              h.IsChildChecked = value
            Next
            OnPropertyChanged()
          End Set
        End Property
        Public Property GroupName As String
        Public Property Host As New List(Of HostName)
    
        Friend Sub h_PropertyChanged(sender As Object, e As PropertyChangedEventArgs)
          Dim iFalse As Integer = 0
          Dim iTrue As Integer = 0
          For Each h In Host
            If h.IsChildChecked Then iTrue += 1 Else iFalse += 1
          Next
          If iTrue > 0 And iFalse = 0 Then Me._IsParentChecked = True
          If iTrue = 0 And iFalse > 0 Then Me._IsParentChecked = False
          If iTrue > 0 And iFalse > 0 Then Me._IsParentChecked = Nothing
          OnPropertyChanged(NameOf(IsParentChecked))
        End Sub
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
        Private Sub OnPropertyChanged(<CallerMemberName> Optional propName As String = "")
          RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
        End Sub
      End Class
    
      Public Class HostName
        Implements INotifyPropertyChanged
    
        Private _IsChildChecked As Boolean? = False
        Public Property IsChildChecked As Boolean?
          Get
            Return Me._IsChildChecked
          End Get
          Set(value As Boolean?)
            Me._IsChildChecked = value
            OnPropertyChanged()
          End Set
        End Property
        Public Property Host As String
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
        Private Sub OnPropertyChanged(<CallerMemberName> Optional propName As String = "")
          RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
        End Sub
      End Class
    
    End Namespace
    

1 additional answer

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-04-30T20:20:27.28+00:00

    Hi, try following demo:

    <Window x:Class="Window013"
            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:WpfApp1.WpfApp013"
            xmlns:mod="clr-namespace:WpfApp1.WpfApp013"
            mc:Ignorable="d"
            Title="Window013" Height="450" Width="800">
      <Window.Resources>
         <local:ViewModel x:Key="vm"/>
       <Style x:Key="HighlightingTextStyle" TargetType="Label">
          <Setter Property="FontStyle" Value="Italic"/>
        </Style>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <TreeView ItemsSource="{Binding TreeViewDataCollection}">
          <TreeView.ItemTemplate>
            <HierarchicalDataTemplate ItemsSource="{Binding HostGroupName}" DataType="{x:Type mod:RootNameList}">
              <StackPanel Orientation="Horizontal">
                <Label Style="{StaticResource HighlightingTextStyle}" Content="{Binding RootSubnet}"/>
              </StackPanel>
              <HierarchicalDataTemplate.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Host}" DataType="{x:Type mod:HostGroup}">
                  <StackPanel Orientation="Horizontal">
                    <CheckBox IsChecked="{Binding IsParentChecked}" IsThreeState="True"/>
                    <Label Style="{StaticResource HighlightingTextStyle}" Content="{Binding GroupName}" Tag="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
                  </StackPanel>
                  <HierarchicalDataTemplate.ItemTemplate>
                    <DataTemplate DataType="{x:Type mod:HostName}">
                      <StackPanel Orientation="Horizontal">
                        <CheckBox IsChecked="{Binding IsChildChecked}"/>
                        <Label Style="{StaticResource HighlightingTextStyle}" Content="{Binding Host}" Tag="{Binding RelativeSource={RelativeSource AncestorType=Window}}"/>
                      </StackPanel>
                    </DataTemplate>
                  </HierarchicalDataTemplate.ItemTemplate>
                </HierarchicalDataTemplate>
              </HierarchicalDataTemplate.ItemTemplate>
            </HierarchicalDataTemplate>
          </TreeView.ItemTemplate>
        </TreeView>
      </Grid>
    </Window>
    

    And classes:

    Imports System.ComponentModel
    Imports System.Runtime.CompilerServices
    
    Namespace WpfApp013
    
      Public Class ViewModel
    
        Public Sub New()
          For i = 1 To 10
            Dim rnl As New RootNameList With {.RootSubnet = $"RootSubnet{i}"}
            For k = 1 To 10
              Dim hg As New HostGroup With {.GroupName = $"GroupName {k + i * 10}"}
              rnl.HostGroupName.Add(hg)
              For l = 1 To 10
                hg.Host.Add(New HostName() With {.Host = $"Host {l}"})
              Next
            Next
            col.Add(rnl)
          Next
          cvs.Source = col
        End Sub
    
        Private cvs As New CollectionViewSource
        Private col As New AsyncObservableCollection(Of RootNameList)
        Public ReadOnly Property TreeViewDataCollection As ICollectionView
          Get
            Return cvs.View
          End Get
        End Property
      End Class
    
      Public Class RootNameList
        Public Property RootSubnet As String
        Public Property HostGroupName As New List(Of HostGroup)
      End Class
    
      Public Class HostGroup
        Private _IsParentChecked As Boolean
        Public Property IsParentChecked As Boolean
          Get
            Return Me._IsParentChecked
          End Get
          Set(value As Boolean)
            Me._IsParentChecked = value
            If value Then
              For Each h In Host
                h.IsChildChecked = True
              Next
            End If
          End Set
        End Property
        Public Property GroupName As String
        Public Property Host As New List(Of HostName)
      End Class
    
      Public Class HostName
        Implements INotifyPropertyChanged
    
        Private _IsChildChecked As Boolean
        Public Property IsChildChecked As Boolean
          Get
            Return Me._IsChildChecked
          End Get
          Set(value As Boolean)
            Me._IsChildChecked = value
            OnPropertyChanged()
          End Set
        End Property
        Public Property Host As String
    
        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
        Private Sub OnPropertyChanged(<CallerMemberName> Optional propName As String = "")
          RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propName))
        End Sub
      End Class
    
    End Namespace