How do I access a control inside a XAML DataTemplate?

假装没事ソ 提交于 2019-12-27 10:26:08

问题


I have this 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>

I want to find img1 of currently selected index. While searching for it I found this method on some post here:

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

It returns me the Image on the first index of flipview but I need the one present on the currently selected index.. I tried to edit this method but I am unable to find the required control. Can anyone help me?


回答1:


The problem you are experiencing is that the DataTemplate is repeating and the content is being generated by the FlipView. The Name is not exposed because it would conflict with the previous sibling that was generated (or the next one that will be).

So, to get a named element in the DataTemplate you have to first get the generated item, and then search inside that generated item for the element you want. Remember, the Logical Tree in XAML is how you access things by name. Generated items are not in the Logical Tree. Instead, they are in the Visual Tree (all controls are in the Visual Tree). That means it is in the Visual Tree you must search for the control you want to reference. The VisualTreeHelper lets you do this.

Now, how to do it?

I wrote an article on this because it is such a recurring question: http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html but the meat of the solution is a recursive method that looks something like this:

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

The key issue here is that a method like this gets all the children, and then in the resulting list of child controls you can search for the specific control you want. Make sense?

And now to answer your question!

Because you specifically want the currently selected item, you can simply update the code like this:

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



回答2:


maybe a little more generic approach might be something like this:

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

You would invoke FindControl like this:

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:


The simple solution to getting access to elements within a DataTemplate is to wrap the contents of the DataTemplate in a UserControl, where you get access to all UI elements in an ItemsControl's item. I think FlipView usually virtualizes its items so even if you have 100 items bound to it - only 2-3 might actually have a current representation in the UI (1-2 of them hidden), so you have to remember that when you want to replace anything and only actually make changes when an item is loaded into the control.

If you really need to identify an item container that represents the item in ItemsSource - you can check your FlipView's ItemContainerGenerator property and its ContainerFromItem() method.

To get coordinates of an item you can use the GetBoundingRect() extension method in WinRT XAML Toolkit.

Overall however, based on your comment it might be that the best approach is actually completely different. If you are binding your FlipView to a source - you can usually control images displayed or overlaid by changing the properties of the bound source collection items.




回答4:


If you just want the screen coordinates on item click... You can register a click handler on the image and then use senderimg.transform tovisual(null) this gives you a generaltransforn from which youcan get the current point coordinates.




回答5:


I was struggling whit this whole day, and it seems that the control has to loaded (rendered) in order to get it's child controls from the DataTemplate. This means that you cant use this code or any code (for the same purpose) on window loaded, initialized... You can how ever use it after the control is initialized for example on SelectedItem.

I suggest using converters and define the requested action in the converter, instead of direct control access.




回答6:


So if You assigned Source via binding why wouldn't you do the same with rest: <FlipView SelectedIndex="{Binding SIndex}" SelectedItem="{Binding SItem}" SelectedValue="{Binding SValue}"/>

You must first prepare place for this properties. It may be a class derived from INotifyPropertyChanged.

Hire MVVM to work for You, do most in XAML. Of course at the begining it seems to be more work but with more sophisticated project with MVVM will be coherent and flexible.



来源:https://stackoverflow.com/questions/16375375/how-do-i-access-a-control-inside-a-xaml-datatemplate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!