Skip to content

Commit e9af6a9

Browse files
Avid29Arlodotexe
authored andcommitted
More ListViewExtensions cleanup
1 parent 51b6906 commit e9af6a9

3 files changed

Lines changed: 99 additions & 114 deletions

File tree

components/Extensions/src/ListViewBase/ListViewExtensions.AlternateRows.cs

Lines changed: 62 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -10,57 +10,49 @@ namespace CommunityToolkit.WinUI;
1010
/// </summary>
1111
public static partial class ListViewExtensions
1212
{
13-
private static Dictionary<IObservableVector<object>, ListViewBase> _itemsForList = new Dictionary<IObservableVector<object>, ListViewBase>();
13+
private static readonly Dictionary<IObservableVector<object>, ListViewBase> _trackedListViews = [];
1414

1515
/// <summary>
1616
/// Attached <see cref="DependencyProperty"/> for binding a <see cref="Brush"/> as an alternate background color to a <see cref="ListViewBase"/>
1717
/// </summary>
18-
public static readonly DependencyProperty AlternateColorProperty = DependencyProperty.RegisterAttached("AlternateColor", typeof(Brush), typeof(ListViewExtensions), new PropertyMetadata(null, OnAlternateColorPropertyChanged));
18+
public static readonly DependencyProperty AlternateColorProperty =
19+
DependencyProperty.RegisterAttached("AlternateColor", typeof(Brush), typeof(ListViewExtensions),
20+
new PropertyMetadata(null, OnAlternateColorPropertyChanged));
1921

2022
/// <summary>
2123
/// Attached <see cref="DependencyProperty"/> for binding a <see cref="DataTemplate"/> as an alternate template to a <see cref="ListViewBase"/>
2224
/// </summary>
23-
public static readonly DependencyProperty AlternateItemTemplateProperty = DependencyProperty.RegisterAttached("AlternateItemTemplate", typeof(DataTemplate), typeof(ListViewExtensions), new PropertyMetadata(null, OnAlternateItemTemplatePropertyChanged));
25+
public static readonly DependencyProperty AlternateItemTemplateProperty =
26+
DependencyProperty.RegisterAttached("AlternateItemTemplate", typeof(DataTemplate), typeof(ListViewExtensions),
27+
new PropertyMetadata(null, OnAlternateItemTemplatePropertyChanged));
2428

2529
/// <summary>
2630
/// Gets the alternate <see cref="Brush"/> associated with the specified <see cref="ListViewBase"/>
2731
/// </summary>
2832
/// <param name="obj">The <see cref="ListViewBase"/> to get the associated <see cref="Brush"/> from</param>
2933
/// <returns>The <see cref="Brush"/> associated with the <see cref="ListViewBase"/></returns>
30-
public static Brush GetAlternateColor(ListViewBase obj)
31-
{
32-
return (Brush)obj.GetValue(AlternateColorProperty);
33-
}
34+
public static Brush GetAlternateColor(ListViewBase obj) => (Brush)obj.GetValue(AlternateColorProperty);
3435

3536
/// <summary>
3637
/// Sets the alternate <see cref="Brush"/> associated with the specified <see cref="DependencyObject"/>
3738
/// </summary>
3839
/// <param name="obj">The <see cref="ListViewBase"/> to associate the <see cref="Brush"/> with</param>
3940
/// <param name="value">The <see cref="Brush"/> for binding to the <see cref="ListViewBase"/></param>
40-
public static void SetAlternateColor(ListViewBase obj, Brush value)
41-
{
42-
obj.SetValue(AlternateColorProperty, value);
43-
}
41+
public static void SetAlternateColor(ListViewBase obj, Brush value) => obj.SetValue(AlternateColorProperty, value);
4442

4543
/// <summary>
4644
/// Gets the <see cref="DataTemplate"/> associated with the specified <see cref="ListViewBase"/>
4745
/// </summary>
4846
/// <param name="obj">The <see cref="ListViewBase"/> to get the associated <see cref="DataTemplate"/> from</param>
4947
/// <returns>The <see cref="DataTemplate"/> associated with the <see cref="ListViewBase"/></returns>
50-
public static DataTemplate GetAlternateItemTemplate(ListViewBase obj)
51-
{
52-
return (DataTemplate)obj.GetValue(AlternateItemTemplateProperty);
53-
}
48+
public static DataTemplate GetAlternateItemTemplate(ListViewBase obj) => (DataTemplate)obj.GetValue(AlternateItemTemplateProperty);
5449

5550
/// <summary>
5651
/// Sets the <see cref="DataTemplate"/> associated with the specified <see cref="ListViewBase"/>
5752
/// </summary>
5853
/// <param name="obj">The <see cref="ListViewBase"/> to associate the <see cref="DataTemplate"/> with</param>
5954
/// <param name="value">The <see cref="DataTemplate"/> for binding to the <see cref="ListViewBase"/></param>
60-
public static void SetAlternateItemTemplate(ListViewBase obj, DataTemplate value)
61-
{
62-
obj.SetValue(AlternateItemTemplateProperty, value);
63-
}
55+
public static void SetAlternateItemTemplate(ListViewBase obj, DataTemplate value) => obj.SetValue(AlternateItemTemplateProperty, value);
6456

6557
private static void OnAlternateColorPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
6658
{
@@ -70,23 +62,29 @@ private static void OnAlternateColorPropertyChanged(DependencyObject sender, Dep
7062
// Cleanup existing subscriptions
7163
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
7264
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
73-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
65+
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;
7466

75-
_itemsForList[listViewBase.Items] = listViewBase;
67+
_trackedListViews[listViewBase.Items] = listViewBase;
7668

7769
// Resubscribe to events as necessary
7870
if (GetAlternateColor(listViewBase) is not null)
7971
{
8072
listViewBase.ContainerContentChanging += ColorContainerContentChanging;
8173
listViewBase.Items.VectorChanged += ColorItemsVectorChanged;
82-
listViewBase.Unloaded += OnListViewBaseUnloaded;
74+
listViewBase.Unloaded += OnListViewBaseUnloaded_AltRow;
8375
}
8476
}
8577

8678
private static void ColorContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
8779
{
88-
var itemContainer = args.ItemContainer as Control;
89-
SetItemContainerBackground(sender, itemContainer, args.ItemIndex);
80+
// Get the row's item container, or contents as a fallback
81+
Control? control = args.ItemContainer ?? args.Item as Control;
82+
83+
// Update the row background if the item was found
84+
if (control is not null)
85+
{
86+
SetRowBackground(sender, control, args.ItemIndex);
87+
}
9088
}
9189

9290
private static void OnAlternateItemTemplatePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
@@ -96,74 +94,55 @@ private static void OnAlternateItemTemplatePropertyChanged(DependencyObject send
9694

9795
// Cleanup existing subscriptions
9896
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
99-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
97+
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;
10098

10199
// Resubscribe to events as necessary
102100
if (GetAlternateItemTemplate(listViewBase) != null)
103101
{
104102
listViewBase.ContainerContentChanging += ItemTemplateContainerContentChanging;
105-
listViewBase.Unloaded += OnListViewBaseUnloaded;
103+
listViewBase.Unloaded += OnListViewBaseUnloaded_AltRow;
106104
}
107105
}
108106

109107
private static void ItemTemplateContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
110108
{
111-
if (args.ItemIndex % 2 == 0)
112-
{
113-
args.ItemContainer.ContentTemplate = GetAlternateItemTemplate(sender);
114-
}
115-
else
116-
{
117-
args.ItemContainer.ContentTemplate = sender.ItemTemplate;
118-
}
119-
}
120-
121-
private static void OnListViewBaseUnloaded(object sender, RoutedEventArgs e)
122-
{
123-
if (sender is ListViewBase listViewBase)
124-
{
125-
_itemsForList.Remove(listViewBase.Items);
126-
127-
listViewBase.ContainerContentChanging -= ItemContainerStretchDirectionChanging;
128-
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
129-
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
130-
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
131-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
132-
}
109+
var template = args.ItemIndex % 2 == 0 ? GetAlternateItemTemplate(sender) : sender.ItemTemplate;
110+
args.ItemContainer.ContentTemplate = template;
133111
}
134112

135113
private static void ColorItemsVectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs args)
136114
{
137-
// If the index is at the end we can ignore
115+
// If the index is at the end, no other items were affected
116+
// and there's no action to take
138117
if (args.Index == (sender.Count - 1))
139-
{
140118
return;
141-
}
142119

143-
// Only need to handle Inserted and Removed because we'll handle everything else in the
144-
// ColorContainerContentChanging method
145-
if ((args.CollectionChange == CollectionChange.ItemInserted) || (args.CollectionChange == CollectionChange.ItemRemoved))
120+
// This function is for updating indirectly affected items
121+
// Therefore we only need to handle items inserted and removed where every
122+
// item beneath would potentially change if they are even or odd.
123+
if (args.CollectionChange is not (CollectionChange.ItemInserted or CollectionChange.ItemRemoved))
124+
return;
125+
126+
// Attempt to get the list view for the affected items
127+
_trackedListViews.TryGetValue(sender, out ListViewBase? listViewBase);
128+
if (listViewBase is null)
129+
return;
130+
131+
int index = (int)args.Index;
132+
for (int i = index; i < sender.Count; i++)
146133
{
147-
_itemsForList.TryGetValue(sender, out ListViewBase? listViewBase);
148-
if (listViewBase == null)
149-
return;
134+
// Get item container or element at index
135+
var itemContainer = listViewBase.ContainerFromIndex(i) as Control;
136+
itemContainer ??= listViewBase.Items[i] as Control;
150137

151-
int index = (int)args.Index;
152-
for (int i = index; i < sender.Count; i++)
138+
if (itemContainer is not null)
153139
{
154-
// Get item container or element at index
155-
var itemContainer = listViewBase.ContainerFromIndex(i) as Control;
156-
itemContainer ??= listViewBase.Items[i] as Control;
157-
158-
if (itemContainer is not null)
159-
{
160-
SetItemContainerBackground(listViewBase, itemContainer, i);
161-
}
140+
SetRowBackground(listViewBase, itemContainer, i);
162141
}
163142
}
164143
}
165144

166-
private static void SetItemContainerBackground(ListViewBase sender, Control itemContainer, int itemIndex)
145+
private static void SetRowBackground(ListViewBase sender, Control itemContainer, int itemIndex)
167146
{
168147
var brush = itemIndex % 2 == 0 ? GetAlternateColor(sender) : null;
169148
var rootBorder = itemContainer.FindDescendant<Border>();
@@ -174,4 +153,19 @@ private static void SetItemContainerBackground(ListViewBase sender, Control item
174153
rootBorder.Background = brush;
175154
}
176155
}
156+
157+
private static void OnListViewBaseUnloaded_AltRow(object sender, RoutedEventArgs e)
158+
{
159+
if (sender is not ListViewBase listViewBase)
160+
return;
161+
162+
// Untrack the list view
163+
_trackedListViews.Remove(listViewBase.Items);
164+
165+
// Unsubscribe from events
166+
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
167+
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
168+
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
169+
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;
170+
}
177171
}

