Posted on January 25, 2012 by Matthieu MEZIL

01/26/2012: Code update

Imagine the following scenario: you have a WCF service with two methods:

List<Customer> GetCustomers();
List<Order> GetOrders(int CustomerId);

You want a treeview with lazy loading in a WPF Window.

There is many way to do it.

I identify three main in my searches:

  • you can use event on your treeview implemented in code-behind

  • you can makes your TreeView control inheriting the framework one’s
  • you can use all the logic on ViewModels and use binding

The last point is realized by adding a CustomerViewModel, having a collection of CustomerViewModel in the VM that encapsulated a Customer and adding IsExpanded property and add the logic of loading orders.

It’s a way often saw in the web and that seems a good way with MVVM for many developers but I think, IMHO, it is NOT a good way.

Indeed, what happens if under Orders, I want OrderDetails? You will add a new OrderViewModel class that encapsulates an Order and the CustomerViewModel class will have a collection of OrderViewModel?

I don’t want to make again my Model in my ViewModel.

I could use the ICustomTypeDescriptor (ICustomTypeProvider in SL) as I did here but I think that if this solution is interesting to add business logic on entity, it is not to add control logic.

I think that the lazy loading control logic should be encapsulated in a behavior and the ViewModel should just have the lazy loading WCF calls logic.

So, I use an ILazyLoader interface:

public interface ILazyLoader
{

string GetChildPropertyName(object obj);

    bool IsLoaded(object obj);
    void Load(object obj);

}

and an implementation of it using delegate:

public class LazyLoader : ILazyLoader
{
    private Func<object, string> _getChildPropertyName;
    private Func<object, bool> _isLoaded;
    private Action<object> _load;
 
    public LazyLoader(Func<object, string> getChildPropertyName, Func<object, bool> isLoaded, Action<object> load)
    {
        _getChildPropertyName = getChildPropertyName;
        _isLoaded = isLoaded;
        _load = load;
    }
 
    public string GetChildPropertyName(object obj)
    {
        return _getChildPropertyName(obj);
    }
 
    public bool IsLoaded(object obj)
    {
        return _isLoaded(obj);
    }
 
    public void Load(object obj)
    {
        _load(obj);
    }

}

Then, in my ViewModel, I use the following code:

public class CustomerViewModel
{
    private ObservableCollection<Customer> _customers;
    public ObservableCollection<Customer> Customers
    {
        get
        {
            if (_customers == null)
            {
                _customers = new ObservableCollection<Customer>();
                var customersService = new CustomerServiceClient();
                EventHandler<GetCustomersCompletedEventArgs> serviceGetCustomersCompleted = null;
                serviceGetCustomersCompleted = (sender, e) =>
                    {
                        customersService.GetCustomersCompleted -= serviceGetCustomersCompleted;
                        foreach (var ht in e.Result)
                            _customers.Add(ht);
                    };
                customersService.GetCustomersCompleted += serviceGetCustomersCompleted;
                customersService.GetCustomersAsync();
            }
            return _customers;
        }
    }
 
    private ILazyLoader _lazyLoader;
    public ILazyLoader LazyLoader
    {
        get { return _lazyLoader ?? (_lazyLoader = new LazyLoader(obj => 
            {
                if (obj is HardwareType)
                    return PropertyName.GetPropertyName((Expression<Func<HardwareType, object>>)(ht => ht.Hardwares));
                return null;
            }, obj => _loadedHardwareTypes.Contains((HardwareType)obj), obj => LoadHardwares((HardwareType)obj))); }

}

 
    private List<Customer> _loadedCustomers = new List<Customer>();
    private void LoadOrders(Customer c)
    {
        var customerService = new CustomerServiceClient();
        c.Orders.Clear();
        EventHandler<GetOrdersCompletedEventArgs> serviceGetOrdersCompleted = null;
        serviceGetOrdersCompleted = (sender, e) =>
        {
            customerService.GetOrdersCompleted -= serviceGetOrdersCompleted;
            foreach (var o in e.Result)
                c.Orders.Add(o);
            _loadedCustomers.Add(c);
        };
        customerService.GetOrdersCompleted += serviceGetCustomersCompleted;
        customerService.GetOrdersAsync(c.Id);
    }

}

