Thursday, July 29, 2010

Silverlight - RadTreeView On demand loading using MVVM

Hi guys, here I am posting a new article for loading the RadTreeView Control on demand (Lazy Loading) using MVVM Pattern.

Lets jump into coding!!!

Create new Silverlight project in Visual studio (I am using silverlight 3.0 with Visual studio 2008 with SP1). My project name is RTVOnDemadLoadingMVVM.

Let visual studio to create web project along with your silverlight project. So You will be having two projects in same solution like below:

sreenshot

Server Side Implementation

Create a table named Tree in one of your SQL Server database using the following script:

   1: CREATE TABLE [dbo].[Tree](
   2:     [ID] [int] IDENTITY(1,1) NOT NULL,
   3:     [Title] [varchar](50) NULL,
   4:     [ParentID] [int] NULL,
   5:  CONSTRAINT [PK_Tree] PRIMARY KEY CLUSTERED 
   6: (
   7:     [ID] ASC
   8: )WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
   9: ) ON [PRIMARY]

Create two folders in RTVOnDemadLoadingMVVM.Web project


  1. Data
  2. WCFServices

Add a LINQ to SQL Classes named TreeDataClasses.dbml in your data folder.

Go to Server explorer and connect to the database containing table Tree.

Once it is connected, drag and drop the Tree table to the TreeDataClasses.dbml work space.

Add a WCF service file to your WCFServices folder, I have given WCFService.svc as name.

Go to your web.config file and change binding="wsHttpBinding" to binding="basicHttpBinding"

Following is the code for my service project:

IWCFService.cs



   1: [ServiceContract]
   2: public interface IWCFService
   3: {
   4:     [OperationContract]
   5:     List<Tree> GetItemsByID(int parentID);
   6: }

WCFService.svc.cs



   1: public class WCFService : IWCFService
   2: {
   3:     public List<Tree> GetItemsByID(int parentID)
   4:     {
   5:         TreeDataClassesDataContext db = new TreeDataClassesDataContext();
   6:         List<Tree> result = db.Trees.Where(r => r.ParentID == parentID).ToList<Tree>();
   7:         return result;
   8:     }
   9: }

Client Side Implementation


If you are done with the server side add the service reference in the silverlight project.

Right click on add reference => Add Service Reference => Click on Discover, Once the service is listed, select the service and give a name for your reference. I have given TreeWCFService.

As I am using MVVM pattern in my project, Lets create 3 folders in Silverlight project


  1. Library
  2. Model
  3. ViewModel
  4. View

In Library you will be having two classes for handling the Expanding Command and Click Command. For this we need to create separate dependency properties.


  1. RadTreeViewItemExpanded.cs
  2. RadTreeViewItemSelected.cs

Following is the code for RadTreeViewItemExpanded.cs



   1: public class ItemExpandedCommandBehavior : CommandBehaviorBase<RadTreeView>
   2: {
   3:    public ItemExpandedCommandBehavior(RadTreeView targetObject)
   4:        : base(targetObject)
   5:    {
   6:        targetObject.LoadOnDemand += new EventHandler<Telerik.Windows.RadRoutedEventArgs>(targetObject_LoadOnDemand);
   7:    }
   8:  
   9:    void targetObject_LoadOnDemand(object sender, Telerik.Windows.RadRoutedEventArgs e)
  10:    {
  11:        RadTreeViewItem item = e.OriginalSource as RadTreeViewItem;
  12:        base.CommandParameter = item.Item;
  13:        base.ExecuteCommand();
  14:    }
  15: }
  16:  
  17: public static class ItemExpanded
  18: {
  19:    public static readonly DependencyProperty ItemExpandedBehaviorProperty =
  20:        DependencyProperty.RegisterAttached(
  21:            "ItemExpandedBehaviorProperty", typeof(ItemExpandedCommandBehavior),
  22:            typeof(ItemExpandedCommandBehavior), null);
  23:  
  24:    #region CommandProperty
  25:  
  26:    public static readonly DependencyProperty CommandProperty =
  27:        DependencyProperty.RegisterAttached(
  28:            "Command", typeof(ICommand), typeof(ItemExpanded),
  29:            new PropertyMetadata(CommandProperty_Changed));
  30:  
  31:    public static ICommand GetCommand(DependencyObject obj)
  32:    {
  33:        return (ICommand)obj.GetValue(CommandProperty);
  34:    }
  35:  
  36:    public static void SetCommand(DependencyObject obj, ICommand value)
  37:    {
  38:        obj.SetValue(CommandProperty, value);
  39:    }
  40:  
  41:    private static void CommandProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
  42:    {
  43:        RadTreeView targetObject = dependencyObject as RadTreeView;
  44:  
  45:        if (targetObject != null)
  46:            GetOrCreateBehavior(targetObject).Command = e.NewValue as ICommand;
  47:    }
  48:  
  49:    #endregion
  50:  
  51:    private static ItemExpandedCommandBehavior GetOrCreateBehavior(RadTreeView targetObject)
  52:    {
  53:        ItemExpandedCommandBehavior behavior = targetObject.GetValue(ItemExpandedBehaviorProperty) as ItemExpandedCommandBehavior;
  54:  
  55:        if (behavior == null)
  56:        {
  57:            behavior = new ItemExpandedCommandBehavior(targetObject);
  58:            targetObject.SetValue(ItemExpandedBehaviorProperty, behavior);
  59:        }
  60:  
  61:        return behavior;
  62:    }
  63: }

