How do I access a control inside a XAML DataTemplate?

56,496

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

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

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

Share:
56,496
Tehreem
Author by

Tehreem

Updated on April 05, 2020

Comments

  • Tehreem
    Tehreem about 4 years

    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?