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. 修改 Semantic UI 中对 Google 字体的引用

    在第一次尝试 Semantic UI 后,发现其 css 中第一行,就引用了 fonts.googleapis.com 中的字体. 不知道为什么要这么做,也许在国外,google 的服务已经是一种互联 ...

  2. 一种解决新版本API完全兼容老版本API的方法

    原文:http://android.eoe.cn/topic/android_sdk 这节课程我们讨论如何创建一个实现类,即能对应新版本的API,又能够保持对老版本API的支持. * 寻找一个替代的解 ...

  3. JasperReport学习札记6-JRXML的标签

    原文源于:http://langhua9527.iteye.com/blog/402317 JasperReport学习笔记6-JRXML的标签1.<jasperReport>根元素包括很 ...

  4. 一条SQL语句中算日销售额和月销售额

    刚刚做项目的时候用到的 用户表:用户ID,用户名,余额 流水表:时间,用户ID,用户名,类型(0充值,1消费),变更金额 现在要查每个用户的日销售额和月销售额,本来最简单的方法是先把所有用户查出来,然 ...

  5. 绑定DDL控件方法

    刚刚写的,用在项目中,先记下来,备忘: 下面是调用方式:

  6. IOS开发----生成静态库(.a)

    由于iPhone控件的极度匮乏和自定义组件在重用上的限制,在过去的项目中我们积累了大量的“纯代码”组件——因为IB本身的限制,我们无法把这些组件封装为IB组件库(本来我们想通过分发xib文件的方式重用 ...

  7. webBrowser获取取Cookie不全的解决方法

    //取当前webBrowser登录后的Cookie值           [DllImport("wininet.dll", CharSet = CharSet.Auto, Set ...

  8. 正确理解web交互中的cookie与session

    cookie存储在客户端的纯文本文件 用户请求服务器脚本 脚本设置cookie内容 并 通过http-response发送cookie内容到客户端并保存在客户端本地 客户端再次发送http请求的时候会 ...

  9. shell(7):四则运算

    shell 四则运算:expr,let,bc,(()), 1.expr的用法: expr命令是一个手工命令行计数器,用于在UNIX/LINUX下求表达式变量的值,一般用于整数值,也可用于字符串. 详细 ...

  10. 【转】Unity3D中脚本的执行顺序和编译顺序

    支持原文,原文请戳: Unity3D中脚本的执行顺序和编译顺序 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与脚本有关的也就是编译和执行啦 ...