locked
Adding HubSections dynamically RRS feed

  • Question

  • The sections we want to show are controlled by data from the cloud. I have gotten dynamic creation of hub sections working, but the header always goes on the bottom. I think this is a hub bug... can anyone confirm?

    Here's how I got dynamic hubsection working:

    XAML DataTemplate for a new section:

            <DataTemplate x:Name="PodDataTemplate">
                <StackPanel/>
            </DataTemplate>

    Code for dynamically creating sections, with a usercontrol added to the panel of the section:

               object templateHub;
               this.Resources.TryGetTypedValue("PodDataTemplate", out templateHub);

               var hubSection = new PodHubSection();
               hubSection.Tag = podUserControl;
               hubSection.ContentTemplate = templateHub as DataTemplate;
               this.hub.Sections.Add(hubSection);

    Then in PodHubSection.OnApplyTemplate():

                var pod = this.Tag as UserControl;
                var panel = this.FindVisualChildByType<Panel>();
                panel.Children.Add(pod);

    Monday, August 19, 2013 5:42 PM

Answers

  • A couple of things to point out here.

    • You will need to name the StackPanel in your DataTemplate and search the VisualTree for that named panel. Not just the first or last panel, you may end of finding one of the Panels in the default hub template.
    • In the OnApplyTemplate method, the HubSection.ContentTemplate has been applied but not necessarily loaded yet.  So a visual tree search for your target panel may return null, or in your current case you are getting one of the Panels from the default HubSection template

    Here is a some code that may work for you.  I have not fully tested this out, so I am not aware of any perf hits you may see.

    public class PodHubSection : HubSection
    {
        public PodHubSection()
        {
            this.Loaded += PodHubSection_Loaded;
        }
    
        void PodHubSection_Loaded(object sender, RoutedEventArgs e)
        {
            base.OnApplyTemplate();
            var panel = FindNameInSubtree<Panel>(this, "target_panel");
            var t = new TextBlock();
            t.Text = "New dynamically added content";
            panel.Children.Add(t);
    
            this.Loaded -= PodHubSection_Loaded;
        }
    
        public T FindNameInSubtree<T>(FrameworkElement element, string descendantName) where T : FrameworkElement
        {
            if (element == null)
                return null;
            if (element.Name == descendantName)                
                return element as T;
            int childrenCount = VisualTreeHelper.GetChildrenCount(element);
            for (int i = 0; i < childrenCount; i++)
            {
                var result = FindNameInSubtree<T>(VisualTreeHelper.GetChild(element, i) as FrameworkElement, descendantName);
                if (result != null)
                    return result;
            }
            return null;
        }
    }


    • Edited by Matt Hohn - MSFT Tuesday, August 20, 2013 9:41 PM
    • Proposed as answer by Pat Finnigan Tuesday, August 20, 2013 11:26 PM
    • Marked as answer by PatFatCat Wednesday, August 21, 2013 3:37 PM
    Tuesday, August 20, 2013 9:41 PM
  • Hi Pat,

    Are you finding the correct StackPanel?  It's hard for me to tell without screenshots/full source.  Note that there is a horizontal StackPanel in the HubSection control template that hosts the Header and chevron.

    I tried to create a quick repro of your scenario as well and it seems that while the HubSection.ContentTemplate is indeed set when HubSection.OnApplyTemplate() runs, it hasn't been expanded.  The StackPanel from the DataTemplate hasn't been added to the visual tree yet.  You could add a Loaded event handler to the StackPanel, and then any code you write here will have direct access to that StackPanel.

    You mentioned your scenario requires populating HubSections dynamically with data from the cloud.  If I understand correctly each HubSection's visual tree is built up dynamically when that HubSection loads.   If feasible perhaps you should consider an approach where existing an DataTemplate is selected once the cloud data object is fetched. I don't have enough context on your scenario to know if this will work for you, but it would simplify the wiring up of your UI.

    • Marked as answer by PatFatCat Tuesday, August 20, 2013 11:04 PM
    Tuesday, August 20, 2013 9:45 PM