Now, this is the code of my behavior:

public static class LazyLoadTreeViewItemBehavior
{
    public static ILazyLoader GetLazyLoader(DependencyObject obj)
    {
        return (ILazyLoader)obj.GetValue(LazyLoaderProperty);
    }
    public static void SetLazyLoader(DependencyObject obj, ILazyLoader value)
    {
        obj.SetValue(LazyLoaderProperty, value);
    }
    public static readonly DependencyProperty LazyLoaderProperty =
        DependencyProperty.RegisterAttached("LazyLoader", typeof(ILazyLoader), typeof(LazyLoadTreeViewItemBehavior), new PropertyMetadata(ApplyingLazyLoadingLogic));
 
    private static void ApplyingLazyLoadingLogic(DependencyObject o, DependencyPropertyChangedEventArgs e)
    {
        var tvi = o as TreeViewItem;
        if (tvi == null)
            throw new InvalidOperationException();
        ILazyLoader lazyLoader= GetLazyLoader(o);
        PropertyInfo childrenProp;
        if (lazyLoader == null)
            return;
        object itemValue = tvi.DataContext;
        string childrenPropName = lazyLoader.GetChildPropertyName(itemValue);
        if (childrenPropName == null || (childrenProp = itemValue.GetType().GetProperty(childrenPropName)) == null)
            return;
        IEnumerable children = (IEnumerable)childrenProp.GetValue(itemValue, null);
        RoutedEventHandler tviExpanded = null;
        RoutedEventHandler tviUnloaded = null;
        tviExpanded = (sender, e2) =>
            {
                tvi.Expanded -= tviExpanded;
                tvi.Unloaded -= tviUnloaded;
if (!lazyLoader.IsLoaded(itemValue))
                {
                    lazyLoader.Load(itemValue);
                    tvi.Items.Clear();
                    tvi.ItemsSource = children;
                }
            };
        tviUnloaded = (sender, e2) =>
            {
                tvi.Expanded -= tviExpanded;
                tvi.Unloaded -= tviUnloaded;
            };
        if (!children.GetEnumerator().MoveNext())
        {
            tvi.ItemsSource = null;
            tvi.Items.Add(new TreeViewItem());
        }
        tvi.Expanded += tviExpanded;
        tvi.Unloaded += tviUnloaded;

}
}

The thing very interesting with it is the fact that my behavior is not dependent of my model or my ViewModel and can be used with other lazy loading TreeViews.

To do it, I just have to apply our behavior into our TreeView, what can be done in xaml:

<TreeView ItemsSource="{Binding Customers}">
    <TreeView.ItemContainerStyle>
        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="local:LazyLoadTreeViewItemBehavior.LazyLoader" 
                    Value="{Binding DataContext.LazyLoader, RelativeSource={RelativeSource AncestorType=local:CustomersWindow}}" />
        </Style>
    </TreeView.ItemContainerStyle>
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate>
            <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate>
                    …
                </DataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
            …
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>

</TreeView>

I really like this way. What do you think about it?

Of course, I write my sample with WPF but it’s still true with SL.

Hope this helps…

This entry was posted in 13461, 7671, 8708. Bookmark the permalink.