components/Extensions/src/ListViewBase/ListViewExtensions.Command.cs

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,66 +12,54 @@ namespace CommunityToolkit.WinUI;
1212
public static partial class ListViewExtensions
1313
{
1414
/// <summary>
15-
/// Attached <see cref="DependencyProperty"/> for binding an <see cref="global::System.Windows.Input.ICommand"/> to handle ListViewBase Item interaction by means of <see cref="ListViewBase"/> ItemClick event. ListViewBase IsItemClickEnabled must be set to true.
15+
/// Attached <see cref="DependencyProperty"/> for binding an <see cref="ICommand"/> to handle ListViewBase Item interaction by means of <see cref="ListViewBase"/> ItemClick event. ListViewBase IsItemClickEnabled must be set to true.
1616
/// </summary>
17-
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ListViewExtensions), new PropertyMetadata(null, OnCommandPropertyChanged));
17+
public static readonly DependencyProperty CommandProperty =
18+
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ListViewExtensions),
19+
new PropertyMetadata(null, OnCommandPropertyChanged));
1820

1921
/// <summary>
2022
/// Gets the <see cref="ICommand"/> associated with the specified <see cref="ListViewBase"/>
2123
/// </summary>
2224
/// <param name="obj">The <see cref="ListViewBase"/> to get the associated <see cref="ICommand"/> from</param>
2325
/// <returns>The <see cref="ICommand"/> associated with the <see cref="ListViewBase"/></returns>
24-
public static ICommand GetCommand(ListViewBase obj)
25-
{
26-
return (ICommand)obj.GetValue(CommandProperty);
27-
}
26+
public static ICommand GetCommand(ListViewBase obj) => (ICommand)obj.GetValue(CommandProperty);
2827

