zoukankan      html  css  js  c++  java
  • 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.

  • 相关阅读:
    为了实现一个函数 clone ,可以对 JavaScript 中 5 种主要的数据类型 (包括 Number、 St「ing 、 Object、 A「「ay、 Boolean )进行值(深)复制。
    说说你对语义化的理解
    vue 根据字符串的长度控制显示的字数超出显示省略号
    加密号码将中间四位改为*
    前端项目部署错误:npm ERR! notarget No matching version found for event-stream@3.3.6
    npm报错:A complete log of this run can be fund in:........
    nrm插件的安装插件和使用
    Vue过渡搭配Velocity.js动画的基本使用
    Asp.Net Core&钉钉开发系列
    KnockoutJS知识规整目录
  • 原文地址:https://www.cnblogs.com/itelite/p/4220417.html
Copyright © 2011-2022 走看看