האצת TreeView ב-WPF על ידי וירטואליזציה עצמית
-
אם יצא לכם לעבוד עם TreeView ב-WPF ולהציג בו הרבה נתונים, אולי נתקלתם בהאטה בביצועים — במיוחד כשיש הרבה Nodes טעונים מראש.
בפקדים מבוססי רשימות (כמו ListBox), ניתן לפתור זאת על ידי שימוש ב-Virtualization, שמונעת טעינה של פריטים שלא מוצגים כרגע. אך TreeView, בשל המבנה ההיררכי שלו, לא תומך ב-Virtualization בצורה יעילה.
לכן, יצרתי פתרון פשוט שמדמה את ההתנהגות הזו על ידי טעינה עצלנית (Lazy Loading) של התוכן. זה לא רעיון חדש, אבל שווה להזכיר למי שלא מכיר או שכח שיש דרך לשפר את הביצועים בצורה ניכרת.
איך זה עובד בפועל:
כשפריט נטען לראשונה (Loaded), מוצג רק placeholder כדי להימנע מטעינה מיותרת.
רק בעת פתיחה (Expanded), נטענים בפועל הנתונים מה-ViewModel.
בעת סגירה (Collapsed), הנתונים מתחלפים שוב ב-placeholder — שחרור פשוט של משאבים.שימו לב! במקרה זה אל תשתמשו ב-itemsource עבור HierarchicalDataTemplate ב- Xaml
להלן קוד דוגמא שמיישם את ההתנהגות הזו:
אשמח לקבל הצעות לשיפור:
כמו"כ אשמח לשמוע רעיונות איך להפוך את זה למשהו יותר כללי (כרגע הוא מגבל לסוג datacontext מסויים מאוד.using System.Windows.Controls; using System.Windows; using Otzarnik.FsViewer; using System.Collections.Generic; namespace Oztarnik.Helpers { public static class TreeItemVirtualizationBehavior { public static bool GetEnableVirtualization(DependencyObject obj) => (bool)obj.GetValue(EnableVirtualizationProperty); public static void SetEnableVirtualization(DependencyObject obj, bool value) => obj.SetValue(EnableVirtualizationProperty, value); public static readonly DependencyProperty EnableVirtualizationProperty = DependencyProperty.RegisterAttached("EnableVirtualization", typeof(bool), typeof(TreeItemVirtualizationBehavior), new PropertyMetadata(false, OnEnableVirtualizationChanged)); private static void OnEnableVirtualizationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is TreeViewItem item && e.NewValue is bool enabled) { if (enabled) { item.Expanded += OnItemExpanded; item.Collapsed += OnItemCollapsed; item.Loaded += Item_Loaded; } else { item.Expanded -= OnItemExpanded; item.Collapsed -= OnItemCollapsed; item.Loaded -= Item_Loaded; } } } private static void Item_Loaded(object sender, RoutedEventArgs e) { if (sender is TreeViewItem treeViewItem && treeViewItem.DataContext is TreeItem treeItem && treeItem.Items?.Count > 0) treeViewItem.ItemsSource = new List<string> { "" }; } private static void OnItemExpanded(object sender, RoutedEventArgs e) { if (sender is TreeViewItem treeViewItem && treeViewItem.DataContext is TreeItem treeItem && treeItem.Items?.Count > 0) treeViewItem.ItemsSource = treeItem.Items; } private static void OnItemCollapsed(object sender, RoutedEventArgs e) { if (sender is TreeViewItem treeViewItem && treeViewItem.DataContext is TreeItem treeItem && treeItem.Items?.Count > 0) treeViewItem.ItemsSource = new List<string> { ""}; } } }
-
@pcinfogmach כתב בהאצת TreeView ב-WPF על ידי וירטואליזציה עצמית:
כמו"כ אשמח לשמוע רעיונות איך להפוך את זה למשהו יותר כללי (כרגע הוא מגבל לסוג datacontext מסויים מאוד.
public static class TreeItemVirtualizationBehavior { public static bool GetEnableVirtualization(DependencyObject obj) => (bool)obj.GetValue(EnableVirtualizationProperty); public static void SetEnableVirtualization(DependencyObject obj, bool value) => obj.SetValue(EnableVirtualizationProperty, value); public static readonly DependencyProperty EnableVirtualizationProperty = DependencyProperty.RegisterAttached("EnableVirtualization", typeof(bool), typeof(TreeItemVirtualizationBehavior), new PropertyMetadata(false, OnEnableVirtualizationChanged)); private static void OnEnableVirtualizationChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var item = (TreeViewItem)d; if ((bool)e.NewValue) { item.Expanded += setSource; item.Collapsed += setSource; item.Loaded += setSource; } else { item.Expanded -= setSource; item.Collapsed -= setSource; item.Loaded -= setSource; } } public static Func<object, bool> needPlaceHolders = (s) => false; private static void setSource(object sender, RoutedEventArgs e) { var treeViewItem = (TreeViewItem)sender; if (needPlaceHolders(treeViewItem.DataContext)) treeViewItem.ItemsSource = Enumerable.Repeat("", 1); } }
צריך להפוך לספציפי בטעינת האפליקציה על ידי השמה של needPlaceHolders:
TreeItemVirtualizationBehavior.needPlaceHolders = (dataContext) => dataContext is TreeItem treeItem && treeItem.Items?.Any() == true;
יש מצב שלא הבנתי מה שהקוד צריך לעשות ואם ככה שטעיתי גם בהצעה.