Introducing on demand loading in the LightSwitch treeview user control – part 2.

Introduction

As explained in my previous post, a decent treeview solution for LightSwitch needs on demand loading.

I have search intensively to achieve this with the silverlight treeview control, but without success. So, I had to look into the direction of Telerik to get what I want. No doubt that other vendors have similar functionality.  Of course, I’m very curious if a pure silverlight treeview solution would be überhaupt possible.

Nonetheless, it’s not because we opt here for a third party control, that it all works out of the box. We still need to integrate it with the LightSwitch way of thinking and the way we use Odata.

Understanding “load on demand”.

We will start from the setup in my previous post and basically replace the silverlight treeview with the Telerik RadTreeView control. The most important difference will be that we will not use any longer the ValueConverter for fetching the Child departments. The Telerik treeview has 2 important events which we will use for the “load on demand” functionality:

private void MyRadTreeView_LoadOnDemand(object s1, Telerik.Windows.RadRoutedEventArgs e1)
        {

        }

        private void MyRadTreeView_ItemPrepared(object sender, Telerik.Windows.Controls.RadTreeViewItemPreparedEventArgs e)
        {

        }

 

The LoadOnDemand event will be used to load the child data and the ItemPrepared event will be used to tell the control that there are children present. This is done by setting the IsLoadOnDemandEnabled property of the current treeViewItem:

private void MyRadTreeView_ItemPrepared(object sender, Telerik.Windows.Controls.RadTreeViewItemPreparedEventArgs e)
        {
            var treeViewItem = e.PreparedItem;
            var dataItem = treeViewItem.Item as IEntityObject;

            // if the dataItem has children 
            {
               // treeViewItem.IsLoadOnDemandEnabled = false;
            }
            //else
            {
                //treeViewItem.IsLoadOnDemandEnabled = true;
            }
        }

The problem is of course: how can we find out if the current item has children?

The problem is that an odata feed has no notion of a count on the number of records in a navigation property. So, that’s something we first need to solve.

Of course, you can easily calculate client side the amount of child records by simply retrieving them… but remember… that’s what we wanted to avoid. We want to load the children on demand.

Create a RIA service for a Department DTO with children count

So, it’s clear so far that we can not directly bind our Telerik treeview to the GetRoot query of Departments. We need a data transfer object (DTO) which carries as well a ChildrenCount method which we will eventually use in the ItemPrepared event.

By doing so, the xaml of the custom control will be:

<UserControl x:Class="SilverlightClassLibrary.TelerikTreeWihtLightSwitchBinding"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    xmlns:telerik="http://schemas.telerik.com/2008/xaml/presentation" 

    d:DesignHeight="300" d:DesignWidth="400">

    <UserControl.Resources>
        <telerik:HierarchicalDataTemplate x:Name="ItemTemplate">
            <TextBlock Text="{Binding DepartmentName}" />
        </telerik:HierarchicalDataTemplate>

    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <telerik:RadTreeView x:Name="MyRadTreeView" LoadOnDemand="MyRadTreeView_LoadOnDemand" IsVirtualizing="True" ItemPrepared="MyRadTreeView_ItemPrepared" 
                               IsLoadOnDemandEnabled="True"  IsExpandOnSingleClickEnabled="True" IsRootLinesEnabled="True"
 IsLineEnabled="True"  IsTextSearchEnabled="True"  ItemTemplate="{StaticResource ItemTemplate}" ItemsSource="{Binding Screen.GetRoot}">
        </telerik:RadTreeView>
    </Grid>
</UserControl>

Note that the xaml is quite similar to the previous implementation, but there is no ValueConverter any longer in the HierarchicalDateTemplate. In the TreeView, we also hook up the 2 aforementioned events ( LoadOnDemand=”MyRadTreeView_LoadOnDemand” ItemPrepared=”MyRadTreeView_ItemPrepared”). We still bind to Screen.GetRoot, but this query is now operating on our DepartmentDTO:

The underlying RIA service looks as follows:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.ServiceModel.DomainServices.Hosting;
using System.ServiceModel.DomainServices.Server;
using System.Text;

