Skip to content

Commit 84042b2

Browse files
authored
Merge pull request #253 from w-ahmad/feat/conditional_styling
feat: Implemented Conditional Cell Styling
2 parents 9bd81e1 + 2fa146d commit 84042b2

File tree

11 files changed

+273
-25
lines changed

11 files changed

+273
-25
lines changed
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
#if WINDOWS
2+
using Microsoft.UI.Xaml;
3+
using Microsoft.UI.Xaml.Markup;
4+
using System;
5+
using System.Collections.Generic;
6+
using System.ComponentModel;
7+
using Windows.Foundation.Collections;
8+
9+
namespace WinUI.TableView.Collections;
10+
11+
/// <summary>
12+
/// Provides a strongly-typed wrapper around <see cref="DependencyObjectCollection"/>
13+
/// for collections containing <typeparamref name="T"/> items.
14+
/// </summary>
15+
/// <typeparam name="T">The type of items in the collection.</typeparam>
16+
[ContentProperty(Name = nameof(Items))]
17+
[EditorBrowsable(EditorBrowsableState.Never)]
18+
public partial class DependencyObjectCollection<T> : DependencyObjectCollection, IList<T> where T : DependencyObject
19+
{
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="DependencyObjectCollection{T}"/> class.
22+
/// </summary>
23+
public DependencyObjectCollection()
24+
{
25+
VectorChanged += OnVectorChanged;
26+
}
27+
28+
/// <summary>
29+
/// Handles changes to the underlying vector of <see cref="DependencyObject"/> items.
30+
/// </summary>
31+
private void OnVectorChanged(IObservableVector<DependencyObject> sender, IVectorChangedEventArgs args)
32+
{
33+
if (args.CollectionChange is CollectionChange.ItemInserted)
34+
{
35+
if (this[(int)args.Index] is not T)
36+
{
37+
throw new InvalidOperationException($"Only items of type {typeof(T).FullName} can be added to this collection.");
38+
}
39+
}
40+
}
41+
42+
/// <summary>
43+
/// Gets an <see cref="IList{T}"/> view over this collection.
44+
/// This provides strongly-typed access to the underlying items.
45+
/// </summary>
46+
public IList<T> Items => this;
47+
48+
T IList<T>.this[int index]
49+
{
50+
get => (T)base[index];
51+
set => base[index] = value;
52+
}
53+
54+
int ICollection<T>.Count => Count;
55+
56+
bool ICollection<T>.IsReadOnly => IsReadOnly;
57+
58+
void ICollection<T>.Add(T item)
59+
{
60+
Add(item);
61+
}
62+
63+
void ICollection<T>.Clear()
64+
{
65+
Clear();
66+
}
67+
68+
bool ICollection<T>.Contains(T item)
69+
{
70+
return Contains(item);
71+
}
72+
73+
void ICollection<T>.CopyTo(T[] array, int arrayIndex)
74+
{
75+
CopyTo(array as DependencyObject[], arrayIndex);
76+
}
77+
78+
IEnumerator<T> IEnumerable<T>.GetEnumerator()
79+
{
80+
foreach (var item in this)
81+
{
82+
yield return (T)item;
83+
}
84+
}
85+
86+
int IList<T>.IndexOf(T item)
87+
{
88+
return IndexOf(item);
89+
}
90+
91+
void IList<T>.Insert(int index, T item)
92+
{
93+
Insert(index, item);
94+
}
95+
96+
bool ICollection<T>.Remove(T item)
97+
{
98+
var index = IndexOf(item);
99+
100+
if (index >= 0)
101+
{
102+
RemoveAt(index);
103+
return true;
104+
}
105+
106+
return false;
107+
}
108+
109+
void IList<T>.RemoveAt(int index)
110+
{
111+
RemoveAt(index);
112+
}
113+
}
114+
#endif

src/Columns/TableViewColumn.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.UI.Xaml;
22
using Microsoft.UI.Xaml.Data;
33
using System;
4+
using System.Collections.Generic;
45
using WinUI.TableView.Extensions;
56
using SD = WinUI.TableView.SortDirection;
67

@@ -20,6 +21,14 @@ public abstract partial class TableViewColumn : DependencyObject
2021
private bool _isFrozen;
2122
private Func<object, object?>? _funcCompiledPropertyPath;
2223

24+
/// <summary>
25+
/// Initializes a new instance of the <see cref="TableViewColumn"/> class with default conditional cell styles.
26+
/// </summary>
27+
public TableViewColumn()
28+
{
29+
SetValue(ConditionalCellStylesProperty, new TableViewConditionalCellStylesCollection());
30+
}
31+
2332
/// <summary>
2433
/// Generates a display element for the cell.
2534
/// </summary>
@@ -277,6 +286,15 @@ public bool CanReorder
277286
set => SetValue(CanReorderProperty, value);
278287
}
279288

