WPF GetTemplateChild 获取 Nullrefrence 异常

WPF GetTemplateChild get Nullrefrence exception

您好,我正在构建一个控件,我需要在后面的代码中访问复选框,但是我收到空错误

这是我的控件

<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>

这就是我访问控制的方式

[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)
        {

        }
    }

当我使用以下代码时,我没有收到空错误,但在运行时没有控制

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

CheckBox_Key 元素不是 CheckTreeView.ControlTemplate 的一部分。它将多次成为树视图节点的一部分。 GetTemplateChild 找不到它

我想说的是 CheckTreeView 的 ItemTemplate 中的 CheckBox intead,您需要更改 TreeViewItem.Template 并在那里添加 CheckBox。它对 GetTemplateChild 没有帮助,但它是使用复选框构建可重用 TreeView 的更合乎逻辑的方法。

这是一个例子。 Check/Uncheck 操作可以通过 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));
}

TreeViewItem 模板的相关部分(使用 WPF 设计器中的 Edit 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>

订阅各个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)
        {

        }
    }

请注意,如果您使用 ObservableCollection 作为项目源,您应该处理该集合中的更改:订阅添加项目的事件,取消订阅删除项目的事件。