Browse Source

Rename ColumnFilter.Value to Text to fix binding setter

The CommunityToolkit.Mvvm source generator over a `value` field interacts
poorly with Avalonia's INPC binding plugin: typing into the per-column
filter TextBox throws TargetException("Non-static method requires a
target") because the cached accessor's target reference comes back null
on write. Renaming the backing field and property avoids the collision
with the contextual `value` setter identifier.

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
dc39ebca6f
  1. 16
      ILSpy.Tests/Metadata/MetadataFilterTests.cs
  2. 2
      ILSpy/Metadata/MetadataColumnBuilder.cs
  3. 11
      ILSpy/ViewModels/MetadataTablePageModel.cs
  4. 2
      ILSpy/Views/MetadataTablePage.axaml.cs

16
ILSpy.Tests/Metadata/MetadataFilterTests.cs

@ -62,8 +62,8 @@ public class MetadataFilterTests @@ -62,8 +62,8 @@ public class MetadataFilterTests
// not just any column. Case-insensitive substring match.
var entry = new SampleEntry { RID = 1, Name = "System.Runtime", Culture = "neutral" };
MetadataTablePageModel.MatchesFilters(entry, new[] {
new ColumnFilter("Name") { Value = "system" },
new ColumnFilter("Culture") { Value = "NEUTRAL" },
new ColumnFilter("Name") { Text = "system" },
new ColumnFilter("Culture") { Text = "NEUTRAL" },
}).Should().BeTrue();
}
@ -74,7 +74,7 @@ public class MetadataFilterTests @@ -74,7 +74,7 @@ public class MetadataFilterTests
// Culture is "neutral", even though Name contains "System".
var entry = new SampleEntry { Name = "System.Runtime", Culture = "neutral" };
MetadataTablePageModel.MatchesFilters(entry, new[] {
new ColumnFilter("Culture") { Value = "system" },
new ColumnFilter("Culture") { Text = "system" },
}).Should().BeFalse();
}
@ -84,8 +84,8 @@ public class MetadataFilterTests @@ -84,8 +84,8 @@ public class MetadataFilterTests
// All filters must match — one missing column is enough to drop the row.
var entry = new SampleEntry { Name = "System.Runtime", Culture = "neutral" };
MetadataTablePageModel.MatchesFilters(entry, new[] {
new ColumnFilter("Name") { Value = "system" },
new ColumnFilter("Culture") { Value = "invariant" },
new ColumnFilter("Name") { Text = "system" },
new ColumnFilter("Culture") { Text = "invariant" },
}).Should().BeFalse();
}
@ -96,7 +96,7 @@ public class MetadataFilterTests @@ -96,7 +96,7 @@ public class MetadataFilterTests
// match" prevents stale filter rows from silently passing rows of a different shape.
var entry = new SampleEntry { Name = "X" };
MetadataTablePageModel.MatchesFilters(entry, new[] {
new ColumnFilter("DoesNotExist") { Value = "anything" },
new ColumnFilter("DoesNotExist") { Text = "anything" },
}).Should().BeFalse();
}
@ -128,13 +128,13 @@ public class MetadataFilterTests @@ -128,13 +128,13 @@ public class MetadataFilterTests
totalCount.Should().BeGreaterThan(0);
var nameFilter = tab.ColumnFilters.Single(f => f.ColumnName == "Name");
nameFilter.Value = "System";
nameFilter.Text = "System";
var visible = tab.Items.Where(e => MetadataTablePageModel.MatchesFilters(e, tab.ColumnFilters)).ToList();
visible.Should().NotBeEmpty("at least one TypeDef row should mention 'System'");
visible.Count.Should().BeLessThan(totalCount, "the filter must hide at least one row");
nameFilter.Value = "";
nameFilter.Text = "";
var afterClear = tab.Items.Where(e => MetadataTablePageModel.MatchesFilters(e, tab.ColumnFilters)).Count();
afterClear.Should().Be(totalCount);
}

2
ILSpy/Metadata/MetadataColumnBuilder.cs

@ -130,7 +130,7 @@ namespace ILSpy.Metadata @@ -130,7 +130,7 @@ namespace ILSpy.Metadata
FontWeight = FontWeight.Normal,
HorizontalAlignment = HorizontalAlignment.Stretch,
};
box.Bind(TextBox.TextProperty, new Binding(nameof(ColumnFilter.Value)) {
box.Bind(TextBox.TextProperty, new Binding(nameof(ColumnFilter.Text)) {
Source = filter,
Mode = BindingMode.TwoWay,
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,

11
ILSpy/ViewModels/MetadataTablePageModel.cs

@ -52,7 +52,7 @@ namespace ILSpy.ViewModels @@ -52,7 +52,7 @@ namespace ILSpy.ViewModels
/// <summary>
/// One filter input per column, in the same order as <see cref="Columns"/>. The view
/// renders each <see cref="ColumnFilter.Value"/> as a TextBox baked into that column's
/// renders each <see cref="ColumnFilter.Text"/> as a TextBox baked into that column's
/// header; the filter predicate ANDs every non-empty entry, requiring each row's
/// matching property's stringified value to contain its filter (case-insensitive).
/// </summary>
@ -91,7 +91,7 @@ namespace ILSpy.ViewModels @@ -91,7 +91,7 @@ namespace ILSpy.ViewModels
ArgumentNullException.ThrowIfNull(filters);
foreach (var f in filters)
{
if (string.IsNullOrEmpty(f.Value))
if (string.IsNullOrEmpty(f.Text))
continue;
var prop = propertyLookupCache.GetOrAdd((item.GetType(), f.ColumnName),
static key => key.Type.GetProperty(key.Column,
@ -103,7 +103,7 @@ namespace ILSpy.ViewModels @@ -103,7 +103,7 @@ namespace ILSpy.ViewModels
{ value = prop.GetValue(item); }
catch { return false; }
var s = value?.ToString();
if (s is null || !s.Contains(f.Value, StringComparison.OrdinalIgnoreCase))
if (s is null || !s.Contains(f.Text, StringComparison.OrdinalIgnoreCase))
return false;
}
return true;
@ -120,8 +120,11 @@ namespace ILSpy.ViewModels @@ -120,8 +120,11 @@ namespace ILSpy.ViewModels
public string ColumnName { get; }
// Backing field is `text` rather than `value` because the latter collides with C#'s
// contextual identifier for property setters and trips Avalonia's INPC binding
// plugin on write (the cached accessor's target reference comes back null).
[ObservableProperty]
private string? value;
private string? text;
}
/// <summary>The (row, column) pair clicked in a token cell.</summary>

2
ILSpy/Views/MetadataTablePage.axaml.cs

@ -162,7 +162,7 @@ namespace ILSpy.Views @@ -162,7 +162,7 @@ namespace ILSpy.Views
void OnColumnFilterChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(ColumnFilter.Value))
if (e.PropertyName == nameof(ColumnFilter.Text))
itemsView?.Refresh();
}

Loading…
Cancel
Save