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

你可能感兴趣的:(listbox)