Browse Source

Keep metadata-row source strongly typed so default sort works

Each metadata tree node was projecting its rows into List<object> via Cast<object>
before assigning to MetadataTablePageModel.Items. That collapsed the source's
element type at the runtime interface chain — DataGridCollectionView.GetItemType
walks IEnumerable<T> and only saw IEnumerable<object>, which made
DataGridSortDescription.Initialize resolve a null propertyType, which made
ReflectionHelper.GetNestedPropertyValue's "propertyInfo.PropertyType != propertyType"
check return null for every row. Result: header click flipped the sort indicator
but the row order never changed.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
9e519ec052
  1. 2
      ILSpy/Metadata/BlobHeapTreeNode.cs
  2. 2
      ILSpy/Metadata/CoffHeaderTreeNode.cs
  3. 2
      ILSpy/Metadata/DataDirectoriesTreeNode.cs
  4. 2
      ILSpy/Metadata/DebugDirectoryTreeNode.cs
  5. 2
      ILSpy/Metadata/DosHeaderTreeNode.cs
  6. 2
      ILSpy/Metadata/GuidHeapTreeNode.cs
  7. 10
      ILSpy/Metadata/MetadataTableTreeNode.cs
  8. 2
      ILSpy/Metadata/OptionalHeaderTreeNode.cs
  9. 2
      ILSpy/Metadata/StringHeapTreeNode.cs
  10. 2
      ILSpy/Metadata/UserStringHeapTreeNode.cs
  11. 41
      ILSpy/Views/MetadataTablePage.axaml.cs

2
ILSpy/Metadata/BlobHeapTreeNode.cs

@ -49,7 +49,7 @@ namespace ILSpy.Metadata @@ -49,7 +49,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "Blob Heap",
Items = EnsureEntries().Cast<object>().ToList(),
Items = EnsureEntries(),
};
MetadataColumnBuilder.Populate<BlobHeapEntry>(page);
return page;

2
ILSpy/Metadata/CoffHeaderTreeNode.cs

@ -49,7 +49,7 @@ namespace ILSpy.Metadata @@ -49,7 +49,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "COFF Header",
Items = BuildEntries().Cast<object>().ToList(),
Items = BuildEntries(),
};
MetadataColumnBuilder.Populate<Entry>(page);
return page;

2
ILSpy/Metadata/DataDirectoriesTreeNode.cs

@ -50,7 +50,7 @@ namespace ILSpy.Metadata @@ -50,7 +50,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "Data Directories",
Items = BuildEntries().Cast<object>().ToList(),
Items = BuildEntries(),
};
MetadataColumnBuilder.Populate<DataDirectoryEntry>(page);
return page;

2
ILSpy/Metadata/DebugDirectoryTreeNode.cs

@ -55,7 +55,7 @@ namespace ILSpy.Metadata @@ -55,7 +55,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "Debug Directory",
Items = BuildEntries().Cast<object>().ToList(),
Items = BuildEntries(),
};
MetadataColumnBuilder.Populate<DebugDirectoryEntryView>(page);
return page;

2
ILSpy/Metadata/DosHeaderTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata @@ -48,7 +48,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "DOS Header",
Items = BuildEntries().Cast<object>().ToList(),
Items = BuildEntries(),
};
MetadataColumnBuilder.Populate<Entry>(page);
return page;

2
ILSpy/Metadata/GuidHeapTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata @@ -48,7 +48,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "Guid Heap",
Items = EnsureEntries().Cast<object>().ToList(),
Items = EnsureEntries(),
};
MetadataColumnBuilder.Populate<GuidHeapEntry>(page);
return page;

10
ILSpy/Metadata/MetadataTableTreeNode.cs

