如何在XAML DataTemplate中访问控件?

30
我有一个flipview:
<FlipView x:Name="models_list" SelectionChanged="selectionChanged">
 <FlipView.ItemTemplate>
          <DataTemplate>
                <Grid x:Name="cv">
                        <Image x:Name="img1" Source = "{Binding ModelImage}" Stretch="Fill" Tag="{Binding ModelTag}"/>
                </Grid>
           </DataTemplate>
  </FlipView.ItemTemplate>

我想找到当前选定索引下的img1。在搜索过程中,我在这里的某篇帖子中发现了这个方法:

private DependencyObject FindChildControl<T>(DependencyObject control, string ctrlName)
    {
        int childNumber = VisualTreeHelper.GetChildrenCount(control);
        for (int i = 0; i < childNumber; i++)
        {
            DependencyObject child = VisualTreeHelper.GetChild(control, i);
            FrameworkElement fe = child as FrameworkElement;
            // Not a framework element or is null
            if (fe == null) return null;

            if (child is T && fe.Name== ctrlName)
            {
                // Found the control so return
                return child;
            }
            else
            {
                // Not found it - search children
                DependencyObject nextLevel = FindChildControl<T>(child, ctrlName);
                if (nextLevel != null)
                    return nextLevel;
            }
        }
        return null;
    }

它返回给我flipview的第一个索引处的图像,但我需要当前选择索引处的图像。 我尝试编辑此方法,但无法找到所需的控件。 有人可以帮我吗?


1
通常那是错误的方法。你具体想对那张图片做什么?请注意,FlipView会虚拟其子项,因此该图像可能甚至不存在。 - Filip Skakun
@FilipSkakun 我想获取该图像的屏幕坐标。我还有其他方法可以做到吗? - Tehreem
你需要屏幕坐标做什么? - Damir Arh
@DamirArh 我想把这张图片覆盖到另一张图片上,所以我需要知道这张图片的确切位置... - Tehreem
1
相关问题 http://stackoverflow.com/questions/27333169/how-to-get-all-the-textboxes-in-the-usercontrol-added-into-gridview/27434520#27434520 - Jerry Nixon
6个回答

57
您遇到的问题是DataTemplate正在重复并且内容由FlipView生成。因为Name会与已生成的前一个兄弟(或者将要生成的后一个兄弟)发生冲突,所以它没有被公开。
因此,要在DataTemplate中获取命名元素,您必须先获取生成的项,然后在该生成的项内搜索您想要的元素。请记住,在XAML中,逻辑树是通过名称访问事物的方式。生成的项不在逻辑树中。而是在可视树中(所有控件都在可视树中)。这意味着您必须在可视树中搜索要引用的控件,VisualTreeHelper可以帮助您实现。
那么,如何做呢?
我写了一篇文章来解决这个问题,因为这是一个经常出现的问题: http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html 但是解决方案的核心是一个递归方法,大致如下:
public void TestFirstName()
{
    foreach (var item in MyFlipView.Items)
    {
        var _Container = MyFlipView.ItemContainerGenerator
            .ContainerFromItem(item);
        var _Children = AllChildren(_Container);

        var _FirstName = _Children
            // only interested in TextBoxes
            .OfType<TextBox>()
            // only interested in FirstName
            .First(x => x.Name.Equals("FirstName"));

        // test & set color
        _FirstName.Background = 
            (string.IsNullOrWhiteSpace(_FirstName.Text))
            ? new SolidColorBrush(Colors.Red)
            : new SolidColorBrush(Colors.White);
    }
}

public List<Control> AllChildren(DependencyObject parent)
{
    var _List = new List<Control>();
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
        var _Child = VisualTreeHelper.GetChild(parent, i);
        if (_Child is Control)
            _List.Add(_Child as Control);
        _List.AddRange(AllChildren(_Child));
    }
    return _List;
}

关键问题在于这种方法会获取所有子控件,然后在子控件列表中搜索您想要的特定控件。明白了吗?

现在来回答你的问题!

因为您特别想要当前选定的项,所以您可以简单地将代码更新为:

if (MyFlipView.SelectedItem == null)
    return;
var _Container = MyFlipView.ItemContainerGenerator
    .ContainerFromItem(MyFlipView.SelectedItem);
// then the same as above...

