question

abijithgowda-5277 avatar image
abijithgowda-5277 asked ·

Tree View Checkbox issue

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-wpf
checkbox-issue.txt (2.2 KiB)
1 comment
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

It's kinda hard to follow your XAML without knowing how the various models relate to each other. My GUESS is that the HierarchicalDataTemplate.ItemTemplate> within the other HierarchicalDataTemplate.ItemTemplate> is where the problem lies. Try writing it as a simple DataTemplate.

0 Votes 0 · ·
PeterFleischer-3316 avatar image
PeterFleischer-3316 answered ·

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



4 comments Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Peter,
I made most of the changes but the event
' AddHandler h.PropertyChanged, AddressOf hg.h_PropertyChanged '

It wouldnt allow me to add it threw errors.
I have attached the Model class code that i have with me. Can you please check and let me know as to what might be wrong with it.

In the attached code, i get data from a web server in json format which i try to convert to my classes, namely RootNameList, HostGroup and HostName.7847-modelclass.txt


0 Votes 0 · ·
modelclass.txt (1.6 KiB)

Hi Peter,
I tried by adding the event handler, but it did not work. I need the tree view parent to go to indeterminate state if any of the child under that parent is unchecked.
Is there any other way by which this can be achieved.
Please let me know.
Thank you in advance!!

0 Votes 0 · ·

Hi, if you want inform parent (HostGroup) when changed child (HostName) you must subcribe to event PropertyChanged. If you load data from json you can insert second step after loading like this:

  Private Sub SetHandler()
    For Each rnl In col ' collection of RootNameList
      For Each hg In rnl.HostGroupName ' get each HostGroup
        hg.SetHandler() ' call method in Hostgroup to subscribe PropertyChanged event
      Next
    Next
  End Sub

And in HostGroup class

 Public Class HostGroup
  ...
  Public Property Host As New List(Of HostName)
    
  Public Sub SetHandler()
    For Each hn In Host
      AddHandler hn.PropertyChanged, AddressOf h_PropertyChanged
    Next
  End Sub
 ...


0 Votes 0 · ·

Astounding!!
That worked out perfectly Peter.
Thanks a ton for helping me in getting this done!!

0 Votes 0 · ·
PeterFleischer-3316 avatar image
PeterFleischer-3316 answered ·

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


1 comment Share
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Hi Peter,
This worked out perfectly for me!! Thank you!!
Is it possible to extend the above answer to a tristate checkbox. The behavior would be if any of the child under the parent is unchecked, the parent node is to go indeterminate state, i.e, null.

0 Votes 0 · ·