Follwing is the code for RadTreeViewItemSelected.cs



   1: public class ItemClickedCommandBehavior : CommandBehaviorBase<RadTreeView>
   2: {
   3:     public ItemClickedCommandBehavior(RadTreeView targetObject)
   4:         : base(targetObject)
   5:     {
   6:         targetObject.Selected += new EventHandler<Telerik.Windows.RadRoutedEventArgs>(targetObject_Selected);
   7:     }
   8:  
   9:     void targetObject_Selected(object sender, Telerik.Windows.RadRoutedEventArgs e)
  10:     {
  11:         RadTreeViewItem raTVItem = e.OriginalSource as RadTreeViewItem;
  12:         base.CommandParameter = raTVItem.Item;
  13:         base.ExecuteCommand();
  14:     }
  15: }
  16:  
  17: public static class ItemClicked
  18: {
  19:     public static readonly DependencyProperty ItemClickedBehaviorProperty =
  20:         DependencyProperty.RegisterAttached(
  21:             "ItemClickedBehaviorProperty", typeof(ItemClickedCommandBehavior),
  22:             typeof(ItemClickedCommandBehavior), null);
  23:  
  24:     #region CommandProperty
  25:  
  26:     public static readonly DependencyProperty CommandProperty =
  27:         DependencyProperty.RegisterAttached(
  28:             "Command", typeof(ICommand), typeof(ItemClicked),
  29:             new PropertyMetadata(CommandProperty_Changed));
  30:  
  31:     public static ICommand GetCommand(DependencyObject obj)
  32:     {
  33:         return (ICommand)obj.GetValue(CommandProperty);
  34:     }
  35:  
  36:     public static void SetCommand(DependencyObject obj, ICommand value)
  37:     {
  38:         obj.SetValue(CommandProperty, value);
  39:     }
  40:  
  41:     private static void CommandProperty_Changed(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
  42:     {
  43:         RadTreeView targetObject = dependencyObject as RadTreeView;
  44:  
  45:         if (targetObject != null)
  46:             GetOrCreateBehavior(targetObject).Command = e.NewValue as ICommand;
  47:     }
  48:  
  49:     #endregion
  50:  
  51:     private static ItemClickedCommandBehavior GetOrCreateBehavior(RadTreeView targetObject)
  52:     {
  53:         ItemClickedCommandBehavior behavior = targetObject.GetValue(ItemClickedBehaviorProperty) as ItemClickedCommandBehavior;
  54:  
  55:         if (behavior == null)
  56:         {
  57:             behavior = new ItemClickedCommandBehavior(targetObject);
  58:             targetObject.SetValue(ItemClickedBehaviorProperty, behavior);
  59:         }
  60:  
  61:         return behavior;
  62:     }
  63: }

In model create a model class file called RTVItem.cs which will represent each node of the tree view. Following is the code for my model class:



   1: public class RTVItem : INotifyPropertyChanged
   2: {
   3:     #region Private Variables
   4:     private int _id;
   5:     private string _title;
   6:     private ObservableCollection<RTVItem> _rTVItems;
   7:     #endregion
   8:  
   9:     #region Public Properties
  10:     public int ID
  11:     {
  12:         get
  13:         {
  14:             return _id;
  15:         }
  16:         set
  17:         {
  18:             _id = value;
  19:             NotifyPropertyChanged("ID");
  20:         }
  21:     }
  22:     public string Title
  23:     {
  24:         get
  25:         {
  26:             return _title;
  27:         }
  28:         set
  29:         {
  30:             _title = value;
  31:             NotifyPropertyChanged("Title");
  32:         }
  33:     }
  34:     public ObservableCollection<RTVItem> RTVItems
  35:     {
  36:         get
  37:         {
  38:             return _rTVItems;
  39:         }
  40:         set
  41:         {
  42:             _rTVItems = value;
  43:             NotifyPropertyChanged("RTVItems");
  44:         }
  45:     }
  46:     #endregion
  47:     
  48:     #region INotifyPropertyChanged Members
  49:     
  50:     public event PropertyChangedEventHandler PropertyChanged;
  51:     private void NotifyPropertyChanged(String propertyName)
  52:     {
  53:         if (PropertyChanged != null)
  54:         {
  55:             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  56:         }
  57:     }
  58:     
  59:     #endregion
  60: }

In View Model folder create a class called ViewModelRTV.cs, here you can implement your logic for populating the tree from the database. Following is the code for the view model class:



   1: public class ViewModelRTV : INotifyPropertyChanged
   2: {
   3:     #region Private Variables
   4:     private ObservableCollection<RTVItem> _rTVItems;
   5:     private RTVItem _selectedRTVItem;
   6:     #endregion
   7:  
   8:     #region Public Properties
   9:  
  10:     public RTVItem SelectedRTVItem
  11:     {
  12:         get
  13:         {
  14:             return _selectedRTVItem;
  15:         }
  16:         set
  17:         {
  18:             _selectedRTVItem = value;
  19:             NotifyPropertyChanged("SelectedRTVItem");
  20:         }
  21:     }
  22:  
  23:     public ObservableCollection<RTVItem> RTVItems
  24:     {
  25:         get
  26:         {
  27:             return _rTVItems;
  28:         }
  29:         set
  30:         {
  31:             _rTVItems = value;
  32:             NotifyPropertyChanged("RTVItems");
  33:         }
  34:     }
  35:  
  36:     public ICommand OnExpanded { get; set; }
  37:     public ICommand OnItemClick { get; set; }
  38:     #endregion
  39:  
  40:     #region Constructor(s)
  41:     public ViewModelRTV()
  42:     {
  43:         this.RTVItems = new ObservableCollection<RTVItem>();
  44:  
  45:         this.RTVItems.Add(new RTVItem
  46:         {
  47:             Title = "I am the Master",
  48:             RTVItems = new ObservableCollection<RTVItem>()
  49:         });
  50:  
  51:         SelectedRTVItem = RTVItems[0];
  52:         this.OnExpanded = new DelegateCommand<object>(o => this.OnExpanded_Click(o));
  53:         this.OnItemClick = new DelegateCommand<object>(o => this.OnItemClick_Click(o));
  54:     }
  55:     #endregion
  56:  
  57:     #region Member Functions
  58:     private void OnExpanded_Click(object argument)
  59:     {
  60:         // implememntation for device group tree structure
  61:  
  62:         SelectedRTVItem = argument as RTVItem;
  63:         WCFServiceClient _sClient = new WCFServiceClient();
  64:         _sClient.GetItemsByIDCompleted += new EventHandler<GetItemsByIDCompletedEventArgs>(_sClient_GetItemsByIDCompleted);
  65:         _sClient.GetItemsByIDAsync(SelectedRTVItem.ID);
  66:     }
  67:  
  68:     void _sClient_GetItemsByIDCompleted(object sender, GetItemsByIDCompletedEventArgs e)
  69:     {
  70:         if (e.Result != null)
  71:         {
  72:             ObservableCollection<Tree> listTreeItems = (ObservableCollection<Tree>)e.Result;
  73:             foreach (Tree item in listTreeItems)
  74:             {
  75:                 SelectedRTVItem.RTVItems.Add(new RTVItem
  76:                 {
  77:                     Title = item.Title,
  78:                     RTVItems = new ObservableCollection<RTVItem>(),
  79:                     ID = item.ID,
  80:                 });
  81:             }
  82:         }
  83:     }
  84:  
  85:     private void OnItemClick_Click(object argument)
  86:     {
  87:         this.SelectedRTVItem = argument as RTVItem;
  88:         // implement code for expanding tree (lazy loading)
  89:     }
  90:  
  91:     #endregion
  92:  
  93:     #region INotifyPropertyChanged Members
  94:     public event PropertyChangedEventHandler PropertyChanged;
  95:     private void NotifyPropertyChanged(String propertyName)
  96:     {
  97:         if (PropertyChanged != null)
  98:         {
  99:             PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
 100:         }
 101:     }
 102:     #endregion
 103: }

In View create a Silverlight user control named UCRadTreeView.xaml, here you will the define the UI for your application in XAML. Following is the xaml code for the View:



   1: <UserControl.Resources>
   2:     <Style x:Key="ExpanderStyle1" TargetType="ToggleButton">
   3:         <Setter Property="IsEnabled" Value="True" />
   4:         <Setter Property="IsTabStop" Value="False" />
   5:         <Setter Property="Cursor" Value="Hand" />
   6:         <Setter Property="Template">
   7:             <Setter.Value>
   8:                 <ControlTemplate TargetType="ToggleButton">
   9:                     <Grid>
  10:                         <VisualStateManager.VisualStateGroups>
  11:                             <VisualStateGroup x:Name="CommonStates">
  12:                                 <VisualState x:Name="Normal">
  13:  
  14:                                 </VisualState>
  15:                                 <VisualState x:Name="MouseOver">
  16:                                     <Storyboard>
  17:                                         <DoubleAnimation Duration="0:0:0.05"
  18:                                                 Storyboard.TargetName="Button"
  19:                                                 Storyboard.TargetProperty="Opacity" To="0" />
  20:                                         <DoubleAnimation Duration="0:0:0.05"
  21:                                                 Storyboard.TargetName="ButtonOver"
  22:                                                 Storyboard.TargetProperty="Opacity" To="1" />
  23:                                     </Storyboard>
  24:                                 </VisualState>
  25:                             </VisualStateGroup>
  26:                             <VisualStateGroup x:Name="CheckStates">
  27:                                 <VisualState x:Name="Checked">
  28:                                     <Storyboard>
  29:                                         <DoubleAnimation Duration="0:0:0.05"
  30:                                                 Storyboard.TargetName="CollapsedVisual"
  31:                                                 Storyboard.TargetProperty="Opacity" To="0" />
  32:                                         <DoubleAnimation Duration="0:0:0.05"
  33:                                                 Storyboard.TargetName="CollapsedVisualOver"
  34:                                                 Storyboard.TargetProperty="Opacity" To="0" />
  35:                                     </Storyboard>
  36:                                 </VisualState>
  37:  
  38:                                 <VisualState x:Name="Unchecked">
  39:                                     <Storyboard>
  40:                                         <DoubleAnimation Duration="0:0:0.05"
  41:                                                 Storyboard.TargetName="CollapsedVisual"
  42:                                                 Storyboard.TargetProperty="Opacity" To="1" />
  43:                                         <DoubleAnimation Duration="0:0:0.05"
  44:                                                 Storyboard.TargetName="CollapsedVisualOver"
  45:                                                 Storyboard.TargetProperty="Opacity" To="1" />
  46:                                     </Storyboard>
  47:                                 </VisualState>
  48:                             </VisualStateGroup>
  49:                         </VisualStateManager.VisualStateGroups>
  50:  
  51:                         <Grid x:Name="Button" Margin="0,7,4,0" HorizontalAlignment="Right"
  52:                                 VerticalAlignment="Top" Width="11" Height="11">
  53:                             <Grid.Background>
  54:                                 <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  55:                                     <GradientStop Color="#3F047BA5" Offset="0.996" />
  56:                                     <GradientStop Color="#00000000" Offset="0" />
  57:                                 </LinearGradientBrush>
  58:                             </Grid.Background>
  59:                             <Rectangle Stroke="#FF000000" HorizontalAlignment="Left"
  60:                                     VerticalAlignment="Top" Width="11" Height="11" />
  61:  
  62:                             <Rectangle x:Name="CollapsedVisual" Fill="#FF000000"
  63:                                     HorizontalAlignment="Left" VerticalAlignment="Top" Width="1"
  64:                                     Height="5" Margin="5,3,0,0" />
  65:                             <Rectangle Fill="#FF000000" VerticalAlignment="Top"
  66:                                     HorizontalAlignment="Left" Height="1" Width="5"
  67:                                     Margin="3,5,0,0" />
  68:                         </Grid>
  69:  
  70:                         <Grid x:Name="ButtonOver" Margin="0,7,4,0" HorizontalAlignment="Right"
  71:                                 VerticalAlignment="Top" Width="11" Height="11">
  72:                             <Rectangle Stroke="#FF167497" HorizontalAlignment="Left"
  73:                                     VerticalAlignment="Top" Width="11" Height="11">
  74:                                 <Rectangle.Fill>
  75:                                     <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
  76:                                         <GradientStop Color="#26167497" Offset="1" />
  77:                                         <GradientStop Color="#00167497" Offset="0" />
  78:                                     </LinearGradientBrush>
  79:                                 </Rectangle.Fill>
  80:                             </Rectangle>
  81:                             <Rectangle x:Name="CollapsedVisualOver" Fill="#FF167497"
  82:                                     HorizontalAlignment="Left" VerticalAlignment="Top" Width="1"
  83:                                     Height="5" Margin="5,3,0,0" />
  84:                             <Rectangle Fill="#FF167497" VerticalAlignment="Top"
  85:                                     HorizontalAlignment="Left" Height="1" Width="5"
  86:                                     Margin="3,5,0,0" />
  87:                         </Grid>
  88:                     </Grid>
  89:                 </ControlTemplate>
  90:             </Setter.Value>
  91:         </Setter>
  92:     </Style>
  93: </UserControl.Resources>
  94: <Grid x:Name="LayoutRoot" Background="White">
  95:     <Grid.ColumnDefinitions>
  96:         <ColumnDefinition/>
  97:         <ColumnDefinition/>
  98:     </Grid.ColumnDefinitions>
  99:     <telerikNavigation:RadTreeView 
 100:         dpprop:ItemExpanded.Command="{Binding OnExpanded}" 
 101:         dpprop:ItemClicked.Command="{Binding OnItemClick}" 
 102:         IsLineEnabled="True" 
 103:         ItemsSource="{Binding RTVItems}" 
 104:         ExpanderStyle="{StaticResource ExpanderStyle1}" 
 105:         IsLoadOnDemandEnabled="True"
 106:         Grid.Column="0"
 107:         >
 108:         <telerikNavigation:RadTreeView.ItemTemplate>
 109:             <telerik:HierarchicalDataTemplate ItemsSource="{Binding RTVItems}">
 110:                 <StackPanel>
 111:                     <TextBlock Text="{Binding Title}" />
 112:                 </StackPanel>
 113:             </telerik:HierarchicalDataTemplate>
 114:         </telerikNavigation:RadTreeView.ItemTemplate>
 115:     </telerikNavigation:RadTreeView>
 116:     <telerikNavigation:RadTreeView 
 117:         dpprop:ItemExpanded.Command="{Binding OnExpanded}" 
 118:         dpprop:ItemClicked.Command="{Binding OnItemClick}" 
 119:         IsLineEnabled="True" 
 120:         ItemsSource="{Binding ClientDeviceGroups}" 
 121:         ExpanderStyle="{StaticResource ExpanderStyle1}" 
 122:         IsLoadOnDemandEnabled="True"
 123:         Grid.Column="1"
 124:         >
 125:         <telerikNavigation:RadTreeView.ItemTemplate>
 126:             <telerik:HierarchicalDataTemplate ItemsSource="{Binding ClientDeviceGroups}">
 127:                 <StackPanel>
 128:                     <TextBlock Text="{Binding DeviceGroupName}" />
 129:                 </StackPanel>
 130:             </telerik:HierarchicalDataTemplate>
 131:         </telerikNavigation:RadTreeView.ItemTemplate>
 132:     </telerikNavigation:RadTreeView>
 133: </Grid>

Include the UCRadTreeView.xaml in MainPage.xaml. Following is the code for MainPage.xaml:



   1: <UserControl x:Class="RTVOnDemadLoadingMVVM.MainPage"
   2:     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
   3:     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   4:     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
   5:     xmlns:local="clr-namespace:RTVOnDemadLoadingMVVM.View"
   6:     mc:Ignorable="d" d:DesignWidth="640" d:DesignHeight="480">
   7:   <Grid x:Name="LayoutRoot">
   8:         <local:UCRadTreeView></local:UCRadTreeView>
   9:     </Grid>
  10: </UserControl>

Initialize your view model and set the data context of view in MainPage.xaml.cs. Following is the code for MainPage.xaml.cs:



   1: public partial class MainPage : UserControl
   2: {
   3:     public ViewModelRTV _vMRTV;
   4:     public MainPage()
   5:     {
   6:         InitializeComponent();
   7:         _vMRTV = new ViewModelRTV();
   8:         this.DataContext = _vMRTV;
   9:     }
  10: }

Press F5………. You are done!!!!


Click here to find the complete source code for this article.


If you find any problem, please feel free to ask questions!!!


Thanks Guys!!!