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