How do I access a control inside a XAML DataTemplate?
Asked Answered
F

6

30

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?

Frantic answered 4/5, 2013 at 14:30 Comment(5)
That is usually the wrong approach. What is it specifically you want done with that image? Note that FlipView virtualizes its children so that Image might not even exist.Amersham
@FilipSkakun I want to get the screen coordinates of that image. Can I do that any other way?Frantic
What do you need the screen coordinates for?Roee
@DamirArh I want overwrite this image on another image that's why i need the exact position of this image...Frantic
Related question #27333669Cuxhaven
C
57

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...
Cuxhaven answered 8/5, 2013 at 17:24 Comment(4)
Jerry - It seems that this only works if you have a SelectedItem. How would you target an element through an button click outside of the bounds of the templated item? See #23645831Ibnrushd
@JerryNixon-MSFT I'm try to use your code in a windows phone 8.1 application, but it cannot find a reference to VisualTreeHelperClass. But when I search for it in object browser I can find it in System.Windows.Media and Windows.UI.Xaml.Media . Also OfType<TextBox>() gives error Systems.Generic.Connections.List does not contain definiation of OfType(). The structure of my FlipViewBoroughenglish
Related question #27333669Cuxhaven
Using this code (removing ItemContainerGenerator since it's obsolete now), I'm getting "The parameter is incorrect" at GetChildrenCount in AllChildren. The code is nearly identical but I'm developing for Windows 10, not 8.1Objectify
A
13

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;         
Alecto answered 29/8, 2013 at 22:7 Comment(0)
A
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.

Amersham answered 5/5, 2013 at 4:21 Comment(3)
I resolved the issue by getting all the images in FlipView in a list and then I iterated through that list to find my required image. Yes you are right I could only access 3 items of the FlipView at a time but as I needed currently focused item, it is always present in those three items. Thankyou for your helpFrantic
@Frantic could you provide solution i'm facing the same situation.Rudin
You can traverse the visual tree using VisualTreeHelper, but I've been hearing it might not be the most performant way to get access to elements, so wrapping your DataTemplate in a UserControl might work better. Whatever works is fine though.Amersham
L
0

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.

Lumenhour answered 16/5, 2013 at 19:27 Comment(0)
C
0

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.

Charqui answered 23/1, 2015 at 12:23 Comment(0)
P
0

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.

Persecute answered 20/10, 2016 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.