/// <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 }
/// <summary> /// 表示可见的类型 /// </summary> public interface IVisible { /// <summary> /// 是否可见 /// </summary> bool IsVisible { get; set; } }
核心代码是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; }
这个方法,我觉得了解WPF的应该都知道。我甚至在学习WPF的第一天就见到了这个方法。
一开始,我在VirtualizedListBox的构造函数中调用此方法,发现获取到的ScrollViewer是空的。
我想,应该这时候ListBox还没开始加载。
然后,我在ListBox的Loaded事件中调用此方法,发现获取到的ScrollViewer还是空的。
什么鬼?感觉有点颠覆三观。
最后,我心灰意冷,把键盘砸了。砸键盘的过程中,代码成了。
嘿嘿,不知道大家是否知道在什么情况下,ListBox即使已经加载完成(Loaded),却依然无法获取到ListBox控件模板中的ScrollViewer呢?