Search code examples
xamluwpdatatemplateuwp-xaml

How to set the visiblility of a grid when the grid is inside a DataTemplate?


In our UWP app the DataTemplate for MyListView is set in the code behind to either DataTemplateA or DataTemplateB in Page.Resources. Each data template contains a grid (TopGrid) which contains a DisplayGridButton and another grid (DisplayGrid).

DisplayGrid contains SecondListView and a HideGridButton

DisplayGridButton should show DisplayGrid. HideGridButton should collapse DisplayGrid.

The XAML is

<Page.Resources>
    <DataTemplate x:Key="DataTemplateA">
        <Grid Name="TopGrid">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Horizontal">
                <TextBox/>
                <Button Name="DisplayGridButton" Content="Show" Margin="10,0" Click="DisplayGridButton_Click"/>
            </StackPanel>
            <Grid Name="DisplayGrid" Grid.Row="1" Visibility="Collapsed">
                <StackPanel>
                    <Button Name="HideGridButton" Content="Hide" Click="HideGridButton_Click"/>
                    <ListView Name="SecondListView">
                        <ListView.ItemTemplate>
                            <DataTemplate >

                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </StackPanel>
            </Grid>
        </Grid>
    </DataTemplate>

    <DataTemplate x:Key="DataTemplateB">
        <Grid Name="TopGrid">
            <Grid.RowDefinitions>
                <RowDefinition Height="auto"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <StackPanel Orientation="Horizontal">
                <TextBox/>
                <Button Name="DisplayGridButton" Content="Show" Margin="10,0" Click="DisplayGridButton_Click"/>
            </StackPanel>
            <Grid Name="DisplayGrid" Grid.Row="1" Visibility="Collapsed">
                <StackPanel>
                    <Button Name="HideGridButton" Content="Hide" Click="HideGridButton_Click"/>
                    <ListView Name="SecondListView">
                        <ListView.ItemTemplate>
                            <DataTemplate >

                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </StackPanel>
            </Grid>
        </Grid>
    </DataTemplate>
</Page.Resources>

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <ListView Name="MyListView">

    </ListView>
</Grid>

DataTemplateA or DataTemplateB is set in the code behind.

if (condition)
{
    MyListView.ItemTemplate = (DataTemplate)Resources["DataTemplateA"];
}
    else
{
    MyListView.ItemTemplate = (DataTemplate)Resources["DataTemplateB"];
}

In the Code behind I can create the event handler but I cannot access the DisplayGrid to make it visible or to collapse it.

I would normally set visibility like this.

 private void DisplayGridButton_Click(object sender, RoutedEventArgs e)
    {
        DisplayGrid.Visibility = Visibility.Visible;
    }

 private void HideGridButton_Click(object sender, RoutedEventArgs e)
    {
        DisplayGrid.Visibility = Visibility.Collapsed;
    }

How do I access the DisplayGrid in the DataTemplate from the button click events?


Solution

  • Since the grid is defined in a template, you'll have to dig it out at runtime. (If you could reference it as "DisplayGrid" from code-behind, you wouldn't know which listview item it belonged to anyway.)

    Implement click handlers something like this:

    private void DisplayGridButton_Click(object sender, RoutedEventArgs e)
    {
        Button button = sender as Button;
        StackPanel stackPanel = button?.Parent as StackPanel;
        Grid grid = stackPanel?.Parent as Grid;
        if (grid != null)
        {
            Grid displayGrid = FindVisualChild<Grid>(grid, "DisplayGrid");
            if (displayGrid != null)
            {
                displayGrid.Visibility = Visibility.Visible;
            }
        }
    }
    
    private void HideGridButton_Click(object sender, RoutedEventArgs e)
    {
        Button button = sender as Button;
        StackPanel stackPanel = button?.Parent as StackPanel;
        Grid grid = stackPanel?.Parent as Grid;
        if (grid != null)
        {
            grid.Visibility = Visibility.Collapsed;
        }
    }
    

    (Fair warning: The way this code finds the appropriate parent to start from is a bit brittle; it might break if the template changes. Searching by name would be better, but I don't have anything handy right now.)

    Here is the helper method that finds a named child in the visual tree:

    public static T FindVisualChild<T>(
        DependencyObject parent,
        string name = null)
        where T : DependencyObject
    {
        if (parent != null)
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
            for (int i = 0; i < childrenCount; i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(parent, i);
                T candidate = child as T;
                if (candidate != null)
                {
                    if (name == null)
                    {
                        return candidate;
                    }
    
                    FrameworkElement element = candidate as FrameworkElement;
                    if (name == element?.Name)
                    {
                        return candidate;
                    }
                }
    
                T childOfChild = FindVisualChild<T>(child, name);
                if (childOfChild != null)
                {
                    return childOfChild;
                }
            }
        }
    
        return default(T);
    }
    

    (This method can also search by type only; just pass null as the name.)