'WPF Dependency Property Not Binding / PropertyChangedCallback Not Getting Called

My project consists of a Treeview with a Datagrid as a child. The parent in the Treeview has a checkbox, and every row in the datagrid also has a check box. My goal is to bind the checkboxes together, so that when the parent checkbox is checked, all of the datagrid rows are checked as well. When the datagrid rows are checked I would like the parent checkbox to be updated based of the datagrid row checked values.

Parent checkbox IsChecked = true when all of the datagrid row checkboxes are true, null if only some are, and false if no rows are checked.

The problem I'm facing is that my dependency properties aren't updating and the PropertyChangedCallback is not getting called.

This is my Checkbox_Helper class in which the IsChecked properties of both the parent and the datagrid row checkboxes are bound to.

public class CheckBox_Helper : DependencyObject
    {
        public static readonly DependencyProperty IsCheckedProperty =
           DependencyProperty.RegisterAttached("IsChecked", typeof(bool?), typeof(CheckBox_Helper),
           new PropertyMetadata(false, new PropertyChangedCallback(OnIsCheckedPropertyChanged)));

        private static void OnIsCheckedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is Parent && ((bool?)e.NewValue).HasValue)
            {
                foreach (Child s in (d as Parent).SheetList[0].SheetChildList)
                {
                    if (s.IsSizeCompatible)
                    {
                        SetIsChecked(s, (bool?)e.NewValue);
                    }                    
                }
            }

            if (d is Child)
            {
                AddToCheckedElements(d as Child);
                int _checked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == true).Count();
                int _unchecked = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Where(x => GetIsChecked(x) == false).Count();
                int _total = (d.GetValue(ParentProperty) as Parent).SheetList[0].SheetChildList.Count();

                if ((d as Child).MegaParent.SelectedRowList != null && (d as Child).MegaParent.SelectedRowList.Count > 0)
                {
                    (d as Child).MegaParent.SelectedRowList.Where(i => GetIsChecked(i) != GetIsChecked(d as Child)).ToList().ForEach(i => CheckBox_Helper.SetIsChecked(i, GetIsChecked(d as Child)));
                }

                if (_checked == 0)
                {
                    (d.GetValue(ParentProperty) as Parent).HasCheckedItems = false;
                }
                else
                {
                    (d.GetValue(ParentProperty) as Parent).HasCheckedItems = true;
                }

                (d.GetValue(ParentProperty) as Parent).CheckedRatio = $"{_checked}/{_total}";

                if (_unchecked > 0 && _checked > 0)
                {
                    CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, null);
                    return;
                }

                if (_checked > 0)
                {
                    CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, true);
                    return;
                }

                CheckBox_Helper.SetIsChecked((d as Child).GetValue(CheckBox_Helper.ParentProperty) as DependencyObject, false);
            }
        }

        public static readonly DependencyProperty ParentProperty = DependencyProperty.RegisterAttached("Parent", typeof(Parent), typeof(CheckBox_Helper));

        public static void SetIsChecked(DependencyObject element, bool? IsChecked)
        {
            element.SetValue(CheckBox_Helper.IsCheckedProperty, IsChecked);
        }

        public static bool? GetIsChecked(DependencyObject element)
        {
            return (bool?)element.GetValue(CheckBox_Helper.IsCheckedProperty);
        }

        // Set and Get Parent are unused since they are hardcoded categories
        public static void SetParent(DependencyObject element, object Parent)
        {
            element.SetValue(CheckBox_Helper.ParentProperty, Parent);
        }

        public static object GetParent(DependencyObject element)
        {
            return (object)element.GetValue(CheckBox_Helper.ParentProperty);
        }

        private static void AddToCheckedElements(Child child)
        {
            ObservableCollection<Child> tempList = child.MegaParent.CheckedElements;
            if (!child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == true)
            {
                tempList.Add(child);
                child.MegaParent.CheckedElements = tempList;
            }
            else if (child.MegaParent.CheckedElements.Contains(child) && GetIsChecked(child) == false)
            {
                tempList.Remove(child);
                child.MegaParent.CheckedElements = tempList;
            }

        }
    }

This is the code for the Parent class:

