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呢?

原文地址:https://www.cnblogs.com/DoNetCoder/p/4284496.html