2928
/// <summary>
3029
/// Sets the <see cref="ICommand"/> associated with the specified <see cref="ListViewBase"/>
3130
/// </summary>
3231
/// <param name="obj">The <see cref="ListViewBase"/> to associate the <see cref="ICommand"/> with</param>
3332
/// <param name="value">The <see cref="ICommand"/> for binding to the <see cref="ListViewBase"/></param>
34-
public static void SetCommand(ListViewBase obj, ICommand value)
35-
{
36-
obj.SetValue(CommandProperty, value);
37-
}
33+
public static void SetCommand(ListViewBase obj, ICommand value) => obj.SetValue(CommandProperty, value);
3834

3935
private static void OnCommandPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
4036
{
41-
var listViewBase = sender as ListViewBase;
42-
43-
if (listViewBase == null)
44-
{
37+
if (sender is not ListViewBase listViewBase)
4538
return;
46-
}
4739

4840
var oldCommand = args.OldValue as ICommand;
49-
if (oldCommand != null)
41+
if (oldCommand is not null)
5042
{
5143
listViewBase.ItemClick -= OnListViewBaseItemClick;
5244
}
5345

5446
var newCommand = args.NewValue as ICommand;
55-
if (newCommand != null)
47+
if (newCommand is not null)
5648
{
5749
listViewBase.ItemClick += OnListViewBaseItemClick;
5850
}
5951
}
6052