WPF/SL: lazy loading TreeView的更多相关文章

  1. Angular2+typescript+webpack2(支持aot, tree shaking, lazy loading)

    概述 Angular2官方推荐的应该是使用systemjs加载, 但是当我使用到它的tree shaking的时候,发现如果使用systemjs+rollup,只能打包成一个文件,然后lazy loa ...

  2. Lazyr.js – 延迟加载图片(Lazy Loading)

    Lazyr.js 是一个小的.快速的.现代的.相互间无依赖的图片延迟加载库.通过延迟加载图片,让图片出现在(或接近))视窗才加载来提高页面打开速度.这个库通过保持最少选项并最大化速度. 在线演示    ...

  3. Can you explain Lazy Loading?

    Introduction Lazy loading is a concept where we delay the loading of the object until the point wher ...

  4. [AngularJS] Lazy Loading modules with ui-router and ocLazyLoad

    We've looked at lazy loading with ocLazyLoad previously, but what if we are using ui-router and want ...

  5. [AngularJS] Lazy loading Angular modules with ocLazyLoad

    With the ocLazyLoad you can load AngularJS modules on demand. This is very handy for runtime loading ...

  6. iOS swift lazy loading

    Why bother lazy loading and purging pages, you ask? Well, in this example, it won't matter too much ...

  7. Entity Framework加载相关实体——延迟加载Lazy Loading、贪婪加载Eager Loading、显示加载Explicit Loading

    Entity Framework提供了三种加载相关实体的方法:Lazy Loading,Eager Loading和Explicit Loading.首先我们先来看一下MSDN对三种加载实体方法的定义 ...

  8. Lazy Loading | Explicit Loading | Eager Loading in EntityFramework and EntityFramework.Core

    EntityFramework Eagerly Loading Eager loading is the process whereby a query for one type of entity ...

  9. EFCore Lazy Loading + Inheritance = 干净的数据表 (二) 【献给处女座的DB First程序猿】

    前言 本篇是上一篇EFCore Lazy Loading + Inheritance = 干净的数据表 (一) [献给处女座的DB First程序猿] 前菜 的续篇.这一篇才是真的为处女座的DB Fi ...

随机推荐

  1. 绝对路径${pageContext.request.contextPath}用法及其与web.xml中Servlet的url-pattern匹配过程

    以系统的一个“添加商品”的功能为例加以说明,系统页面为add.jsp,如图一所示: 图一  添加商品界面 系统的代码目录结构及add.jsp代码如图二所示: 图二   系统的代码目录结构及add.js ...

  2. [na]TCP的三次握手四次挥手/SYN泛洪

    1.TCP报文格式 上图中有几个字段需要重点介绍下: (1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记. (2)确认序号:Ack序号,占32位, ...

  3. 压力测试工具ab及centos下单独安装方法 nginx和tomcat静态资源的性能测试

    Apache安装包中自带的压力测试工具Apache Benchmark(简称ab)简单易用,这里采用ab作为压国测试工具. 独立安装: ab运行需要信赖apr-util包: # yum install ...

  4. OpenStack的基本概念与架构图

    https://blog.csdn.net/zjluobing/article/details/51489325 OpenStack项目是一个开源的云计算平台,旨在实现很简单,大规模可伸缩,功能丰富. ...

  5. HTTPS演变小图

    HTTP就是我们平时浏览网页时候使用的一种协议.HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全.为了保证这些隐私数据能加密传输,网景公司设计了SSL(Se ...

  6. Lua 自己实现排序sort比较方法,抛出错误invalid order function for sorting

    明天新功能就要上了,结果刚刚突然QA说项目抛出了错误.握草,吓得立马出了一身汗. 查了一下错误,发现可能是自己写的不稳定排序造成的.自己感觉应该就是.把排序方法写成稳定的之后,代码分离编译进手机,跑了 ...

  7. C语言版——点亮LED灯,深入到栈

    在上一篇进行了汇编语言的编写之后,我们采用C语言来编写程序,毕竟C语言才是我们使用最多的语言. 仅仅是点亮LED灯显然太过于简单,我们需要分析最后的反汇编,了解函数调用栈,深入C语言骨髓去分析代码,并 ...

  8. Android开发(六)——组件颜色Selector(Selector与Shape的基本用法 )

    andorid控件改变状态时改变颜色,使用selector. <?xml version="1.0" encoding="utf-8" ?> < ...

  9. 正则表达式-python-无捕获分组与分支选择

    无捕获分组 当你要将一部分规则作为一个整体对它进行某些操作,比如指定其重复次数时,你需要将这部分规则用 (?:) 把它包围起来. 分支条件 在正则表达式中,分支条件是一个很常用的条件. 满足条件A 或 ...

  10. JAVA-JSP内置对象之config对象

    相关资料:<21天学通Java Web开发> config对象1.config对象可以用来获得Servlet的配置信息. 方法                               ...