namespace Ria
{
    public class DepartmentDTO
    {
        [Key]
        public int ID { get; set; }
        public string DepartmentName { get; set; }
        public string Location { get; set; }
        public int? ParentID { get; set; }
        [Association("MyRef", "ParentID", "ID", IsForeignKey = true)]
        public DepartmentDTO ParentDepartment { get; set; }
        [Association("MyRef", "ID", "ParentID", IsForeignKey = false)]
        public IQueryable<DepartmentDTO> ChildDepartments { get; set; }
        public int ChildrenCount { get; set; }
    }
    [EnableClientAccess()]
    public class DepartmentService : LightSwitchDomainServiceBase
    {
        [Query(IsDefault = true)]
        public IQueryable<DepartmentDTO> GetOrders()
        {
            return from d in this.Context.Departments
                   select new DepartmentDTO
                   {
                       ID = d.Id,
                       ParentID = d.Department_Department,
                       DepartmentName = d.DepartmentName,
                       Location = d.Location,
                       ChildrenCount = d.ChildDepartments.Count()
                   };
        }
    }
}

 

As you can see, the DTO has a ChildrenCount property, which counts the children server side and includes the property in the DepartmentDTO object.

Note also that the DepartmentDTO class needs the correct attributes (the [Association] attribute)  for setting up the hierarchical relationship !

The RIA service is consumed by the GetRoot query in LightSwitch:

 public partial class DepartmentServiceDataService
    {
        partial void GetRoot_PreprocessQuery(ref IQueryable<DepartmentDTO> query)
        {
            query = query.Where(d => d.ParentDepartment.ID == null);

        }
    }

We can use now this ChildrenCount property in our ItemPrepared event:

private void MyRadTreeView_ItemPrepared(object sender, Telerik.Windows.Controls.RadTreeViewItemPreparedEventArgs e)
        {
            var treeViewItem = e.PreparedItem;
            var dataItem = treeViewItem.Item as IEntityObject;

            if ((int)dataItem.Details.Properties["ChildrenCount"].Value <= 0)
            {
                treeViewItem.IsLoadOnDemandEnabled = false;
            }
            else
            {
                treeViewItem.IsLoadOnDemandEnabled = true;
            }
        }

 

Now, only the most important thing is left now: implementing the LoadOnDemand event:

private void MyRadTreeView_LoadOnDemand(object s1, Telerik.Windows.RadRoutedEventArgs e1)
        {
            RadTreeViewItem clickedItem = e1.OriginalSource as RadTreeViewItem;
            var dataItem = clickedItem.Item as IEntityObject;
            IEntityCollectionProperty entityNavigationProp = dataItem.Details.Properties["ChildDepartments"] as IEntityCollectionProperty;
            IExecutableWithResult query = entityNavigationProp.Loader as IExecutableWithResult;

            query.ExecuteCompleted += new EventHandler<ExecuteCompletedEventArgs>((s2, e2) =>
            {
                clickedItem.IsLoadOnDemandEnabled = true;
                clickedItem.ItemsSource = query.Result as IEnumerable;
                clickedItem.IsExpanded = true;
                clickedItem.IsLoadingOnDemand = false;

            });
            query.ExecuteAsync();
        }

In fact the LoadOnDemand event is doing more or less the same as in our previous implementation (that from part 1), but in a much more transparant way. There is no fuzz anylonger with doing things on the right thread, enumerating the collection, storing it in an ObservableCollection, etc. …

We simply retrieve the child collection in an async way and set the ItemSource (and a few other properties) of the clicked treeviewItem to the retrieve child records.

The proof of the pudding is in the eating

Let’s verify now that indeed we have not a much more responsive UI and that data is actually loaded on demand.

As a result, only one call with only the data we need. Let’s click now on a certain root node:

 

Only the direct childs are retrieved and let’s verify what’s in such a child:

 

It contains exactly what we want, the child record with the ChildrenCount property. Nothing more, nothing less !

Conclusion

Setting up load on demand is not that complicated, when using a control that supports it.

In a next post I will demonstrate that the binding of the treeview to the LightSwitch data could be done also directly to the odata service rather than via the typical binding mechanism used between LightSwitch and a custom control.