6153
private static void OnListViewBaseItemClick(object sender, ItemClickEventArgs e)
6254
{
63-
if (sender is ListViewBase listViewBase)
64-
{
65-
var command = GetCommand(listViewBase);
66-
if (listViewBase == null || command == null)
67-
{
68-
return;
69-
}
55+
if (sender is not ListViewBase listViewBase)
56+
return;
7057

71-
if (command.CanExecute(e.ClickedItem))
72-
{
73-
command.Execute(e.ClickedItem);
74-
}
75-
}
58+
var command = GetCommand(listViewBase);
59+
if (command is null)
60+
return;
61+
62+
if (command.CanExecute(e.ClickedItem))
63+
command.Execute(e.ClickedItem);
7664
}
7765
}

components/Extensions/src/ListViewBase/ListViewExtensions.StretchItemContainer.cs

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,21 @@ public static partial class ListViewExtensions
1414
/// </summary>
1515
public static readonly DependencyProperty ItemContainerStretchDirectionProperty = DependencyProperty.RegisterAttached("ItemContainerStretchDirection", typeof(ItemContainerStretchDirection), typeof(ListViewExtensions), new PropertyMetadata(null, OnItemContainerStretchDirectionPropertyChanged));
1616

17-
/// <summary>
17+
/// <summary>
1818
/// Gets the stretch <see cref="ItemContainerStretchDirection"/> associated with the specified <see cref="ListViewBase"/>
1919
/// </summary>
2020
/// <param name="obj">The <see cref="ListViewBase"/> to get the associated <see cref="ItemContainerStretchDirection"/> from</param>
2121
/// <returns>The <see cref="ItemContainerStretchDirection"/> associated with the <see cref="ListViewBase"/></returns>
2222
public static ItemContainerStretchDirection? GetItemContainerStretchDirection(ListViewBase obj)
23-
{
24-
return (ItemContainerStretchDirection)obj.GetValue(ItemContainerStretchDirectionProperty);
25-
}
23+
=> (ItemContainerStretchDirection)obj.GetValue(ItemContainerStretchDirectionProperty);
2624

