locked
How to access a named element inside the HeaderTemplate in ItemsControl or ListView? RRS feed

  • Question

  • I need to access a named element in a HeaderTemplate of ItemsControl, but because it is in DataTemplate, it can not be found just by traverse the visual tree of the ItemsControl. So we need to traverse the groupitem generated by the HeaderTemplate. But I have not idea about how to do this.

    So, my question is: how to access a named element the HeaderTemplate of the ItemsControl? 
    Sunday, November 3, 2013 3:04 PM

Answers

  • Please refer to the following example where the ItemsControl is bound to the results of a grouping LINQ query through a CollectionViewSource. The code iterates through the items in the ItemsControl and traverse the visual tree to find the corresponding GroupItem for each item in the Items collection, and then the TextBlock in the HeaderTemplate of the GroupStyle:

    XAML:

    <Page
        x:Class="Win8App1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:Win8App1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        <Page.Resources>
            <CollectionViewSource x:Key="cvs" IsSourceGrouped="True"/>
        </Page.Resources>
        <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <ItemsControl x:Name="ic" ItemsSource="{Binding Source={StaticResource cvs}}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock x:Name="txt" Text="{Binding Name}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <TextBlock x:Name="headerTextBlock" FontWeight="Bold" FontSize="15" Text="{Binding Key}"/>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </ItemsControl.GroupStyle>
            </ItemsControl>
        </Grid>
    </Page>

    C#:

    public sealed partial class MainPage : Page
        {
            List<Country> countries;
            public MainPage()
            {
                InitializeComponent();
    
                countries = new List<Country>();
                countries.Add(new Country(){Name="USA", Continent="NA"});
                countries.Add(new Country() { Name = "UK", Continent = "EU" });
    
                var grouped = (from c in countries group c by c.Continent into grp orderby grp.Key
                               select grp)
                    .ToList();
    
                CollectionViewSource cvs = this.Resources["cvs"] as CollectionViewSource;
                cvs.Source = grouped;
    
                this.Loaded += MainPage_Loaded;
            }
    
            private void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                foreach (object c in ic.Items)
                {
                    DependencyObject depobj = ic.ItemContainerGenerator.ContainerFromItem(c) as DependencyObject;
                    GroupItem gi = FindVisualParent<GroupItem>(depobj);
                    TextBlock headerTextBlock = FindVisualChildByName<TextBlock>(gi, "headerTextBlock");
                    headerTextBlock.Foreground = new SolidColorBrush(Windows.UI.Colors.Blue);
                }
            }
    
            private static T FindVisualParent<T>(DependencyObject dependencyObject) where T : DependencyObject
            {
                var parent = VisualTreeHelper.GetParent(dependencyObject);
     
                if (parent == null) return null;
     
                var parentT = parent as T;
                return parentT ?? FindVisualParent<T>(parent);
            }
    
            private static T FindVisualChildByName<T>(FrameworkElement obj, string name) where T : FrameworkElement
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    FrameworkElement child = VisualTreeHelper.GetChild(obj, i) as FrameworkElement;
                    if (child != null && child is T && child.Name.Equals(name))
                        return (T)child;
                    else
                    {
                        T childOfChild = FindVisualChildByName<T>(child, name);
                        if (childOfChild != null)
                            return childOfChild;
                    }
                }
                return null;
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }
        }
    

    • Marked as answer by silverbird.lee Tuesday, November 5, 2013 10:00 AM
    Monday, November 4, 2013 10:09 AM

