• WPF:间接支持虚拟化的ListBox


        /// <summary>
        /// 间接实现了虚拟化的ListBox
        /// 子项必须实现IVisible接口
        /// 你可以在IsVisible发生改变时实现一系列自定义动作
        /// 比如:当IsVisible = false时,清空子项的内容;当IsVisible = true时,还原子项的内容
        /// </summary>
        public class VirtualizedListBox : ListBox
        {
            private ScrollViewer scrollViewer;
    
            public override void OnApplyTemplate()
            {
                scrollViewer = FindVisualChild<ScrollViewer>(this);
                if (scrollViewer != null)
                {
                    scrollViewer.ScrollChanged -= OnScrollChanged;
                    scrollViewer.ScrollChanged += OnScrollChanged;
                }
            }
    
            #region 事件
            /// <summary>
            /// 滚动条滚动事件
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void OnScrollChanged(object sender, ScrollChangedEventArgs e)
            {
                foreach (IVisible item in this.Items)
                {
                    var listBoxItem = (FrameworkElement)this.ItemContainerGenerator.ContainerFromItem(item);
                    item.IsVisible = IsChildVisibleInParent(listBoxItem, scrollViewer);
                }
            } 
            #endregion
    
            #region 私有方法
            /// <summary>
            /// 判断子控件是否在父控件中可见
            /// </summary>
            /// <param name="child">子控件</param>
            /// <param name="parent">父控件</param>
            /// <returns></returns>
            private bool IsChildVisibleInParent(FrameworkElement child, FrameworkElement parent)
            {
                var childTransform = child.TransformToAncestor(parent);
                var childRectangle = childTransform.TransformBounds(new Rect(new Point(0, 0), child.RenderSize));
                var ownerRectangle = new Rect(new Point(0, 0), parent.RenderSize);
                return ownerRectangle.IntersectsWith(childRectangle);
            }
    
            public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
            {
                if (obj != null)
                {
                    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                    {
                        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                        if (child != null && child is T)
                        {
                            return (T)child;
                        }
                        T childItem = FindVisualChild<T>(child);
                        if (childItem != null) return childItem;
                    }
                }
                return null;
            } 
            #endregion
        }
    VirtualizedListBox
        /// <summary>
        /// 表示可见的类型
        /// </summary>
        public interface IVisible
        {
            /// <summary>
            /// 是否可见
            /// </summary>
            bool IsVisible
            {
                get;
                set;
            }
        }
    IVisible

    核心代码是IsChildVisibleInParent方法,可以判断某个子控件是否在父控件中可见。

    针对ListBox,需要判断某个ListBoxItem是否在ListBox的ScrollView中可见。然后根据子项是否可见,再对子项进行处理,实现间接的虚拟化。

    于是,需要获取ListBox的模板中的ScrollViewer。于是,祭出神器:

    public static T FindVisualChild<T>(DependencyObject obj) where T : DependencyObject
            {
                if (obj != null)
                {
                    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
                    {
                        DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                        if (child != null && child is T)
                        {
                            return (T)child;
                        }
                        T childItem = FindVisualChild<T>(child);
                        if (childItem != null) return childItem;
                    }
                }
                return null;
            } 
    FindVisualChild

    这个方法,我觉得了解WPF的应该都知道。我甚至在学习WPF的第一天就见到了这个方法。

    一开始,我在VirtualizedListBox的构造函数中调用此方法,发现获取到的ScrollViewer是空的。

    我想,应该这时候ListBox还没开始加载。

    然后,我在ListBox的Loaded事件中调用此方法,发现获取到的ScrollViewer还是空的。

    什么鬼?感觉有点颠覆三观。

    最后,我心灰意冷,把键盘砸了。砸键盘的过程中,代码成了。

    嘿嘿,不知道大家是否知道在什么情况下,ListBox即使已经加载完成(Loaded),却依然无法获取到ListBox控件模板中的ScrollViewer呢?

  • 相关阅读:
    线段树
    数据结构<三> 队列
    数据结构<二>双向链表
    数据结构<一>单链表
    扩展欧几里德算法
    90 个 node.js 扩展模块,我们疯了
    nodejs的查询构造器
    express的路由配置优化
    express路由方案
    Redis学习笔记~目录
  • 原文地址:https://www.cnblogs.com/DoNetCoder/p/4284496.html
Copyright © 2020-2023  润新知