Binding to HierarchicalDataTemplate

Mar 15, 2010 at 3:24 PM

Hi Thomas,

I have the following scenario where I have an ObservableCollection of items which need to be grouped by 2 properties (SubDest and then Dest) displaying Count and Sum such that I can bind to a TreeView displaying SubDest with totals followed by Dest which have same SubDest and then the raw values.

I created a class called Outbound Summary below to achieve that. OrdersgroupedBySubDest creates an IEnumerable of OutboundSummary which is grouped by SubDest and OrdersGroupedByDest works on the IEnumerable OrdersgroupedBySubDest to create another list grouped by SubDest and Dest.

public class OutboundSummary
    {
        public int ExecShares { get; set; }
        public int TotalShares { get; set; }
        public double TotalOrders { get; set; }

        //Groups
        public string SubDest { get; set; }
        public string Dest { get; set; }
        public string System { get; set; }
    }

public IEnumerable<OutboundSummary> OrdersGroupedBySubDest
        {
            get
            {
                return
                    OE.Async(
                            ExpressionObserver.Execute(
                                () =>
                                    (
                                        childOrdersList.GroupBy(x => new { x.SubDest })
                                            .Select(x => new OutboundSummary
                                            {
                                                SubDest = x.First().SubDest,
                                                Dest = x.First().Dest,
                                                System = x.First().Src,
                                                ExecShares = x.Sum(p => p.ExecShares),
                                                TotalShares = x.Sum(p => p.OrdSize),
                                                TotalOrders = x.Count(),
                                            }).OrderByDescending(c => c.SubDest)
                                    )
                            ).Cascade()
                        );
            }
        }


public IEnumerable OrdersGroupedByDest
        {
            get
            {
                return
                    OE.Async(
                            ExpressionObserver.Execute(OrdersGroupedBySubDest, t =>
                                from d in t.OfType<OutboundSummary>().ToList()
                                  group d by new {  d.SubDest, d.Dest } into pg
                                  select new { Process = pg.Key, Items = pg }
                            ).Cascade().AsEnumerable()
                        );
            }
        }

I want to bind the OrdersGroupedByDest to the following treeview .

<TreeView Grid.Row="1"  Name="_treeView">
                                <TreeView.Resources>
                                    <!-- Level 0 template leaves space for 2 child "Toggle" levels -->
                                    <HierarchicalDataTemplate ItemsSource="{Binding}" x:Key="Template" DataType="{x:Type outbound:OutboundSummary}">
                                        <Grid>
                                            <Grid.ColumnDefinitions>
                                                <ColumnDefinition SharedSizeGroup="SubDest"/>
                                                <ColumnDefinition SharedSizeGroup="Toggle"/>
                                                <ColumnDefinition SharedSizeGroup="Toggle"/>
                                                <ColumnDefinition SharedSizeGroup="TotalShares"/>
                                                <ColumnDefinition SharedSizeGroup="TotalOrders"/>
                                            </Grid.ColumnDefinitions>
                                            <TextBlock Grid.Column="0" Text="{Binding SubDest}" />
                                            <TextBlock Grid.Column="3" Text="{Binding Dest}" Style="{StaticResource TextBlockBoldStyle}" />
                                            <TextBlock Grid.Column="4" Text="{Binding TotalOrders}" Style="{StaticResource TextBlockBoldStyle}" />
                                        </Grid>

                                        <!-- Level 1 template leaves space for 1 child "Toggle" level -->
                                        <HierarchicalDataTemplate.ItemTemplate>
                                            <HierarchicalDataTemplate ItemsSource="{Binding Items}" DataType="{x:Type outbound:OutboundSummary}">
                                                <Grid Name="grd">
                                                    <Grid.ColumnDefinitions>
                                                        <ColumnDefinition SharedSizeGroup="Dest"/>
                                                        <ColumnDefinition/>
                                                        <ColumnDefinition SharedSizeGroup="Toggle"/>
                                                        <ColumnDefinition SharedSizeGroup="TotalShares"/>
                                                        <ColumnDefinition SharedSizeGroup="TotalOrders"/>
                                                    </Grid.ColumnDefinitions>
                                                    <TextBlock Grid.Column="0" Text="{Binding Process.System}" />
                                                    <TextBlock Grid.Column="3" Text="{Binding Process.TotalShares}" Style="{StaticResource TextBlockBoldStyle}" />
                                                    <TextBlock Grid.Column="4" Text="{Binding Process.TotalOrders}" Style="{StaticResource TextBlockBoldStyle}" />
                                                </Grid>
                                            </HierarchicalDataTemplate>
                                        </HierarchicalDataTemplate.ItemTemplate>
                                    </HierarchicalDataTemplate>
                                </TreeView.Resources>
                            </TreeView>

 However OrdersGroupedByDest creates an IEnumerable of type AnonymousType#1 and does not bind to the tree.

My 1st question is , Is this approach of creating 2 ExpressionObservers to group by 2 properties the right way of doing it or do you suggest a different approach. The underlying ObservableCollection is changed via socket callback so performance is important.

The 2nd question is more related to WPF than Obtics, where by how would you bind an Anonymous Type to a HierarchicalDataTemplate for a treeview.

Any help is as usual appreciated

Thanks

Vinay

Mar 15, 2010 at 6:16 PM

Basically, how can I create a multi-grouped structure that could be binded to a Treeview. An example of the linq is below

public IEnumerable<SupplierCountryStockGroup> GetSupplierCountries_StockGroups_Products()
    {
      var GroupedProducts = from p in dc.Products.OrderBy(o => o.UnitsInStock)
                            where p.UnitsInStock != null
                            group p by new { Country = p.Supplier.Country, StockGroup = p.UnitsInStock > 50 } into gr
                            select new UnitsInStockGroup() 
                              { Country = gr.Key.Country, StockGroup = gr.Key.StockGroup, Products = gr };
 
      return from p in GroupedProducts
             group p by new { Country = p.Country } into gr
             select new SupplierCountryStockGroup() { Country = gr.Key.Country, StockGroups = gr };
    }

 Thanks

Vinay

Coordinator
Mar 16, 2010 at 12:34 PM

Hi Vinay,

I believe that WPF can only bind to properties. Anonymous types implement their members as fields. This means that in WPF you can not bind to the members of an anonyous type.

This is a bit of a bummer but the solution is to create a named type like you did for OutboundSummary. If you know that the members will never get updated you will get a somewhat better performance in Obtics if you would seal the type. In this way Obtics knows it will never get change notifications from an object of that type and will not try to register for such notifications.

If performancy is an issue I would dispatch the changes to your ObservableCollection asynchronously. So invoke a different thread to do the updating and return from your socket callback quickly.

You could also access your ObservableCollection via one of the Async() overrides. In that way change notifications from the ObservableCollection would get dispatched. ( So: OE.Async( dc.Products ).OrderBy( ... etc. )  Note that this would dispatch changes to the collection only and not changes to the individual members of the collection! This would be fine of you would only add or remove Product objects. 

Regs,

Thomas.