Skip to content

Commit 221dc8f

Browse files
authored
Merge branch 'main' into auto_gen_columns
2 parents 1780bb0 + f8edf76 commit 221dc8f

File tree

9 files changed

+573
-117
lines changed

9 files changed

+573
-117
lines changed

src/ColumnFilterHandler.cs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,12 @@ public virtual IList<TableViewFilterItem> GetFilterItems(TableViewColumn column,
3434
foreach (var item in collectionView)
3535
{
3636
var value = column.GetCellContent(item);
37-
filterValues.Add(value);
37+
filterValues.Add(IsBlank(value) ? null : value);
3838
}
3939

40-
return filterValues.Select(value =>
40+
return [.. filterValues.Select(value =>
4141
{
42-
value = string.IsNullOrWhiteSpace(value?.ToString()) ? TableViewLocalizedStrings.BlankFilterValue : value;
42+
value ??= TableViewLocalizedStrings.BlankFilterValue;
4343
var isSelected = !column.IsFiltered || !string.IsNullOrEmpty(searchText) ||
4444
(column.IsFiltered && SelectedValues[column].Contains(value));
4545

@@ -48,13 +48,21 @@ public virtual IList<TableViewFilterItem> GetFilterItems(TableViewColumn column,
4848
? new TableViewFilterItem(isSelected, value)
4949
: null;
5050

51-
}).OfType<TableViewFilterItem>()
52-
.ToList();
51+
}).OfType<TableViewFilterItem>()];
5352
}
5453

5554
return [];
5655
}
5756

