通过遍历VisualTree从模板加载控件是否正确? [英] Is loading the controls from template by traversing through the VisualTree a right way?

查看:71
本文介绍了通过遍历VisualTree从模板加载控件是否正确?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a custom control which has it's template defined and the template contains below code:

<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不会递归地获取模板子):

In code behind of my custom control, I have this code to load the controls in the template (I had to do this trick since GetTemplateChild returns null because PART_UserControlB is again a part of the template of FlipView and GetTemplateChild does not recursively gets the templated child):

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实例.

Problem is that when I tap on an item in UserControlB, it does not trigger the OnPointerPressed event for that control. It is like I am not getting the same instance of the UserControlB in the code behind.

推荐答案

当您检索模板子项(如您的部件)时,应使用

When you retrieve a Template Child (like your part) you should retrieve it using FrameworkElement.GetTemplateChild

在您的情况下:

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

所以要回答标题中的问题:不,这不是正确的方法.

So to answer the question in the title: No it's not the right way to do it.

此外,我不认为您应该在此处调用ApplyTemplate().

Furthermore I don't think you should call ApplyTemplate() on it here.

我在这里看到的另一件事是您的模板中没有ElementName或RelativeSource的绑定:这确实是一件坏事.您不能保证自定义Control DataContext在运行时将是什么.这将导致意外行为.

Another thing I see here is bindings without ElementName or RelativeSource in your Template: This is a REALLY bad thing. You can't guarantee what your custom Control DataContext will be at runtime. This WILL lead to unexpected behavior.

您模板中的所有绑定都应以模板父对象或模板内部的可视控件为目标,但永远不要使用DataContext.

All bindings in your Template should either have the Template parent or a visual Control inside the Template as target but shouldn't ever use the DataContext.

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

Ok so I read your code again and your PART_UserControlB is inside a DataTemplate, inside ItemsControl's ItemTemplate, which means that for EACH item in your ItemsControl you'll have a UserControlB named PART_UserControlB. The behavior you noticed is normal: You're finding the first Control named PART_UserControlB and put an event handler on one of its event. But what about all other UserControlB?

在这里您并不是真正使用Template子级,而是根据ItemsControl内容指的是可能存在或不存在的事物.这些不是您的自定义控件的一部分,因此不应将其命名为PART_xxx.您可以使用的是UserControlB中的Command DP,该DP将在引发事件时执行:

You're not really using Template child here, you're referring to things that may or may not exist according to an ItemsControl content. Those aren't a part of your custom control and should therefore not be named PART_xxx. What you could use is a Command DP in your UserControlB that will be executed when the event is raised:

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

然后将命令绑定到TemplatedParent中的命令.

And then bind the Command to a Command in your TemplatedParent.

//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进行更改,通过可视化树并添加处理程序.这将是一种快速且有点肮脏的方式来实现您想要的目标.

Using event handler could be an option but this will be a nightmare: You'll have to watch your ItemsControl's ItemsSource for changes, get through the Visual Tree and add handlers. This would be a quick and a bit dirty way to do what you want to achieve.

这篇关于通过遍历VisualTree从模板加载控件是否正确?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