Access Views inside a DataTemplate at runtime in Xamarin.Forms
Asked Answered
K

2

5

I would like to know if there is a way to obtain a reference to a view inside a DataTemplate in a ListView in Xamarin.Forms. Supposing I have this xaml:

<ListView x:Name="ProductList" ItemsSource="{Binding Products}">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <StackLayout BackgroundColor="#eee" x:Name="ProductStackLayout"
                                     Orientation="Vertical" Padding="5" Tapped="ListItemTapped">
                            <Label Text="{Binding Name}" 
                                   Style="{DynamicResource ProductPropertiesStyle}"/>
                            <Label Text="{Binding Notes}" IsVisible="{Binding HasNotes}" 
                                    Style="{DynamicResource NotesStyle}"
                                />
                            <Label Text="{Binding Date}" 
                                   Style="{DynamicResource DateStyle}"
                            />
                        </StackLayout>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

I would like to be able to grab a reference to the StackLayout named "ProductStackLayout" in every row of the ListView. I need to do this when the page is appearing, to dynamically manipulate it's content (for something than can't be achieved with data binding), so I can't take advantage of view references passed in event handlers originating from elements in the DataTemplate itself like ItemTapped or similar.

For what I know, in WPF or UWP something like that could be achieved with the help of the VisualTreeHelper class, but I don't believe there is an equivalent of this class in Xamarin.Forms.

Kurtzman answered 19/3, 2018 at 23:24 Comment(1)
Why not it create the ListView in the code behind then ? If you want to work with each reference, that seems to be the way to go.Mountebank
G
5

Yeah, It is possible access the view which is created using DataTemplate in run-time. Hook BindingContextChanged event for the view inside the DataTemplate from XAML. In the event call back, the view created from DataTemplate can be accessed using sender parameter. You need type cast the sender to access the view, because sender is boxed to object type.

Or else, you can go for DataTemplate selector to create views based on your object.

Glenoid answered 20/3, 2018 at 4:16 Comment(2)
Hello @Dharmendar, thank you for your answer. I think this is exactly what I need. I've already used DataTemplate selectors in other contexts, and I completely forgot that they could come handily now. But the solution that hooks in the BindingContextChanged event is even simpler, I've already tested attaching a view at run time based on the state of the item view model and it works perfectly. What I want to do is a bit more complicated because I want to draw some graphics with SkiaSharp and I need some time to test it, but I don't see why it should not work the same.Kurtzman
Found a solution i was looking for so long :) Thanks bro.Kairouan
L
3

You can also cast like this:

    ITemplatedItemsView<Cell> templatedItemsView = listView as ITemplatedItemsView<Cell>;
    ViewCell firstCell = templatedItemsView.TemplatedItems[0] as ViewCell;
    StackLayout stackLayout = firstCell.View as StackLayout;

Which will give you reference to the views


But you probably want to react based on the change of the binding context since you otherwise will have to manually change the content of the view.

Using BindingContextChanged I suspect would make you render the content twice - first the change causes a render like normal - afterwards you render it again. So if for instance a change in a string occurs - a label will rerender - afterwards you get the value in BindingContextChanged and preform the render that you actually wanted.

You can subclass ListView which i think would prevent it:

public class CustomListView : ListView
{
    protected override void SetupContent(Cell content, int index)
    {
        // render differently depending on content.BindingContext
        base.SetupContent(content, index);
    }
}
Least answered 29/5, 2019 at 15:8 Comment(1)
Both snippets are interesting. I didn't know about ITemplatedItemsView<T> interface, nor did I think about subclassing the ListView and overriding the SetupContent method. If the BindingContext is available at that time this definitely seems to be the best solution. My app is now in production since many months and uses the BindingContextChanged approach: it works basically well, but If I will need something like this again, I would certainly consider the subclassing approach, as well as the DataTemplateSelector one.Kurtzman

© 2022 - 2024 — McMap. All rights reserved.