289+
/// <summary>
290+
/// Gets or sets the collection of conditional cell styles for the column.
291+
/// </summary>
292+
public IList<TableViewConditionalCellStyle> ConditionalCellStyles
293+
{
294+
get => (IList<TableViewConditionalCellStyle>)GetValue(ConditionalCellStylesProperty);
295+
set => SetValue(ConditionalCellStylesProperty, value);
296+
}
297+
280298
internal TableViewColumnsCollection? OwningCollection { get; set; }
281299

282300
/// <summary>
@@ -511,4 +529,9 @@ private static void OnHeaderStyleChanged(DependencyObject d, DependencyPropertyC
511529
/// Identifies the CanReorder dependency property.
512530
/// </summary>
513531
public static readonly DependencyProperty CanReorderProperty = DependencyProperty.Register(nameof(CanReorder), typeof(bool), typeof(TableViewColumn), new PropertyMetadata(true));
532+
533+
/// <summary>
534+
/// Identifies the <see cref="ConditionalCellStyles"/> dependency property.
535+
/// </summary>
536+
public static readonly DependencyProperty ConditionalCellStylesProperty = DependencyProperty.Register(nameof(ConditionalCellStyles), typeof(IList<TableViewConditionalCellStyle>), typeof(TableViewColumn), new PropertyMetadata(default));
514537
}

src/ItemsSource/CollectionView.Events.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,9 @@ private void OnPropertyChanged([CallerMemberName] string propertyName = null!)
8383
/// Occurs when a property value changes.
8484
/// </summary>
8585
public event PropertyChangedEventHandler? PropertyChanged;
86+
87+
/// <summary>
88+
/// Occurs when an item's property value changes.
89+
/// </summary>
90+
public event PropertyChangedEventHandler? ItemPropertyChanged;
8691
}

src/ItemsSource/CollectionView.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ private void OnSourceCollectionChanged(object? arg1, NotifyCollectionChangedEven
141141
/// </summary>
142142
private void OnItemPropertyChanged(object? item, PropertyChangedEventArgs e)
143143
{
144+
ItemPropertyChanged?.Invoke(item, e);
145+
144146
if (!AllowLiveShaping || item is null || string.IsNullOrEmpty(e.PropertyName))
145147
{
146148
return;

src/TableView.Properties.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,12 @@ public partial class TableView
254254
/// Identifies the CanReorderColumns dependency property.
255255
/// </summary>
256256
public static readonly DependencyProperty CanReorderColumnsProperty = DependencyProperty.Register(nameof(CanReorderColumns), typeof(bool), typeof(TableView), new PropertyMetadata(true));
257+
258+
/// <summary>
259+
/// Identifies the <see cref="ConditionalCellStyles"/> dependency property.
260+
/// </summary>
261+
public static readonly DependencyProperty ConditionalCellStylesProperty = DependencyProperty.Register(nameof(ConditionalCellStyles), typeof(IList<TableViewConditionalCellStyle>), typeof(TableView), new PropertyMetadata(default));
262+
257263
/// <summary>
258264
/// Gets or sets a value indicating whether opening the column filter over header right-click is enabled.
259265
/// </summary>
@@ -301,6 +307,15 @@ public TableViewCellSlot? CurrentCellSlot
301307
set => SetValue(CurrentCellSlotProperty, value);
302308
}
303309

310+
/// <summary>
311+
/// Gets or sets the collection of conditional cell styles.
312+
/// </summary>
313+
public IList<TableViewConditionalCellStyle> ConditionalCellStyles
314+
{
315+
get => (IList<TableViewConditionalCellStyle>)GetValue(ConditionalCellStylesProperty);
316+
set => SetValue(ConditionalCellStylesProperty, value);
317+
}
318+
304319
/// <summary>
305320
/// Gets or sets the selection start cell slot.
306321
/// </summary>

src/TableView.cs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
using CommunityToolkit.WinUI;
2-
using Microsoft.UI;
32
using Microsoft.UI.Xaml;
43
using Microsoft.UI.Xaml.Controls;
54
using Microsoft.UI.Xaml.Controls.Primitives;
@@ -8,6 +7,7 @@
87
using System;
98
using System.Collections;
109
using System.Collections.Generic;
10+
using System.ComponentModel;
1111
using System.ComponentModel.DataAnnotations;
1212
using System.IO;
1313
using System.Linq;
@@ -19,7 +19,6 @@
1919
using Windows.Storage;
2020
using Windows.Storage.Pickers;
2121
using Windows.System;
22-
using WinRT.Interop;
2322
using WinUI.TableView.Extensions;
2423
using WinUI.TableView.Helpers;
2524

@@ -49,12 +48,17 @@ public TableView()
4948

5049
Columns = new TableViewColumnsCollection(this);
5150
FilterHandler = new ColumnFilterHandler(this);
51+
5252
base.ItemsSource = _collectionView;
5353
base.SelectionMode = SelectionMode;
54+
55+
SetValue(ConditionalCellStylesProperty, new TableViewConditionalCellStylesCollection());
5456
RegisterPropertyChangedCallback(ItemsControl.ItemsSourceProperty, OnBaseItemsSourceChanged);
5557
RegisterPropertyChangedCallback(ListViewBase.SelectionModeProperty, OnBaseSelectionModeChanged);
58+
5659
Loaded += OnLoaded;
5760
SelectionChanged += TableView_SelectionChanged;
61+
_collectionView.ItemPropertyChanged += OnItemPropertyChanged;
5862
}
5963

6064
/// <summary>
@@ -84,6 +88,16 @@ private void TableView_SelectionChanged(object sender, SelectionChangedEventArgs
8488
}
8589
}
8690