All replies

  • Sunday, November 3, 2013 8:11 PM
  • Hi, Sachin. I have read the link as your recommendation, but it does not help, because this article only addressed how to access a named element in ItemTemplate, not HeaderTemplate in ListView's GroupStyle.

    I have just posted my original question here.(http://social.msdn.microsoft.com/Forums/windowsapps/en-US/e8d4f7b3-93a8-4f17-9679-8b700b8d02e6/how-to-adjust-the-width-of-group-header-in-listview-to-stretch-full-width?forum=winappswithcsharp)  Can you give a look at it too? thx

    Monday, November 4, 2013 1:54 AM
  • Hi,

    You can bind the StackPanel Tag property to the Key. And try to define the StackPanel Loaded event, then you can access the StackPanel in its loaded event.

    See some codes below:

    In XAML I bind the StackPanel Tag property to the Key:

    <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <StackPanel HorizontalAlignment="Stretch"
                                            x:Name="MyPanel"
                                            Background="Red"
                                            Width="{Binding ActualWidth, ElementName=lbxHouseGuests}"  Loaded="MyPanel_Loaded" Tag="{Binding Key}" >
                                    <Button Content="{Binding Key}"
                                            HorizontalAlignment="Stretch" />
                                </StackPanel>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>

    Code-behind, I can access the special StackPanel by its Tag property:

    private void MyPanel_Loaded(object sender, RoutedEventArgs e)
            {
                StackPanel panle = sender as StackPanel;
                if (panle.Tag != null && panle.Tag.Equals("Player"))
                {
                    panle.Width = 300;
                    panle.Background = new SolidColorBrush(Windows.UI.Colors.Blue);
                }
            }

    Best Wishes!




    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey. Thanks<br/> MSDN Community Support<br/> <br/> Please remember to &quot;Mark as Answer&quot; the responses that resolved your issue. It is a common way to recognize those who have helped you, and makes it easier for other visitors to find the resolution later.

    Monday, November 4, 2013 6:48 AM
  • Anne, thanks for the quick reply.

    It's my fault. I have forgotten to mention that my HeaderTemplate was in a resource dictionary file separately, so I have absolutely no access to its event anyhow. Besides, I think maybe it's not a good idea to adjust the width property in the loaded event(what if the size of ListView changes dynamically?). 

    Anyway, many thanks!

    Monday, November 4, 2013 7:41 AM
  • Sachin's link still provide the best way to go. you will need to use the visualtreehelper to look into the listview.

    Microsoft Certified Solutions Developer - Windows Store Apps Using C#

    Monday, November 4, 2013 7:57 AM
  • Hi, Dave. Maybe it sounds like a good solution, but I have no idea how to get the ListViewGroupHeaderItem(or something), in order to traverse its visual tree.



    Monday, November 4, 2013 8:56 AM
  • Please refer to the following example where the ItemsControl is bound to the results of a grouping LINQ query through a CollectionViewSource. The code iterates through the items in the ItemsControl and traverse the visual tree to find the corresponding GroupItem for each item in the Items collection, and then the TextBlock in the HeaderTemplate of the GroupStyle:

    XAML:

    <Page
        x:Class="Win8App1.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:Win8App1"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        <Page.Resources>
            <CollectionViewSource x:Key="cvs" IsSourceGrouped="True"/>
        </Page.Resources>
        <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <ItemsControl x:Name="ic" ItemsSource="{Binding Source={StaticResource cvs}}">
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <TextBlock x:Name="txt" Text="{Binding Name}"/>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
                <ItemsControl.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <TextBlock x:Name="headerTextBlock" FontWeight="Bold" FontSize="15" Text="{Binding Key}"/>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </ItemsControl.GroupStyle>
            </ItemsControl>
        </Grid>
    </Page>

    C#:

    public sealed partial class MainPage : Page
        {
            List<Country> countries;
            public MainPage()
            {
                InitializeComponent();
    
                countries = new List<Country>();
                countries.Add(new Country(){Name="USA", Continent="NA"});
                countries.Add(new Country() { Name = "UK", Continent = "EU" });
    
                var grouped = (from c in countries group c by c.Continent into grp orderby grp.Key
                               select grp)
                    .ToList();
    
                CollectionViewSource cvs = this.Resources["cvs"] as CollectionViewSource;
                cvs.Source = grouped;
    
                this.Loaded += MainPage_Loaded;
            }
    
            private void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                foreach (object c in ic.Items)
                {
                    DependencyObject depobj = ic.ItemContainerGenerator.ContainerFromItem(c) as DependencyObject;
                    GroupItem gi = FindVisualParent<GroupItem>(depobj);
                    TextBlock headerTextBlock = FindVisualChildByName<TextBlock>(gi, "headerTextBlock");
                    headerTextBlock.Foreground = new SolidColorBrush(Windows.UI.Colors.Blue);
                }
            }
    
            private static T FindVisualParent<T>(DependencyObject dependencyObject) where T : DependencyObject
            {
                var parent = VisualTreeHelper.GetParent(dependencyObject);
     
                if (parent == null) return null;
     
                var parentT = parent as T;
                return parentT ?? FindVisualParent<T>(parent);
            }
    
            private static T FindVisualChildByName<T>(FrameworkElement obj, string name) where T : FrameworkElement
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                {
                    FrameworkElement child = VisualTreeHelper.GetChild(obj, i) as FrameworkElement;
                    if (child != null && child is T && child.Name.Equals(name))
                        return (T)child;
                    else
                    {
                        T childOfChild = FindVisualChildByName<T>(child, name);
                        if (childOfChild != null)
                            return childOfChild;
                    }
                }
                return null;
            }
    
            protected override void OnNavigatedTo(NavigationEventArgs e)
            {
            }
        }
    

    • Marked as answer by silverbird.lee Tuesday, November 5, 2013 10:00 AM
    Monday, November 4, 2013 10:09 AM
  • Hi, Magnus, thanks again for your quick reply.

    I test this solution, it does not work either.

    Not only the method  

    Windows.UI.Xaml.Controls.ItemContainerGenerator.ContainerFromItem()

    is marked obsolete in windows 8.1, but also it seems that it always return null. When I changed it to ItemControl.ContainerFromItem(), it did return some non-null dependency object. But when I pass it to the FindVisualParent<T>() method, the return value is null

    GroupItem gi = FindVisualParent<GroupItem>(depobj);

    that is, gi is always null. That means depobj does not have a parent element of type GroupItem. It's getting weird more and more.

    Any way, these two solutions all seem in the right direction, good job and many thanks!


    Monday, November 4, 2013 12:20 PM
  • The sample code should work fine on Windows 8.1, I've tested it.
    • Marked as answer by silverbird.lee Tuesday, November 5, 2013 9:59 AM
    • Unmarked as answer by silverbird.lee Tuesday, November 5, 2013 9:59 AM
    Tuesday, November 5, 2013 9:31 AM
  • Magnus, my mistake, your code did work.  

    Tuesday, November 5, 2013 10:02 AM