正在通过正确的方式遍历VisualTree从模板加载控件



我有一个自定义控件,它定义了模板,模板包含以下代码:

<FlipView Grid.Row="3"
          Grid.ColumnSpan="2" x:Name="FlipView1" BorderBrush="Black"
          ItemsSource="{Binding ItemsCollection, RelativeSource={RelativeSource TemplatedParent}}">
            <FlipView.ItemTemplate>
                    <DataTemplate>
                          <ScrollViewer>
                                <Grid>
                                    <local:UserControlA x:Name="PART_UserControlA"/>
                                    <Grid>
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="100" />
                                            <ColumnDefinition />
                                        </Grid.ColumnDefinitions>
                                        <local:UserControlB Grid.Column="1"
                                                            View="{Binding View}"
                                                            x:Name="PART_UserControlB"
                                                            ItemsSource="{Binding ItemsSourcePropertyOfAnItemInItemsCollection}"
                                                            ItemTemplate="{Binding TemplatePropertyOfAnItemInItemsCollection}" />
                                    </Grid>
                                </Grid>
                          </ScrollViewer>
                    </DataTemplate>
            </FlipView.ItemTemplate>
</FlipView>

在我的自定义控件的代码隐藏中,我有这样的代码来加载模板中的控件(我不得不这样做,因为GetTemplateChild返回null,因为PART_UserControlB再次是FlipView模板的一部分,并且GetTemplateChild不会递归地获取模板化的子控件):

protected override void OnApplyTemplate()
{
    FlipView flipView = GetTemplateChild("FlipView1") as FlipView;
            DataTemplate dt = flipView.ItemTemplate;
            DependencyObject dio1 = dt.LoadContent();
            DependencyObject dio = (dio1 as ScrollViewer).Content as DependencyObject;
foreach (var item in FindVisualChildren<UserControlB>(dio))
            {
                if (item.Name == "PART_UserControlB")
                {
                    UserControlB controlB = item;
                    controlB.ApplyTemplate();
                    controlB.PointerPressed += OnPointerPressed;
                }
            }
}
public IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        yield return (T)child;
                    }
                    foreach (T childOfChild in FindVisualChildren<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }

问题是,当我点击UserControlB中的一个项目时,它不会触发该控件的OnPointerPressed事件。这就像我在后面的代码中没有得到相同的UserControlB实例。

当您检索Template Child(像您的零件一样)时,您应该使用FrameworkElement.GetTemplateChild 来检索它

在您的情况下:

UserControlB controlB = GetTemplateChild("PART_UserControlB") as UserControlB;

因此,要回答标题中的问题:不,这不是正确的做法。

此外,我认为您不应该在这里调用ApplyTemplate()。

我在这里看到的另一件事是在你的模板中没有ElementName或RelativeSource的绑定:这真是一件糟糕的事情。您不能保证您的自定义控件DataContext在运行时是什么。这将导致意想不到的行为。

Template中的所有绑定都应该以Template父级或Template内的可视控件作为目标,但不应该使用DataContext。

编辑

好的,所以我再次阅读了您的代码,您的PART_UserControlB在DataTemplate中,在ItemsControl的ItemTemplate中,这意味着对于ItemsControl中的每个项目,您将有一个名为PART_UserControl B的UserControlB。您注意到的行为是正常的:您找到了第一个名为PART_UserControlB的控件,并在其中一个事件上放置了事件处理程序。但是其他的UserControlB呢?

你在这里并没有真正使用Template子项,你指的是根据ItemsControl内容可能存在或不存在的东西。这些不是自定义控件的一部分,因此不应命名为part_xxx。您可以使用UserControlB中的命令DP,该命令DP将在引发事件时执行:

//in your UserControlB.cs
public event EventHandler<YourEventArgs> PointerPressed;
private void OnPointerPressed() {
    YourEventArgs arg = new YourEventArgs();
    if (PointerPressed != null) {
        PointerPressed(this, arg);
    }
    if (PointerPressedCommand != null &&    PointerPressedCommand.CanExecute(PointerPressedCommandParameter)) {
        PointerPressedCommand.Execute(PointerPressedCommandParameter);
    }
}
#region PointerPressedCommand 
public ICommand PointerPressedCommand
{
    get { return (ICommand)GetValue(PointerPressedCommandProperty); }
    set { SetValue(PointerPressedCommandProperty, value); }
}
private readonly static FrameworkPropertyMetadata PointerPressedCommandMetadata = new FrameworkPropertyMetadata {
    DefaultValue = null,
    DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
};
public static readonly DependencyProperty PointerPressedCommandProperty = 
    DependencyProperty.Register("PointerPressedCommand", typeof(ICommand), typeof(UserControlB), PointerPressedCommandMetadata);
#endregion

然后将命令绑定到TemplatedPrent中的命令。

//in your Template
<local:UserControlB Grid.Column="1"
    View="{Binding View}"
    x:Name="PART_UserControlB"
    ItemsSource="{Binding ItemsSourcePropertyOfAnItemInItemsCollection}"
    ItemTemplate="{Binding TemplatePropertyOfAnItemInItemsCollection}" 
    PointerPressedCommand="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=MyCommand}"/>

使用事件处理程序可能是一种选择,但这将是一场噩梦:您必须监视ItemsControl的ItemsSource以了解更改,通过可视化树并添加处理程序。这将是一种快速而有点肮脏的方式来实现你想要实现的目标。

最新更新