2725
/// <summary>
2826
/// Sets the stretch <see cref="ItemContainerStretchDirection"/> associated with the specified <see cref="ListViewBase"/>
2927
/// </summary>
3028
/// <param name="obj">The <see cref="ListViewBase"/> to associate the <see cref="ItemContainerStretchDirection"/> with</param>
3129
/// <param name="value">The <see cref="ItemContainerStretchDirection"/> for binding to the <see cref="ListViewBase"/></param>
3230
public static void SetItemContainerStretchDirection(ListViewBase obj, ItemContainerStretchDirection value)
33-
{
34-
obj.SetValue(ItemContainerStretchDirectionProperty, value);
35-
}
31+
=> obj.SetValue(ItemContainerStretchDirectionProperty, value);
3632

3733
private static void OnItemContainerStretchDirectionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
3834
{
@@ -41,29 +37,36 @@ private static void OnItemContainerStretchDirectionPropertyChanged(DependencyObj
4137

4238
// Cleanup existing subscriptions
4339
listViewBase.ContainerContentChanging -= ItemContainerStretchDirectionChanging;
44-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
40+
listViewBase.Unloaded -= OnListViewBaseUnloaded_StretchDirection;
4541

4642
// Resubscribe to events as necessary
4743
if (GetItemContainerStretchDirection(listViewBase) is not null)
4844
{
4945
listViewBase.ContainerContentChanging += ItemContainerStretchDirectionChanging;
50-
listViewBase.Unloaded += OnListViewBaseUnloaded;
46+
listViewBase.Unloaded += OnListViewBaseUnloaded_StretchDirection;
5147
}
5248
}
5349

5450
private static void ItemContainerStretchDirectionChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
5551
{
5652
var stretchDirection = GetItemContainerStretchDirection(sender);
5753

58-
if (stretchDirection == ItemContainerStretchDirection.Vertical || stretchDirection == ItemContainerStretchDirection.Both)
59-
{
60-
args.ItemContainer.VerticalContentAlignment = VerticalAlignment.Stretch;
61-
}
62-
63-
if (stretchDirection == ItemContainerStretchDirection.Horizontal || stretchDirection == ItemContainerStretchDirection.Both)
64-
{
54+
// Set vertical content stretching
55+
if (stretchDirection is ItemContainerStretchDirection.Horizontal or ItemContainerStretchDirection.Both)
6556
args.ItemContainer.HorizontalContentAlignment = HorizontalAlignment.Stretch;
66-
}
57+
58+
// Set horizontal content stretching
59+
if (stretchDirection is ItemContainerStretchDirection.Vertical or ItemContainerStretchDirection.Both)
60+
args.ItemContainer.VerticalContentAlignment = VerticalAlignment.Stretch;
6761
}
6862

63+
private static void OnListViewBaseUnloaded_StretchDirection(object sender, RoutedEventArgs e)
64+
{
65+
if (sender is not ListViewBase listViewBase)
66+
return;
67+
68+
// Unsubscribe from events
69+
listViewBase.ContainerContentChanging -= ItemContainerStretchDirectionChanging;
70+
listViewBase.Unloaded -= OnListViewBaseUnloaded_StretchDirection;
71+
}
6972
}

0 commit comments

Comments
 (0)