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. java空指针异常

    我们都知道java是没有指针的,这里说的"java指针"指的就是java的引用,我们不在这里讨论叫指针究竟合不合适,而只是针对这个异常本身进行分析.空指针就是空引用,java空指针 ...

  2. Atitti html5 h5 新特性attilax总结

    Atitti html5 h5 新特性attilax总结 Attilax觉得不错的新特性 3.语义Header和Footer (The Semantic Header and Footer) 8.占位 ...

  3. NGINX宏观手记(变量|配置)

    前言 任何一个工具都有它的灵魂所在,作为一个PHP程序员,我们可能仅仅使用了它的一小部分,这篇文章让你更加了解Nginx,本章大多都是总结.翻译.整理 ,希望你可以知道nginx不仅仅是PHP的附属品 ...

  4. angular学习笔记(三十)-指令(8)-scope

    本篇讲解指令的scope属性: scope属性值可以有三种: 一.scope:false 默认值,这种情况下,指令的作用域就是指令元素当前所在的作用域. 二.scope:true 创建一个继承了父作用 ...

  5. [Windows Azure] Learn SQL Reporting on Windows Azure (9-Step Tutorial)

    Learn SQL Reporting on Windows Azure (9-Step Tutorial) 4 out of 4 rated this helpful - Rate this top ...

  6. 每天一个linux命令(6) ar命令

    当我们的程序中有经常使用的模块,而且这种模块在其他程序中也会用到,这时按照软件重用的思想,我们应该将它们生成库,使得以后编程可以减少开发代码量.这里介绍命令ar,用来对库操作. ar命令可以用来创建. ...

  7. 每天一个linux命令(3):du命令

    Linux du命令也是查看使用空间的,但是与df命令不同的是Linux du命令是查看当前指定文件或目录(会递归显示子目录)占用磁盘空间大小,还是和df命令有一些区别的. 1.命令格式: du [选 ...

  8. Recommender Systems中Yehuda Koren 和 Ma Hao的paper

    以前读了Yehuda Koren和Ma Hao的论文,感觉非常不错,这里分享一下.如果想着具体了解他们近期发的论文,可以去DBLP去看看. Yehuda Koren也是Netflix Prize的冠军 ...

  9. Python Import机制

    最近在看<Python源码剖析>,对Python内部运行机制比以前了解的更深入了,感觉自己有机会也可以做个小型的动态脚本语言了,呵呵,当然是吹牛了.目的当然不是创造一个动态语言,目的只有一 ...

  10. Oracle字段类型及存储(一)

    Oracle中2000个byte,并不是2000个字符的意思,1个字符在Oracle中可能是1个byte到4个byte不等,需看数据库字符集的设置了. 对GBK字符集而言,ASCII码中128个字符使 ...