WPF/SL: lazy loading TreeView
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的更多相关文章
- Angular2+typescript+webpack2(支持aot, tree shaking, lazy loading)
概述 Angular2官方推荐的应该是使用systemjs加载, 但是当我使用到它的tree shaking的时候,发现如果使用systemjs+rollup,只能打包成一个文件,然后lazy loa ...
- Lazyr.js – 延迟加载图片(Lazy Loading)
Lazyr.js 是一个小的.快速的.现代的.相互间无依赖的图片延迟加载库.通过延迟加载图片,让图片出现在(或接近))视窗才加载来提高页面打开速度.这个库通过保持最少选项并最大化速度. 在线演示 ...
- Can you explain Lazy Loading?
Introduction Lazy loading is a concept where we delay the loading of the object until the point wher ...
- [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 ...
- [AngularJS] Lazy loading Angular modules with ocLazyLoad
With the ocLazyLoad you can load AngularJS modules on demand. This is very handy for runtime loading ...
- iOS swift lazy loading
Why bother lazy loading and purging pages, you ask? Well, in this example, it won't matter too much ...
- Entity Framework加载相关实体——延迟加载Lazy Loading、贪婪加载Eager Loading、显示加载Explicit Loading
Entity Framework提供了三种加载相关实体的方法:Lazy Loading,Eager Loading和Explicit Loading.首先我们先来看一下MSDN对三种加载实体方法的定义 ...
- 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 ...
- EFCore Lazy Loading + Inheritance = 干净的数据表 (二) 【献给处女座的DB First程序猿】
前言 本篇是上一篇EFCore Lazy Loading + Inheritance = 干净的数据表 (一) [献给处女座的DB First程序猿] 前菜 的续篇.这一篇才是真的为处女座的DB Fi ...
随机推荐
- Android app 全局异常统一处理
异常处理需求 Android app 出现 crash 时,会出现 "程序异常退出" 的提示并关闭,体验不好,另外主要是无法知道哪里出现的崩溃,需要知道哪里造成的异常,就需要一个全 ...
- 看了一下unity5.6的新功能 以及Timeline
3月31日unity5.6发布,然而timeline(前sequence模块)被delay到unity 2017.上个星期官方又发布了unity 2017的beta版本 抽空看了下 (unity5.6 ...
- 深入浅出HTTPS基本原理
基础知识准备:在了解HTTPS的基本原理之前,需要先了解如下的基本知识. 一.什么是HTTPS,TLS,SSL HTTPS,也称作HTTP over TLS.TLS的前身是SSL,TLS 1.0通常被 ...
- IP地址格式转换(htonl、ntohl;inet_addr、inet_ntoa)
名词解析: 主机字节序: 不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序.最常见的有两种 1.Little endian:低字节存高地址,高字节存低地址 2.Bi ...
- ListView数据更新后,自动滚动到底部(聊天时常用)| Listview Scroll to the end of the list after updating the list
转:http://www.cnblogs.com/bjshsqlt/p/3311830.html If you would like to after you have updated by list ...
- QQ通信原理及QQ是怎么穿透内网进行通信的?
http://blog.csdn.net/frank_good/article/details/51160027 ******************************************* ...
- 【转】如何在Mac上卸载Java及安装Java
如何在 Mac 上卸载 Java? 本文适用于: 平台: Macintosh OS X Java 版本: 7.0, 8.0 使用终端卸载 Oracle Java 注:要卸载 Java,必须具有管理员权 ...
- 从 shell 眼中看世界
(字符) 展开每一次你输入一个命令,然后按下 enter 键,在 bash 执行你的命令之前, bash 会对输入的字符完成几个步骤处理.我们已经知道两三个案例,怎样一个简单的字符序列,例如 “*”, ...
- iOS开发中多线程断点下载大文件
主要思想,就是创建一个与目标文件等大小的空白文件,然后分段往这个空白文件中写入数据. 可以通过发送HEAD请求,获得服务器中文件的具体大小,然后再将这样的长度分割成若干等大的数据块,在发送get请求时 ...
- whatweb wordpress.rb
## # This file is part of WhatWeb and may be subject to # redistribution and commercial restrictions ...