public class Parent : DependencyObject
    {
        public string Name { get; set; }
        public ObservableCollection<ChildList> SheetList { get; set; }

        public static readonly DependencyProperty HasCheckedItemsProperty =
        DependencyProperty.Register("HasCheckedItems", typeof(bool), typeof(Parent), new UIPropertyMetadata(false));

        public bool HasCheckedItems
        {
            get { return (bool)GetValue(HasCheckedItemsProperty); }
            set { SetValue(HasCheckedItemsProperty, value); }
        }

        public static readonly DependencyProperty CheckedRatioProperty =
        DependencyProperty.Register("CheckedRatio", typeof(string), typeof(Parent), new UIPropertyMetadata($"0/0"));
        public string CheckedRatio
        {
            get { return (string)GetValue(CheckedRatioProperty); }
            set { SetValue(CheckedRatioProperty, value); }
        }

        public Parent(string name)
        {
            Name = name;
            SheetList = new ObservableCollection<ChildList>();
        }
    }

This is the code for the Child class: (Note: The MegaParent property is the viewmodel)

public class Child : DependencyObject
    {
        public ViewSheet Sheet { get; set; }
        public string Name { get; set; }
        public string Number { get; set; }
        private string _paperSize = "";
        public string PaperSize
        {
            get { return _paperSize; }
            set { _paperSize = value; }
        }

        public static readonly DependencyProperty IsSizeCompatibleProperty =
        DependencyProperty.Register("IsSizeCompatible", typeof(bool), typeof(Child), new UIPropertyMetadata(true));
        public bool IsSizeCompatible
        {
            get { return (bool)GetValue(IsSizeCompatibleProperty); }
            set { SetValue(IsSizeCompatibleProperty, value); }
        }
        public PrintSheets_ViewModel MegaParent { get; set; }

        public static readonly DependencyProperty IsSelectedProperty =
        DependencyProperty.Register("IsSelected", typeof(bool), typeof(Child), new UIPropertyMetadata(false));
        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); AddToSelectedList(); }
        }
        private void AddToSelectedList()
        {
            ObservableCollection<Child> tempList = MegaParent.SelectedRowList;
            if (!MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == true)
            {
                tempList.Add(this);
                MegaParent.SelectedRowList = tempList;
            }
            else if (MegaParent.SelectedRowList.Contains(this) && (bool)GetValue(IsSelectedProperty) == false)
            {
                tempList.Remove(this);
                MegaParent.SelectedRowList = tempList;
            }
        }

        public Child(string name, string number, ViewSheet sheet, string paperSize, bool isSizeCompatible, PrintSheets_ViewModel megaParent)
        {
            Name = name;
            Number = number;
            Sheet = sheet;
            PaperSize = paperSize;
            IsSizeCompatible = isSizeCompatible;
            MegaParent = megaParent;
        }
    }

