Skip to content

Commit b67f901

Browse files
committed
feat: add hierarchy (tree-view) support
1 parent 6ad6f6d commit b67f901

17 files changed

Lines changed: 3001 additions & 67 deletions

src/ColumnFilterHandler.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ public virtual IList<TableViewFilterItem> GetFilterItems(TableViewColumn column,
4141
}));
4242
}
4343

44-
collectionView.Source = (column.TableView.ItemsSource as IEnumerable) ?? Enumerable.Empty<object>();
44+
collectionView.Source = (_tableView.IsHierarchicalEnabled
45+
? (IEnumerable)_tableView.GetAllHierarchyItemsFlat().ToList()
46+
: column.TableView.ItemsSource as IEnumerable) ?? Enumerable.Empty<object>();
4547

4648
var items = _tableView.ShowFilterItemsCount ?
4749
GetFilterItemsWithCount(column, searchText, collectionView) :
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using System;
2+
3+
namespace WinUI.TableView;
4+
5+
/// <summary>
6+
/// Provides data for the <see cref="TableView.RowExpanded"/> and <see cref="TableView.RowCollapsed"/> events.
7+
/// </summary>
8+
public sealed class TableViewRowExpansionChangedEventArgs : EventArgs
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="TableViewRowExpansionChangedEventArgs"/> class.
12+
/// </summary>
13+
public TableViewRowExpansionChangedEventArgs(object item, int index, bool isExpanded)
14+
{
15+
Item = item;
16+
Index = index;
17+
IsExpanded = isExpanded;
18+
}
19+
20+
/// <summary>
21+
/// Gets the item whose expansion state changed.
22+
/// </summary>
23+
public object Item { get; }
24+
25+
/// <summary>
26+
/// Gets the index of the item in the display list.
27+
/// </summary>
28+
public int Index { get; }
29+
30+
/// <summary>
31+
/// Gets a value indicating whether the item is now expanded.
32+
/// </summary>
33+
public bool IsExpanded { get; }
34+
}

src/ItemsSource/CollectionView.Properties.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@ namespace WinUI.TableView;
88

99
partial class CollectionView
1010
{
11+
/// <summary>
12+
/// Gets or sets a value indicating whether sort descriptions should be ignored when
13+
/// rebuilding the view. Set by <see cref="TableView"/> when hierarchical mode is active
14+
/// so that per-level sorting is applied during flattening instead of globally.
15+
/// </summary>
16+
internal bool BypassSort { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets a value indicating whether filter descriptions should be ignored when
20+
/// rebuilding the view. Set by <see cref="TableView"/> when hierarchical mode is active
21+
/// so that subtree-aware filtering is applied during hierarchy flattening instead of
22+
/// the flat per-item filter used in normal mode.
23+
/// </summary>
24+
internal bool BypassFilter { get; set; }
25+
1126
/// <summary>
1227
/// Gets or sets the source collection.
1328
/// </summary>

src/ItemsSource/CollectionView.cs

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ private void OnItemPropertyChanged(object? item, PropertyChangedEventArgs e)
255255
return;
256256
}
257257

258-
if (FilterDescriptions.Any(fd => string.IsNullOrEmpty(fd.PropertyName) || fd.PropertyName == e.PropertyName))
258+
if (!BypassFilter && FilterDescriptions.Any(fd => string.IsNullOrEmpty(fd.PropertyName) || fd.PropertyName == e.PropertyName))
259259
{
260260
var filterResult = FilterDescriptions.All(x => x.Predicate(item));
261261
var viewIndex = _view.IndexOf(item);
@@ -318,7 +318,7 @@ private void HandleSourceChanged()
318318

319319
if (Source is not null)
320320
{
321-
if (FilterDescriptions.Count > 0)
321+
if (!BypassFilter && FilterDescriptions.Count > 0)
322322
{
323323
foreach (var item in Source)
324324
{
@@ -331,7 +331,7 @@ private void HandleSourceChanged()
331331
_view.AddRange(_source.OfType<object>());
332332
}
333333

334-
if (SortDescriptions.Count > 0)
334+
if (SortDescriptions.Count > 0 && !BypassSort)
335335
_view.Sort(this);
336336
}
337337

@@ -344,6 +344,8 @@ private void HandleSourceChanged()
344344
/// </summary>
345345
private void HandleFilterChanged()
346346
{
347+
if (BypassFilter) return;
348+
347349
if (FilterDescriptions.Count > 0)
348350
{
349351
for (var index = 0; index < _view.Count; index++)
@@ -384,24 +386,26 @@ private void HandleFilterChanged()
384386
/// </summary>
385387
private void HandleSortChanged()
386388
{
387-
if (SortDescriptions.Count > 0)
389+
if (!BypassSort)
388390
{
389-
_view.Sort(this);
390-
}
391-
else
392-
{
393-
HandleSourceChanged();
394-
}
391+
if (SortDescriptions.Count > 0)
392+
_view.Sort(this);
393+
else
394+
HandleSourceChanged();
395395

396-
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
396+
OnVectorChanged(new VectorChangedEventArgs(CollectionChange.Reset));
397+
}
398+
// When BypassSort is true the external caller (TableView) handles the full
399+
// rebuild via RebuildHierarchyView(). Firing VectorChanged here would trigger
400+
// a stale RebuildDisplayedItems() before the hierarchy is re-flattened.
397401
}
398402

399403
/// <summary>
400404
/// Handles the addition of an item to the collection.
401405
/// </summary>
402406
private bool HandleItemAdded(int newStartingIndex, object? newItem, int? viewIndex = null)
403407
{
404-
if (!FilterDescriptions.All(x => x.Predicate(newItem)))
408+
if (!BypassFilter && !FilterDescriptions.All(x => x.Predicate(newItem)))
405409
{
406410
return false;
407411
}

src/Strings/en-US/WinUI.TableView.resw

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,4 +180,10 @@
180180
<data name="Copy" xml:space="preserve">
181181
<value>Copy</value>
182182
</data>
183+
<data name="ExpandRow" xml:space="preserve">
184+
<value>Expand row</value>
185+
</data>
186+
<data name="CollapseRow" xml:space="preserve">
187+
<value>Collapse row</value>
188+
</data>
183189
</root>

src/TableView.Events.cs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,30 @@ protected internal virtual void OnColumnReordered(TableViewColumnReorderedEventA
271271
{
272272
ColumnReordered?.Invoke(this, args);
273273
}
274-
}
274+
275+
/// <summary>
276+
/// Occurs when a row is expanded in hierarchical mode.
277+
/// </summary>
278+
public event EventHandler<TableViewRowExpansionChangedEventArgs>? RowExpanded;
279+
280+
/// <summary>
281+
/// Occurs when a row is collapsed in hierarchical mode.
282+
/// </summary>
283+
public event EventHandler<TableViewRowExpansionChangedEventArgs>? RowCollapsed;
284+
285+
/// <summary>
286+
/// Called when a row is expanded.
287+
/// </summary>
288+
protected internal virtual void OnRowExpanded(TableViewRowExpansionChangedEventArgs args)
289+
{
290+
RowExpanded?.Invoke(this, args);
291+
}
292+
293+
/// <summary>
294+
/// Called when a row is collapsed.
295+
/// </summary>
296+
protected internal virtual void OnRowCollapsed(TableViewRowExpansionChangedEventArgs args)
297+
{
298+
RowCollapsed?.Invoke(this, args);
299+
}
300+
}

0 commit comments

Comments
 (0)