57+
private static bool IsBlank(object? value)
58+
{
59+
return value == null ||
60+
value == DBNull.Value ||
61+
(value is string str && string.IsNullOrWhiteSpace(str)) ||
62+
(value is Guid guid && guid == Guid.Empty);
63+
}
64+
65+
5866
public virtual void ApplyFilter(TableViewColumn column)
5967
{
6068
if (column is { TableView: { } })
@@ -107,7 +115,7 @@ public virtual void ClearFilter(TableViewColumn? column)
107115
public virtual bool Filter(TableViewColumn column, object? item)
108116
{
109117
var value = column.GetCellContent(item);
110-
value = string.IsNullOrWhiteSpace(value?.ToString()) ? TableViewLocalizedStrings.BlankFilterValue : value;
118+
value = IsBlank(value) ? TableViewLocalizedStrings.BlankFilterValue : value!;
111119
return SelectedValues[column].Contains(value);
112120
}
113121

src/Columns/TableViewBoundColumn.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,20 +14,20 @@ public abstract class TableViewBoundColumn : TableViewColumn
1414
private Type? _listType;
1515
private string? _propertyPath;
1616
private Binding _binding = new();
17-
private (MemberInfo, object?)[]? _memberInfo;
17+
private (PropertyInfo, object?)[]? _propertyInfo;
1818

1919
public override object? GetCellContent(object? dataItem)
2020
{
2121
if (dataItem is null) return null;
2222

23-
if (_memberInfo is null || dataItem.GetType() != _listType)
23+
if (_propertyInfo is null || dataItem.GetType() != _listType)
2424
{
2525
_listType = dataItem.GetType();
26-
dataItem = dataItem.GetValue(_listType, PropertyPath, out _memberInfo);
26+
dataItem = dataItem.GetValue(_listType, PropertyPath, out _propertyInfo);
2727
}
2828
else
2929
{
30-
dataItem = dataItem.GetValue(_memberInfo);
30+
dataItem = dataItem.GetValue(_propertyInfo);
3131
}
3232

3333
if (Binding?.Converter is not null)

src/Extensions/ObjectExtensions.cs

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,61 @@
1-
using System;
1+
using System;
22
using System.Collections;
33
using System.Linq;
44
using System.Reflection;
5+
using System.Text.RegularExpressions;
56

67
namespace WinUI.TableView.Extensions;
78

89
/// <summary>
910
/// Provides extension methods for object types.
1011
/// </summary>
11-
internal static class ObjectExtensions
12+
internal static partial class ObjectExtensions
1213
{
14+
// Regex to split property paths into property names and indexers (for cases like e.g. "[2].Foo[0].Bar", where Foo might be a Property that returns an array)
15+
[GeneratedRegex(@"([^.[]+)|(\[[^\]]+\])", RegexOptions.Compiled)]
16+
private static partial Regex PropertyPathRegex();
17+
1318
/// <summary>
1419
/// Gets the value of a property from an object using a sequence of property info and index pairs.
1520
/// </summary>
1621
/// <param name="obj">The object from which to get the value.</param>
17-
/// <param name="members">An array of member info and index pairs.</param>
22+
/// <param name="pis">An array of property info and index pairs.</param>
1823
/// <returns>The value of the property, or null if the object is null.</returns>
19-
internal static object? GetValue(this object? obj, (MemberInfo info, object? index)[] members)
24+
internal static object? GetValue(this object? obj, (PropertyInfo pi, object? index)[] pis)
2025
{
21-
foreach (var (info, index) in members)
26+
foreach (var (pi, index) in pis)
2227
{
2328
if (obj is null)
24-
{
2529
break;
26-
}
2730

28-
if (info is PropertyInfo pi)
31+
if (pi != null)
32+
{
33+
// Use property getter, with or without index
2934
obj = index is not null ? pi.GetValue(obj, [index]) : pi.GetValue(obj);
30-
else if (info is MethodInfo mi)
31-
obj = index is not null ? mi.Invoke(obj, [index]) : mi.Invoke(obj, []);
35+
}
36+
else if (index is int i)
37+
{
38+
// Array
39+
if (obj is Array arr)
40+
{
41+
obj = arr.GetValue(i);
42+
}
43+
// IList
44+
else if (obj is IList list)
45+
{
46+
obj = list[i];
47+
}
48+
else
49+
{
50+
// Not a supported indexer type
51+
return null;
52+
}
53+
}
54+
else
55+
{
56+
// Not a supported path segment
57+
return null;
58+
}
3259
}
3360

3461
return obj;
@@ -40,51 +67,92 @@ internal static class ObjectExtensions
4067
/// <param name="obj">The object from which to get the value.</param>
4168
/// <param name="type">The type of the object.</param>
4269
/// <param name="path">The property path.</param>
43-
/// <param name="members">An array of member info and index pairs.</param>
70+
/// <param name="pis">An array of property info and index pairs.</param>
4471
/// <returns>The value of the property, or null if the object is null.</returns>
45-
internal static object? GetValue(this object? obj, Type? type, string? path, out (MemberInfo info, object? index)[] members)
72+
internal static object? GetValue(this object? obj, Type? type, string? path, out (PropertyInfo pi, object? index)[] pis)
4673
{
47-
var parts = path?.Split('.');
74+
if (obj == null || string.IsNullOrWhiteSpace(path) || type == null)
75+
{
76+
pis = [];
77+
return obj;
78+
}
4879

49-
if (parts is null)
80+
var matches = PropertyPathRegex().Matches(path);
81+
if (matches.Count == 0)
5082
{
51-
members = [];
83+
pis = [];
5284
return obj;
5385
}
5486

55-
members = new (MemberInfo, object?)[parts.Length];
87+
// Pre-size the steps array to the number of matches
88+
pis = new (PropertyInfo, object?)[matches.Count];
89+
int i = 0;
90+
object? current = obj;
91+
Type? currentType = type;
5692

57-
for (var i = 0; i < parts.Length; i++)
93+
foreach (Match match in matches)
5894
{
59-
var part = parts[i];
60-
var index = default(object?);
61-
if (part.StartsWith('[') && part.EndsWith(']'))
62-
{
63-
index = int.TryParse(part[1..^1], out var ind) ? ind : index;
64-
part = "Item";
65-
}
95+
string part = match.Value;
96+
object? index = null;
97+
PropertyInfo? pi = null;
6698

67-
if (type?.GetProperty(part) is { } pi)
68-
{
69-
members[i] = (pi, index);
70-
obj = index is not null ? pi?.GetValue(obj, [index]) : pi?.GetValue(obj);
71-
type = obj?.GetType();
72-
}
73-
else if (type?.IsArray is true && type.GetMethod("GetValue", [typeof(int)]) is { } mi)
99+
if (part.StartsWith('[') && part.EndsWith(']'))
74100
{
75-
members[i] = (mi, index);
76-
obj = index is not null ? mi?.Invoke(obj, [index]) : mi?.Invoke(obj, []);
77-
type = obj?.GetType();
78-
101+
// Indexer: [index] or [key]
102+
string indexer = part[1..^1];
103+
if (int.TryParse(indexer, out int intIndex))
104+
index = intIndex;
105+
else
106+
index = indexer;
107+
108+
// Try array
109+
if (current is Array arr && index is int idx)
110+
{
111+
current = arr.GetValue(idx);
112+
pis[i++] = (null!, idx);
113+
currentType = current?.GetType();
114+
continue;
115+
}
116+
117+
// Try IList
118+
if (current is IList list && index is int idx2)
119+
{
120+
current = list[idx2];
121+
pis[i++] = (null!, idx2);
122+
currentType = current?.GetType();
123+
continue;
124+
}
125+
126+
// Try default indexer property (e.g., this[string])
127+
pi = currentType?.GetProperty("Item");
128+
if (pi != null)
129+
{
130+
current = pi.GetValue(current, [index]);
131+
pis[i++] = (pi, index);
132+
currentType = current?.GetType();
133+
continue;
134+
}
135+
136+
// Not found
137+
pis = null!;
138+
return null;
79139
}
80140
else
81141
{
82-
members = null!;
83-
return null;
142+
// Property access
143+
pi = currentType?.GetProperty(part, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
144+
if (pi == null)
145+
{
146+
pis = null!;
147+
return null;
148+
}
149+
current = pi.GetValue(current);
150+
pis[i++] = (pi, null);
151+
currentType = current?.GetType();
84152
}
85153
}
86154

87-
return obj;
155+
return current;
88156
}
89157

90158
/// <summary>

0 commit comments

Comments
 (0)