From 3e8cc5924fb20fb523ee6e805e4f0406e7bcbf1f Mon Sep 17 00:00:00 2001 From: redth Date: Mon, 24 Jul 2023 09:25:26 -0400 Subject: [PATCH] Use better logical child tracking For now there are internal methods we can call for adding/removing logical children without having to maintain a list ourselves. This moves the logic out into extension methods which currently uses reflection to get either the internal method, or if that's not found, on newer .NET 8 previews/rc's the method is public so we can safeguard to get that via reflection until a newer sdk is released where we can compile against the upcoming change. --- VirtualListView/Controls/ViewExtensions.cs | 58 +++++++++ VirtualListView/Controls/VirtualListView.cs | 134 ++++++++------------ VirtualListView/IVirtualListView.cs | 2 + 3 files changed, 113 insertions(+), 81 deletions(-) create mode 100644 VirtualListView/Controls/ViewExtensions.cs diff --git a/VirtualListView/Controls/ViewExtensions.cs b/VirtualListView/Controls/ViewExtensions.cs new file mode 100644 index 0000000..9896c48 --- /dev/null +++ b/VirtualListView/Controls/ViewExtensions.cs @@ -0,0 +1,58 @@ +using System; +using System.Reflection; + +namespace Microsoft.Maui.Controls; + +internal static class ViewExtensions +{ + static PropertyInfo DataTemplateIdPropertyInfo; + + internal static string GetDataTemplateId(this DataTemplate dataTemplate) + { + DataTemplateIdPropertyInfo ??= dataTemplate.GetType().GetProperty("Id", BindingFlags.Instance | BindingFlags.NonPublic); + + return DataTemplateIdPropertyInfo.GetValue(dataTemplate)?.ToString(); + + } + + static MethodInfo removeLogicalChildMethod = null; + + internal static void RemoveLogicalChild(this Element parent, IView view) + { + if (view is Element elem) + { + removeLogicalChildMethod ??= GetLogicalChildMethod(parent, "RemoveLogicalChildInternal", "RemoveLogicalChild"); + removeLogicalChildMethod?.Invoke(parent, new[] { elem }); + } + } + + static MethodInfo addLogicalChildMethod = null; + + internal static void AddLogicalChild(this Element parent, IView view) + { + if (view is Element elem) + { + addLogicalChildMethod ??= GetLogicalChildMethod(parent, "AddLogicalChildInternal", "AddLogicalChild"); + addLogicalChildMethod?.Invoke(parent, new[] { elem }); + } + } + + static MethodInfo GetLogicalChildMethod(Element parent, string internalName, string publicName) + { + var internalMethod = parent.GetType().GetMethod( + internalName, + BindingFlags.Instance | BindingFlags.NonPublic, + new[] { typeof(Element) }); + + if (internalMethod is null) + { + internalMethod = parent.GetType().GetMethod( + publicName, + BindingFlags.Instance | BindingFlags.Public, + new[] { typeof(Element) }); + } + + return internalMethod; + } +} + diff --git a/VirtualListView/Controls/VirtualListView.cs b/VirtualListView/Controls/VirtualListView.cs index 6959fbd..19817b4 100644 --- a/VirtualListView/Controls/VirtualListView.cs +++ b/VirtualListView/Controls/VirtualListView.cs @@ -1,16 +1,10 @@ -using Microsoft.Maui.Adapters; -using System.Reflection; -using System.Windows.Input; - +using System.Windows.Input; +using Microsoft.Maui.Adapters; + namespace Microsoft.Maui.Controls; public partial class VirtualListView : View, IVirtualListView, IVirtualListViewSelector, IVisualTreeElement { - static VirtualListView() - { - - } - public static readonly BindableProperty PositionInfoProperty = BindableProperty.CreateAttached( nameof(PositionInfo), typeof(PositionInfo), @@ -165,10 +159,48 @@ public View EmptyView } public static readonly BindableProperty EmptyViewProperty = - BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null); + BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null, + propertyChanged: (bobj, oldValue, newValue) => + { + if (bobj is VirtualListView virtualListView) + { + if (oldValue is IView oldView) + virtualListView.RemoveLogicalChild(oldView); + + if (newValue is IView newView) + virtualListView.AddLogicalChild(newView); + } + }); IView IVirtualListView.EmptyView => EmptyView; + + + public View RefreshView + { + get => (View)GetValue(RefreshViewProperty); + set => SetValue(RefreshViewProperty, value); + } + + public static readonly BindableProperty RefreshViewProperty = + BindableProperty.Create(nameof(EmptyView), typeof(View), typeof(VirtualListView), null, + propertyChanged: (bobj, oldValue, newValue) => + { + if (bobj is VirtualListView virtualListView) + { + if (oldValue is IView oldView) + virtualListView.RemoveLogicalChild(oldView); + + if (newValue is IView newView) + virtualListView.AddLogicalChild(newView); + } + }); + + IView IVirtualListView.RefreshView => RefreshView; + + + + public IVirtualListViewSelector ViewSelector => this; public IView Header => GlobalHeader; @@ -245,17 +277,17 @@ public string GetReuseId(PositionInfo position, object data) => position.Kind switch { PositionKind.Item => - "ITEM_" + (GetDataTemplateId( - ItemTemplateSelector?.SelectTemplate(data, position.SectionIndex, position.ItemIndex) - ?? ItemTemplate) ?? "0"), + "ITEM_" + + (ItemTemplateSelector?.SelectTemplate(data, position.SectionIndex, position.ItemIndex) + ?? ItemTemplate)?.GetDataTemplateId() ?? "0", PositionKind.SectionHeader => - "SECTION_HEADER_" + (GetDataTemplateId( - SectionHeaderTemplateSelector?.SelectTemplate(data, position.SectionIndex) - ?? SectionHeaderTemplate) ?? "0"), + "SECTION_HEADER_" + + (SectionHeaderTemplateSelector?.SelectTemplate(data, position.SectionIndex) + ?? SectionHeaderTemplate)?.GetDataTemplateId() ?? "0", PositionKind.SectionFooter => - "SECTION_FOOTER_" + (GetDataTemplateId( - SectionFooterTemplateSelector?.SelectTemplate(data, position.SectionIndex) - ?? SectionFooterTemplate) ?? "0"), + "SECTION_FOOTER_" + + (SectionFooterTemplateSelector?.SelectTemplate(data, position.SectionIndex) + ?? SectionFooterTemplate)?.GetDataTemplateId() ?? "0", PositionKind.Header => "GLOBAL_HEADER_" + (Header?.GetType()?.FullName ?? "NIL"), PositionKind.Footer => @@ -263,71 +295,11 @@ public string GetReuseId(PositionInfo position, object data) _ => "UNKNOWN" }; - static PropertyInfo DataTemplateIdPropertyInfo; - - string? GetDataTemplateId(DataTemplate dataTemplate) - { - DataTemplateIdPropertyInfo ??= dataTemplate.GetType().GetProperty("Id", BindingFlags.Instance | BindingFlags.NonPublic); - - return DataTemplateIdPropertyInfo.GetValue(dataTemplate)?.ToString(); - - } - - public IReadOnlyList GetVisualChildren() - { - var results = new List(); - - foreach (var c in logicalChildren) - { - if (c.view is IVisualTreeElement vte) - results.Add(vte); - } - - return results; - } - - readonly object lockLogicalChildren = new(); - readonly List<(int section, int item, Element view)> logicalChildren = new(); - public void ViewDetached(PositionInfo position, IView view) - { - var oldLogicalIndex = -1; - lock (lockLogicalChildren) - { - Element elem= null; - - for (var i = 0; i < logicalChildren.Count; i++) - { - var child = logicalChildren[i]; - - if (child.section == position.SectionIndex - && child.item == position.ItemIndex) - { - elem = child.view; - oldLogicalIndex = i; - break; - } - } - - if (oldLogicalIndex >= 0) - { - logicalChildren.RemoveAt(oldLogicalIndex); - if (elem != null) - VisualDiagnostics.OnChildRemoved(this, elem, oldLogicalIndex); - } - } - } + => this.RemoveLogicalChild(view); public void ViewAttached(PositionInfo position, IView view) - { - if (view is Element elem) - { - lock (lockLogicalChildren) - logicalChildren.Add((position.SectionIndex, position.ItemIndex, elem)); - - VisualDiagnostics.OnChildAdded(this, elem); - } - } + => this.AddLogicalChild(view); public bool IsItemSelected(int sectionIndex, int itemIndex) => (Handler as VirtualListViewHandler).IsItemSelected(sectionIndex, itemIndex); diff --git a/VirtualListView/IVirtualListView.cs b/VirtualListView/IVirtualListView.cs index 027f19c..25bfa55 100644 --- a/VirtualListView/IVirtualListView.cs +++ b/VirtualListView/IVirtualListView.cs @@ -31,6 +31,8 @@ public interface IVirtualListView : IView IView EmptyView { get; } + IView RefreshView { get; } + bool IsItemSelected(int sectionIndex, int itemIndex); void SelectItems(params ItemPosition[] paths);