91+
/// <summary>
92+
/// Handles the PropertyChanged event of an item in the TableView.
93+
/// </summary>
94+
private void OnItemPropertyChanged(object? sender, PropertyChangedEventArgs e)
95+
{
96+
var row = ContainerFromItem(sender) as TableViewRow;
97+
98+
row?.EnsureCellsStyle(default, sender);
99+
}
100+
87101
/// <inheritdoc/>
88102
protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
89103
{
@@ -93,6 +107,7 @@ protected override void PrepareContainerForItemOverride(DependencyObject element
93107
{
94108
if (element is TableViewRow row)
95109
{
110+
row.EnsureCellsStyle(default, item);
96111
row.ApplyCellsSelectionState();
97112

98113
if (CurrentCellSlot.HasValue)
@@ -739,8 +754,8 @@ private async Task<StorageFile> GetStorageFile()
739754
var savePicker = new FileSavePicker();
740755
savePicker.FileTypeChoices.Add("CSV (Comma delimited)", [".csv"]);
741756
#if WINDOWS
742-
var hWnd = Win32Interop.GetWindowFromWindowId(XamlRoot.ContentIslandEnvironment.AppWindowId);
743-
InitializeWithWindow.Initialize(savePicker, hWnd);
757+
var hWnd = Microsoft.UI.Win32Interop.GetWindowFromWindowId(XamlRoot.ContentIslandEnvironment.AppWindowId);
758+
WinRT.Interop.InitializeWithWindow.Initialize(savePicker, hWnd);
744759
#endif
745760

746761
return await savePicker.PickSaveFileAsync();

src/TableViewCell.cs

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
using Microsoft.UI.Xaml.Media;
77
using Microsoft.UI.Xaml.Shapes;
88
using System;
9+
using System.Collections.Generic;
910
using System.Linq;
1011
using System.Threading.Tasks;
1112
using Windows.Foundation;
12-
using WinUI.TableView.Extensions;
1313
using WinUI.TableView.Helpers;
1414

1515
namespace WinUI.TableView;
@@ -32,6 +32,7 @@ public partial class TableViewCell : ContentControl
3232
private Rectangle? _v_gridLine;
3333
private object? _uneditedValue;
3434
private RoutedEventArgs? _editingArgs;
35+
private IList<TableViewConditionalCellStyle>? _cellStyles;
3536

3637
/// <summary>
3738
/// Initializes a new instance of the TableViewCell class.
@@ -84,6 +85,7 @@ protected override void OnApplyTemplate()
8485
_v_gridLine = GetTemplateChild("VerticalGridLine") as Rectangle;
8586

8687
EnsureGridLines();
88+
EnsureStyle(Row?.Content);
8789
}
8890

8991
/// <inheritdoc/>
@@ -111,7 +113,7 @@ protected override Size MeasureOverride(Size availableSize)
111113
if (Column is TableViewTemplateColumn)
112114
{
113115
#if WINDOWS
114-
if (element is ContentControl { ContentTemplateRoot: FrameworkElement root })
116+
if (element is ContentControl { ContentTemplateRoot: FrameworkElement root })
115117
#else
116118
if (element.FindDescendant<ContentPresenter>() is { ContentTemplateRoot: FrameworkElement root })
117119
#endif
@@ -368,7 +370,6 @@ internal async Task<bool> BeginCellEditing(RoutedEventArgs editingArgs)
368370
return false;
369371
}
370372

371-
372373
/// <summary>
373374
/// Prepares the cell for editing.
374375
/// </summary>
@@ -531,6 +532,20 @@ internal void EnsureGridLines()
531532
}
532533
}
533534

535+
/// <summary>
536+
/// Ensures the correct style is applied to the cell.
537+
/// </summary>
538+
/// <param name="item">The data item associated with the cell.</param>
539+
internal void EnsureStyle(object? item)
540+
{
541+
_cellStyles ??= [
542+
.. Column?.ConditionalCellStyles ?? [], // Column styles have first priority
543+
.. TableView?.ConditionalCellStyles ?? []]; // TableView styles have second priority
544+
545+
Style = _cellStyles.FirstOrDefault(c => c.Predicate?.Invoke(new(Column!, item)) is true)?
546+
.Style ?? Column?.CellStyle ?? TableView?.CellStyle;
547+
}
548+
534549
/// <summary>
535550
/// Gets a value indicating whether the cell is read-only.
536551
/// </summary>

0 commit comments

Comments
 (0)