All replies

  • Can you share your entire repro code? What do you mean by "the header always goes on the bottom"?

    Here is what I have tried:

    - Create a Hub app in Visual Studio

    - In the function: navigationHelper_LoadState, I added another function called AddHubSection() that does this:

            private void AddHubSection()
            {
                HubSection hubSection= new HubSection();
                TextBlock headerTextBlock = new TextBlock();
                headerTextBlock.Text = "New Hub Section";
                hubSection.Header = headerTextBlock;
                hubSection.Padding = new Thickness(40, 30, 150, 44);
    
                object testDataTemplate;
                this.Resources.TryGetValue("testDataTemplate", out testDataTemplate);
                hubSection.ContentTemplate = testDataTemplate as DataTemplate;
    
                this.TestHub.Sections.Add(hubSection);
            }

    - Where "testDataTemplate" has been defined in the HubPage.xaml as follows:

            <DataTemplate x:Key="testDataTemplate">
                <Grid Height="250" Width="350" Background="Green">
                    <StackPanel>
                        <TextBlock Text="This is a test template" Style="{StaticResource TitleTextBlockStyle}" TextWrapping="NoWrap"/>
                    </StackPanel>
                </Grid>
            </DataTemplate>

    - When the app starts, I can see a new section being added with the appropriate content.

    - Can you rephrase your problem based on my repro or maybe share your complete repro (via SkyDrive for example) so that we can understand the problem better?


    Windows Store Developer Solutions #WSDevSol || Want more solutions? See our blog, http://aka.ms/t4vuvz

    Tuesday, August 20, 2013 7:21 PM
    Moderator
  • Hi Parshant,

    I tried your code and it works. The problem is that your datatemplate is static. In mine I want to add dynamically into the stackpanel inside the hubsection. What goes into it will be a usercontrol. For now, I am just testing by adding a textblock.

    Try this below, and you will see that the content that we dynamically add into the stackpanel inside the controltemplate goes above the header, instead of after the existing "This is a test template"

    Change your code to

    var hubSection= new PodHubSection(); .....

    where

        public class PodHubSection : HubSection
        {
            protected override void OnApplyTemplate()
            {
                base.OnApplyTemplate();

                var panel = this.FindVisualChildByType<Panel>();
                var t = new TextBlock();
                t.Text="New dynamically added content";
                panel.Children.Add(t);
            }
        }

    NOTE: I also tried adding the Children in Loaded and SizeChanged, and, I tried calling InvalidateArrange and InvalidateMeassure after adding the Child item. No joy.

    Tuesday, August 20, 2013 8:31 PM
  • Thanks for the code above. What does FindVisualChildByType look like? Can you share that function definition too?

    Windows Store Developer Solutions #WSDevSol || Want more solutions? See our blog, http://aka.ms/t4vuvz

    Tuesday, August 20, 2013 8:51 PM
    Moderator
  • A couple of things to point out here.

    • You will need to name the StackPanel in your DataTemplate and search the VisualTree for that named panel. Not just the first or last panel, you may end of finding one of the Panels in the default hub template.
    • In the OnApplyTemplate method, the HubSection.ContentTemplate has been applied but not necessarily loaded yet.  So a visual tree search for your target panel may return null, or in your current case you are getting one of the Panels from the default HubSection template

    Here is a some code that may work for you.  I have not fully tested this out, so I am not aware of any perf hits you may see.

    public class PodHubSection : HubSection
    {
        public PodHubSection()
        {
            this.Loaded += PodHubSection_Loaded;
        }
    
        void PodHubSection_Loaded(object sender, RoutedEventArgs e)
        {
            base.OnApplyTemplate();
            var panel = FindNameInSubtree<Panel>(this, "target_panel");
            var t = new TextBlock();
            t.Text = "New dynamically added content";
            panel.Children.Add(t);
    
            this.Loaded -= PodHubSection_Loaded;
        }
    
        public T FindNameInSubtree<T>(FrameworkElement element, string descendantName) where T : FrameworkElement
        {
            if (element == null)
                return null;
            if (element.Name == descendantName)                
                return element as T;
            int childrenCount = VisualTreeHelper.GetChildrenCount(element);
            for (int i = 0; i < childrenCount; i++)
            {
                var result = FindNameInSubtree<T>(VisualTreeHelper.GetChild(element, i) as FrameworkElement, descendantName);
                if (result != null)
                    return result;
            }
            return null;
        }
    }


    • Edited by Matt Hohn - MSFT Tuesday, August 20, 2013 9:41 PM
    • Proposed as answer by Pat Finnigan Tuesday, August 20, 2013 11:26 PM
    • Marked as answer by PatFatCat Wednesday, August 21, 2013 3:37 PM
    Tuesday, August 20, 2013 9:41 PM
  • Hi Pat,

    Are you finding the correct StackPanel?  It's hard for me to tell without screenshots/full source.  Note that there is a horizontal StackPanel in the HubSection control template that hosts the Header and chevron.

    I tried to create a quick repro of your scenario as well and it seems that while the HubSection.ContentTemplate is indeed set when HubSection.OnApplyTemplate() runs, it hasn't been expanded.  The StackPanel from the DataTemplate hasn't been added to the visual tree yet.  You could add a Loaded event handler to the StackPanel, and then any code you write here will have direct access to that StackPanel.

    You mentioned your scenario requires populating HubSections dynamically with data from the cloud.  If I understand correctly each HubSection's visual tree is built up dynamically when that HubSection loads.   If feasible perhaps you should consider an approach where existing an DataTemplate is selected once the cloud data object is fetched. I don't have enough context on your scenario to know if this will work for you, but it would simplify the wiring up of your UI.

    • Marked as answer by PatFatCat Tuesday, August 20, 2013 11:04 PM
    Tuesday, August 20, 2013 9:45 PM
  • Thank you Patrick... that was indeed the problem. Forgot that there's a lot inside the hubsection and that I could be finding the wrong panel. That was easy!
    Tuesday, August 20, 2013 11:05 PM