This is the XAML code for the Treeview:

 <Border Grid.Row="1" Grid.RowSpan="4" Grid.Column="3" Grid.ColumnSpan="3" Margin="10,10,10,10"
                BorderBrush="Black" BorderThickness="1">
            <ScrollViewer x:Name="treeScroller" PreviewMouseWheel="treeScroller_PreviewMouseWheel" VerticalScrollBarVisibility="Auto">
                <TreeView  Background="LightGray" ItemsSource="{Binding tv_model.ParentItems}" x:Name="sheetTree"
                           ScrollViewer.CanContentScroll="False">
                    <TreeView.Resources>
                        <HierarchicalDataTemplate DataType="{x:Type m:Parent}" ItemsSource="{Binding SheetList}">
                            <Border BorderBrush="DarkGray" CornerRadius="5" Padding="5,0,0,0" Height="40"
                                    Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusFortyFiveConverter}}">
                                <Border.Style>
                                    <Style TargetType="Border">
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
                                                <Setter Property="Background" Value="#b5ffc0"/>
                                                <Setter Property="BorderThickness" Value="3"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
                                                <Setter Property="Background" Value="LightCyan"/>
                                                <Setter Property="BorderThickness" Value="2"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </Border.Style>
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="20"/>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="*"/>
                                        <ColumnDefinition Width="Auto"/>
                                        <ColumnDefinition Width="Auto"/>
                                    </Grid.ColumnDefinitions>
                                    <CheckBox Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center"
                                              IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay, 
                                              UpdateSourceTrigger=PropertyChanged}"
                                              Cursor="Hand"/>
                                    <Label Grid.Column="1" Content="{Binding Name}" VerticalAlignment="Center" HorizontalAlignment="Left" FontWeight="SemiBold"/>
                                    <Label Grid.Column="3" VerticalAlignment="Center" HorizontalContentAlignment="Right"
                                           FontWeight="Normal" FontSize="12"
                                           Content="Sheets Checked:"/>
                                    <Label Grid.Column="4" VerticalAlignment="Center" HorizontalAlignment="Right"                                           
                                           Content="{Binding CheckedRatio}">
                                        <Label.Style>
                                            <Style TargetType="Label">
                                                <Style.Triggers>
                                                    <DataTrigger Binding="{Binding HasCheckedItems}" Value="True">
                                                        <Setter Property="FontWeight" Value="Bold"/>
                                                        <Setter Property="FontSize" Value="14"/>
                                                    </DataTrigger>
                                                    <DataTrigger Binding="{Binding HasCheckedItems}" Value="False">
                                                        <Setter Property="FontWeight" Value="Normal"/>
                                                        <Setter Property="FontSize" Value="12"/>
                                                    </DataTrigger>
                                                </Style.Triggers>
                                            </Style>
                                        </Label.Style>
                                    </Label>
                                </Grid>
                            </Border>
                        </HierarchicalDataTemplate>
                        <DataTemplate DataType="{x:Type m:ChildList}">
                            <DataGrid x:Name="ChildDataGrid" ItemsSource="{Binding SheetChildList}"
                                      Width="{Binding Path=ActualWidth, ElementName=sheetTree, Converter={StaticResource minusEightyConverter}}"
                                      IsReadOnly="True" AutoGenerateColumns="False" SelectionMode="Extended" CanUserResizeColumns="True"
                                      EnableRowVirtualization="True" CanUserAddRows="False" CanUserDeleteRows="False" 
                                      CanUserResizeRows="True" CanUserReorderColumns="False" CanUserSortColumns="True"  >
                                <DataGrid.Resources>
                                    <Style TargetType="DataGrid">
                                        <EventSetter Event="LostFocus" Handler="OnLostFocus_DataGrid"/>
                                    </Style>
                                    <Style TargetType="DataGridRow">
                                        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
                                        <Style.Triggers>
                                            <DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="False">
                                                <Setter Property="IsEnabled" Value="False"/>
                                            </DataTrigger>
                                            <DataTrigger Binding="{Binding IsSizeCompatible, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Value="True">
                                                <Setter Property="IsEnabled" Value="True"/>
                                            </DataTrigger>
                                        </Style.Triggers>
                                    </Style>
                                </DataGrid.Resources>                                
                                <DataGrid.Columns>
                                    <DataGridTemplateColumn Width="20">
                                        <DataGridTemplateColumn.CellTemplate>
                                            <DataTemplate>
                                                <CheckBox Cursor="Hand" IsChecked="{Binding Path=(h:CheckBox_Helper.IsChecked), Mode=TwoWay, 
                                                    UpdateSourceTrigger=PropertyChanged}"/>
                                            </DataTemplate>
                                        </DataGridTemplateColumn.CellTemplate>
                                    </DataGridTemplateColumn>
                                    <DataGridTextColumn Header="Sheet Number" Binding="{Binding Number}" IsReadOnly="True" Width="Auto"/>
                                    <DataGridTextColumn Header="Name" Binding="{Binding Name}" IsReadOnly="True" Width="*"/>
                                    <DataGridTextColumn Header="Paper Size" Binding="{Binding PaperSize}" IsReadOnly="True" Width="Auto"/>
                                </DataGrid.Columns>
                            </DataGrid>
                        </DataTemplate>
                    </TreeView.Resources>
                    <TreeView.ItemContainerStyle>
                        <Style TargetType="{x:Type TreeViewItem}">
                            <Setter Property="Margin" Value="0,3,0,3"/>
                        </Style>
                    </TreeView.ItemContainerStyle>
                </TreeView>
            </ScrollViewer>
        </Border>

I had the checkboxes working before I added some of the extra methods to the CheckBox_Helper, but I need those methods in order for other controls on my UI to function properly.

I feel like it could be a DataContext issue. I've tried adding RelativeSource to the IsChecked binding with the AncestorType as Datagrid, and I've also tried it as Window.

Still no luck, any help would be greatly appreciated.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source