Jerry - 看起来这只适用于有SelectedItem的情况。如果您想通过模板项之外的按钮单击来定位元素,该怎么办?请参见http://stackoverflow.com/questions/23645331/how-to-accessing-elements-in-xaml-datatemplate-listview-without-interacting-with - Rob
@JerryNixon-MSFT 我正在尝试在Windows Phone 8.1应用程序中使用你的代码,但它找不到VisualTreeHelperClass的引用。但是当我在对象浏览器中搜索时,可以在System.Windows.Media和Windows.UI.Xaml.Media中找到它。另外 OfType<TextBox>() 给出错误 Systems.Generic.Connections.List不包含 OfType() 的定义。我的FlipView结构 - user3263192
相关问题 http://stackoverflow.com/questions/27333169/how-to-get-all-the-textboxes-in-the-usercontrol-added-into-gridview/27434520#27434520 - Jerry Nixon
使用此代码(删除ItemContainerGenerator,因为它现在已过时),我在AllChildren的GetChildrenCount处收到“参数不正确”的错误。代码几乎相同,但我正在开发Windows 10而不是8.1。 - Arlo

13
也许更通用的方法可能是这样的:

private List<Control> AllChildren(DependencyObject parent)
{
    var _List = new List<Control>();
    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
       var _Child = VisualTreeHelper.GetChild(parent, i);
       if (_Child is Control)
       {
        _List.Add(_Child as Control);
       }
      _List.AddRange(AllChildren(_Child));
    }
    return _List;
 } 


private T FindControl<T> (DependencyObject parentContainer, string controlName)
{            
        var childControls = AllChildren(parentContainer);
        var control = childControls.OfType<Control>().Where(x => x.Name.Equals(controlName)).Cast<T>().First();           
        return control;
}

您可以这样调用FindControl:

var parentContainer = this.flipView.ItemContainerGenerator.ContainerFromItem(this.flipView.SelectedItem);
var myImage = FindControl<Image>(parentContainer, "img1");

//suppose you want to change the visibility
myImage.Visibility = Windows.UI.Xaml.Visibility.Collapsed;         

3
简单的解决方法是将DataTemplate的内容包装在UserControl中,这样您就可以访问ItemsControl项中的所有UI元素。我认为FlipView通常会虚拟化其项,因此即使您有100个项绑定到它,只有2-3个项实际上在UI中具有当前表示(其中1-2个项被隐藏),因此当您想要替换任何内容时,必须记住这一点,并且只有在将项加载到控件中时才实际进行更改。
如果您确实需要识别表示ItemsSource中项的项容器,则可以检查FlipView的ItemContainerGenerator属性及其ContainerFromItem()方法。

要获取项目的坐标,您可以使用WinRT XAML Toolkit中的GetBoundingRect()扩展方法。

总的来说,根据您的评论,最好的方法可能完全不同。如果您将FlipView绑定到源,则通常可以通过更改绑定源集合项的属性来控制显示或叠加的图像。


我通过将FlipView中的所有图像放入列表中,并遍历该列表来查找所需的图像来解决了这个问题。是的,你说得对,我一次只能访问FlipView的3个项目,但由于我需要当前聚焦的项目,它始终存在于这三个项目中。谢谢你的帮助。 - Tehreem
@Tehreem,你能提供解决方案吗?我也遇到了同样的情况。 - Dinesh Haraveer
你可以使用 VisualTreeHelper 遍历可视树,但是我听说这可能不是访问元素的最高效方式,因此在 DataTemplate 中包装一个 UserControl 可能会更好。不过,任何有效的方法都可以。 - Filip Skakun

0

如果你只想在项目点击时获取屏幕坐标... 你可以在图像上注册一个点击处理程序,然后使用 senderimg.transform tovisual(null) 来获取一个通用的转换,从中你可以获取当前点的坐标。


0
我一整天都在苦苦挣扎,似乎必须加载(渲染)控件才能从DataTemplate中获取其子控件。这意味着您不能在窗口加载、初始化等操作中使用此代码或任何代码(用于相同目的)...但是您可以在控件初始化后,例如在SelectedItem上使用它。
我建议使用转换器并在转换器中定义所需的操作,而不是直接访问控件。

0

如果您已经通过绑定指定了源,为什么不用同样的方法处理其余属性:

<FlipView SelectedIndex="{Binding SIndex}" SelectedItem="{Binding SItem}" SelectedValue="{Binding SValue}"/>

您必须先为这些属性准备一个位置。可以使用从 INotifyPropertyChanged 派生的类来实现。

请雇佣 MVVM 为您工作,大部分工作都在 XAML 中完成。当然,在开始阶段可能需要更多的工作量,但是使用 MVVM 处理复杂的项目时将会更加连贯和灵活。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接