Skip to content

Commit

Permalink
Use better logical child tracking
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Redth committed Jul 24, 2023
1 parent c67b395 commit 3e8cc59
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 81 deletions.
58 changes: 58 additions & 0 deletions VirtualListView/Controls/ViewExtensions.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}

134 changes: 53 additions & 81 deletions VirtualListView/Controls/VirtualListView.cs
Original file line number Diff line number Diff line change
@@ -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),
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -245,89 +277,29 @@ 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 =>
"GLOBAL_FOOTER_" + (Footer?.GetType()?.FullName ?? "NIL"),
_ => "UNKNOWN"
};

static PropertyInfo DataTemplateIdPropertyInfo;

string? GetDataTemplateId(DataTemplate dataTemplate)
{
DataTemplateIdPropertyInfo ??= dataTemplate.GetType().GetProperty("Id", BindingFlags.Instance | BindingFlags.NonPublic);

return DataTemplateIdPropertyInfo.GetValue(dataTemplate)?.ToString();

}

public IReadOnlyList<IVisualTreeElement> GetVisualChildren()
{
var results = new List<IVisualTreeElement>();

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);
Expand Down
2 changes: 2 additions & 0 deletions VirtualListView/IVirtualListView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down

0 comments on commit 3e8cc59

Please sign in to comment.