@ -58,6 +58,7 @@ namespace ILSpy.Metadata @@ -58,6 +58,7 @@ namespace ILSpy.Metadata
/// them clickable hyperlinks via <see cref="MetadataColumnBuilder"/>.
/// </summary>
public abstract class MetadataTableTreeNode<TEntry> : MetadataTableTreeNode
where TEntry : class
{
IReadOnlyList<TEntry>? cached;
@ -73,9 +74,16 @@ namespace ILSpy.Metadata @@ -73,9 +74,16 @@ namespace ILSpy.Metadata
public override TabPageModel CreateTab()
{
// IReadOnlyList<T> is covariant on T, so a strongly-typed list passes through to
// MetadataTablePageModel.Items (declared as IReadOnlyList<object>) without a
// reflective copy. The runtime type stays IReadOnlyList<TEntry>, which lets
// DataGridCollectionView.GetItemType find the IEnumerable<TEntry> interface and
// resolve property-path sorts correctly — see DataGridSortDescription.Initialize
// + ReflectionHelper.GetNestedPropertyValue, which return null for every row when
// the source's element type is object.
var page = new MetadataTablePageModel {
Title = $"{Kind} ({RowCount})",
Items = (cached ??= LoadTable()).Cast<object>().ToList(),
Items = cached ??= LoadTable(),
};
MetadataColumnBuilder.Populate<TEntry>(page);
return page;

2
ILSpy/Metadata/OptionalHeaderTreeNode.cs

@ -50,7 +50,7 @@ namespace ILSpy.Metadata @@ -50,7 +50,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "Optional Header",
Items = BuildEntries().Cast<object>().ToList(),
Items = BuildEntries(),
};
MetadataColumnBuilder.Populate<Entry>(page);
return page;

2
ILSpy/Metadata/StringHeapTreeNode.cs

@ -49,7 +49,7 @@ namespace ILSpy.Metadata @@ -49,7 +49,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "String Heap",
Items = EnsureEntries().Cast<object>().ToList(),
Items = EnsureEntries(),
};
MetadataColumnBuilder.Populate<StringHeapEntry>(page);
return page;

2
ILSpy/Metadata/UserStringHeapTreeNode.cs

@ -48,7 +48,7 @@ namespace ILSpy.Metadata @@ -48,7 +48,7 @@ namespace ILSpy.Metadata
{
var page = new MetadataTablePageModel {
Title = "UserString Heap",
Items = EnsureEntries().Cast<object>().ToList(),
Items = EnsureEntries(),
};
MetadataColumnBuilder.Populate<UserStringHeapEntry>(page);
return page;

41
ILSpy/Views/MetadataTablePage.axaml.cs

@ -69,13 +69,6 @@ namespace ILSpy.Views @@ -69,13 +69,6 @@ namespace ILSpy.Views
grid.AddHandler(PointerPressedEvent, OnGridPointerPressed,
global::Avalonia.Interactivity.RoutingStrategies.Bubble,
handledEventsToo: true);
// Manually drive the DataGridCollectionView's SortDescriptions when the user
// clicks a column header. ProDataGrid's auto-sort path doesn't reach our
// view (Items is IReadOnlyList<object> wrapped in a DataGridCollectionView,
// and the default sorting adapter's reflection chain doesn't pick up the
// SortMemberPath against an object collection). Toggle ascending → descending
// → unsorted on repeated clicks.
grid.Sorting += OnGridSorting;
}
}
@ -334,40 +327,6 @@ namespace ILSpy.Views @@ -334,40 +327,6 @@ namespace ILSpy.Views
void OnAnyColumnFilterChanged() => itemsView?.Refresh();
void OnGridSorting(object? sender, DataGridColumnEventArgs e)
{
// Manually drive sort: cycle through ascending → descending → unsorted on repeated
// clicks for the same column. Mark the event handled so ProDataGrid's auto-sort
// path doesn't run after us — without this it would try to add another
// SortDescription against the same path and produce a stuck "double-sorted" state.
if (itemsView is null)
return;
string? path = (e.Column.SortMemberPath ?? e.Column.Tag as string);
if (string.IsNullOrEmpty(path))
return;
var existing = itemsView.SortDescriptions
.FirstOrDefault(d => d.PropertyPath == path);
itemsView.SortDescriptions.Clear();
foreach (var col in (sender as DataGrid)?.Columns ?? System.Linq.Enumerable.Empty<DataGridColumn>())
col.SortDirection = null;
if (existing is null)
{
itemsView.SortDescriptions.Add(DataGridSortDescription.FromPath(path, ListSortDirection.Ascending));
e.Column.SortDirection = ListSortDirection.Ascending;
}
else if (existing.Direction == ListSortDirection.Ascending)
{
itemsView.SortDescriptions.Add(DataGridSortDescription.FromPath(path, ListSortDirection.Descending));
e.Column.SortDirection = ListSortDirection.Descending;
}
// Third click clears: SortDescriptions stays empty, SortDirection on column is null.
e.Handled = true;
}
void ApplySchema()
{
var grid = this.FindControl<DataGrid>("Grid");

Loading…
Cancel
Save