WPF GetTemplateChild get Nullrefrence exception

260 views Asked by At

Hi I'm building a control I need to access checkbox in the code behind But I get the null error

this is my control

<Style TargetType="TreeViewItem" x:Key="CheckTreeViewItem" >
        <Setter Property="IsExpanded" Value="{Binding Path=IsExpanded,Mode=TwoWay}"/>
    </Style>

    <Style TargetType="local:CheckTreeView">
        <Setter Property="ItemContainerStyle" Value="{StaticResource CheckTreeViewItem}"/>
        <Setter Property="ItemTemplate">
            <Setter.Value>
                <HierarchicalDataTemplate DataType="{x:Type local:CheckTreeSource}" ItemsSource="{Binding Children}">
                    <CheckBox x:Name="PART_CheckBox" Margin="1" IsChecked="{Binding IsChecked}">
                        <TextBlock Text="{Binding Text}"/>
                    </CheckBox>
                </HierarchicalDataTemplate>
            </Setter.Value>
        </Setter>
    </Style>

And this is how I access control

[TemplatePart(Name = CheckBox_Key, Type = typeof(CheckBox))]

    public partial class CheckTreeView : TreeView
    {
        private const string CheckBox_Key = "PART_CheckBox";
        CheckBox checkBox;

        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            checkBox = GetTemplateChild(CheckBox_Key) as CheckBox;
            checkBox.Click += CheckBox_Click;
        }

        private void CheckBox_Click(object sender, RoutedEventArgs e)
        {

        }
    }

When I use the following code I get no null error but no control in runtime

static CheckTreeView()
            {
                DefaultStyleKeyProperty.OverrideMetadata(typeof(CheckTreeView),
                       new FrameworkPropertyMetadata(typeof(CheckTreeView)));
            }
2

There are 2 answers

0
Igor Buchelnikov On BEST ANSWER

To subscribe to Click event of each CheckBox:

    public partial class CheckTreeView : TreeView
    {
        public CheckTreeView()
        {
            InitializeComponent();
            processNode(this);
        }


        void processNode(ItemsControl node)
        {
            node.ItemContainerGenerator.StatusChanged += (sender, args) =>
            {
                ItemContainerGenerator itemContainerGenerator = ((ItemContainerGenerator)sender);
                if (itemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
                {
                    for (int i = 0; i < itemContainerGenerator.Items.Count; i++)
                    {
                        TreeViewItem treeViewItem =
                            (TreeViewItem) itemContainerGenerator.ContainerFromIndex(i);

                        treeViewItem.Loaded += (o, eventArgs) =>
                        {
                            CheckBox checkBox = FindVisualChild<CheckBox>(treeViewItem);
                            checkBox.Click += CheckBox_Click;
                        };

                        processNode(treeViewItem);
                    }
                }
            };
        }

        public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
        {
            if (obj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    var child = VisualTreeHelper.GetChild(obj, i);
                    if (child is T)
                    {
                        return (T)child;
                    }

                    T childItem = FindVisualChild<T>(child);
                    if (childItem != null) return childItem;
                }
            }
            return null;
        }

        private void CheckBox_Click(object sender, RoutedEventArgs e)
        {

        }
    }

Note that if you use ObservableCollection as items source, you should process changes in that collection: subscribe to events of added items, unsubscribe from events of removed items.

0
ASh On

CheckBox_Key element is not a part of CheckTreeView.ControlTemplate. It will be a part in tree view nodes, multiple times. GetTemplateChild won't find it

I would say that intead of CheckBox in ItemTemplate for CheckTreeView, you need to change TreeViewItem.Template and add CheckBox there. It won't help with GetTemplateChild, but is is more logical approach to build reusable TreeView with chechboxes.

Here is an example. Check/Uncheck operations can be handled by specail Command in CheckTreeView class:

public class CheckTreeView: TreeView
{
    public CheckTreeView()
    {
        CheckCommand = new RelayCommand<object>(o => MessageBox.Show(o?.ToString()));
    }
    public ICommand CheckCommand
    {
        get { return (ICommand)GetValue(CheckCommandProperty); }
        set { SetValue(CheckCommandProperty, value); }
    }

    public static readonly DependencyProperty CheckCommandProperty =
        DependencyProperty.Register("CheckCommand", typeof(ICommand), typeof(CheckTreeView), new PropertyMetadata(null));
}

relevant part of TreeViewItem template (use Edit template feature in WPF designer to get full template):

<Style TargetType="{x:Type TreeViewItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type TreeViewItem}">
                <Grid>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition MinWidth="19" Width="Auto"/>
                        <ColumnDefinition Width="Auto"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                    <Grid.RowDefinitions>
                        <RowDefinition Height="Auto"/>
                        <RowDefinition/>
                    </Grid.RowDefinitions>
                    <ToggleButton x:Name="Expander" ClickMode="Press" IsChecked="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Style="{StaticResource ExpandCollapseToggleStyle}"/>

                    <Border x:Name="Bd" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Grid.Column="1" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
                        <StackPanel Orientation="Horizontal">
                            <CheckBox Command="{Binding CheckCommand, RelativeSource={RelativeSource AncestorType={x:Type local:CheckTreeView}}}" 
                                      CommandParameter="{Binding RelativeSource={RelativeSource Self}}"
                                      Margin="0,0,4,0"/>
                            <ContentPresenter x:Name="PART_Header" ContentSource="Header" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </StackPanel>
                    </Border>

                    <ItemsPresenter x:Name="ItemsHost" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1"/>
                </Grid>
            </ControlTemplate/>
        </Setter.Value>
    </Setter>
</Style>