Skip to content

Commit d05b243

Browse files
authored
Merge branch 'main' into Color-Helper
2 parents 0d8c297 + e24000f commit d05b243

File tree

3 files changed

+145
-157
lines changed

3 files changed

+145
-157
lines changed

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

Lines changed: 86 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -10,202 +10,166 @@ 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
{
67-
if (sender is ListViewBase listViewBase)
68-
{
69-
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
70-
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
71-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
72-
73-
_itemsForList[listViewBase.Items] = listViewBase;
74-
if (AlternateColorProperty != null)
75-
{
76-
listViewBase.ContainerContentChanging += ColorContainerContentChanging;
77-
listViewBase.Items.VectorChanged += ColorItemsVectorChanged;
78-
listViewBase.Unloaded += OnListViewBaseUnloaded;
79-
}
80-
}
81-
}
59+
if (sender is not ListViewBase listViewBase)
60+
return;
8261

83-
private static void ColorContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
84-
{
85-
var itemContainer = args.ItemContainer as Control;
86-
SetItemContainerBackground(sender, itemContainer, args.ItemIndex);
87-
}
62+
// Cleanup existing subscriptions
63+
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
64+
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
65+
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;
8866

89-
private static void OnAlternateItemTemplatePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
90-
{
91-
if (sender is ListViewBase listViewBase)
67+
// Resubscribe to events as necessary
68+
if (GetAlternateColor(listViewBase) is not null)
9269
{
93-
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
94-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
95-
96-
if (AlternateItemTemplateProperty != null)
97-
{
98-
listViewBase.ContainerContentChanging += ItemTemplateContainerContentChanging;
99-
listViewBase.Unloaded += OnListViewBaseUnloaded;
100-
}
101-
}
102-
}
70+
listViewBase.ContainerContentChanging += ColorContainerContentChanging;
71+
listViewBase.Items.VectorChanged += ColorItemsVectorChanged;
72+
listViewBase.Unloaded += OnListViewBaseUnloaded_AltRow;
10373

104-
private static void ItemTemplateContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
105-
{
106-
if (args.ItemIndex % 2 == 0)
107-
{
108-
args.ItemContainer.ContentTemplate = GetAlternateItemTemplate(sender);
74+
_trackedListViews[listViewBase.Items] = listViewBase;
10975
}
11076
else
11177
{
112-
args.ItemContainer.ContentTemplate = sender.ItemTemplate;
78+
_trackedListViews.Remove(listViewBase.Items);
11379
}
11480
}
11581

116-
private static void OnItemContainerStretchDirectionPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
82+
private static void ColorContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
11783
{
118-
if (sender is ListViewBase listViewBase)
119-
{
120-
listViewBase.ContainerContentChanging -= ItemContainerStretchDirectionChanging;
121-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
84+
// Get the row's item container, or contents as a fallback
85+
Control? control = args.ItemContainer ?? args.Item as Control;
12286

123-
if (ItemContainerStretchDirectionProperty != null)
124-
{
125-
listViewBase.ContainerContentChanging += ItemContainerStretchDirectionChanging;
126-
listViewBase.Unloaded += OnListViewBaseUnloaded;
127-
}
87+
// Update the row background if the item was found
88+
if (control is not null)
89+
{
90+
SetRowBackground(sender, control, args.ItemIndex);
12891
}
12992
}
13093

131-
private static void ItemContainerStretchDirectionChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
94+
private static void OnAlternateItemTemplatePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
13295
{
133-
var stretchDirection = GetItemContainerStretchDirection(sender);
96+
if (sender is not ListViewBase listViewBase)
97+
return;
13498

135-
if (stretchDirection == ItemContainerStretchDirection.Vertical || stretchDirection == ItemContainerStretchDirection.Both)
136-
{
137-
args.ItemContainer.VerticalContentAlignment = VerticalAlignment.Stretch;
138-
}
99+
// Cleanup existing subscriptions
100+
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
101+
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;
139102

140-
if (stretchDirection == ItemContainerStretchDirection.Horizontal || stretchDirection == ItemContainerStretchDirection.Both)
103+
// Resubscribe to events as necessary
104+
if (GetAlternateItemTemplate(listViewBase) is not null)
141105
{
142-
args.ItemContainer.HorizontalContentAlignment = HorizontalAlignment.Stretch;
106+
listViewBase.ContainerContentChanging += ItemTemplateContainerContentChanging;
107+
listViewBase.Unloaded += OnListViewBaseUnloaded_AltRow;
143108
}
144109
}
145110

146-
private static void OnListViewBaseUnloaded(object sender, RoutedEventArgs e)
111+
private static void ItemTemplateContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
147112
{
148-
if (sender is ListViewBase listViewBase)
149-
{
150-
_itemsForList.Remove(listViewBase.Items);
151-
152-
listViewBase.ContainerContentChanging -= ItemContainerStretchDirectionChanging;
153-
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
154-
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
155-
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
156-
listViewBase.Unloaded -= OnListViewBaseUnloaded;
157-
}
113+
var template = args.ItemIndex % 2 == 0 ? GetAlternateItemTemplate(sender) : sender.ItemTemplate;
114+
args.ItemContainer.ContentTemplate = template;
158115
}
159116

160117
private static void ColorItemsVectorChanged(IObservableVector<object> sender, IVectorChangedEventArgs args)
161118
{
162-
// If the index is at the end we can ignore
119+
// If the index is at the end, no other items were affected
120+
// and there's no action to take
163121
if (args.Index == (sender.Count - 1))
164-
{
165122
return;
166-
}
167123

168-
// Only need to handle Inserted and Removed because we'll handle everything else in the
169-
// ColorContainerContentChanging method
170-
if ((args.CollectionChange == CollectionChange.ItemInserted) || (args.CollectionChange == CollectionChange.ItemRemoved))
124+
// This function is for updating indirectly affected items
125+
// Therefore we only need to handle items inserted and removed where every
126+
// item beneath would potentially change if they are even or odd.
127+
if (args.CollectionChange is not (CollectionChange.ItemInserted or CollectionChange.ItemRemoved))
128+
return;
129+
130+
// Attempt to get the list view for the affected items
131+
_trackedListViews.TryGetValue(sender, out ListViewBase? listViewBase);
132+
if (listViewBase is null)
133+
return;
134+
135+
int index = (int)args.Index;
136+
for (int i = index; i < sender.Count; i++)
171137
{
172-
_itemsForList.TryGetValue(sender, out ListViewBase? listViewBase);
173-
if (listViewBase == null)
174-
{
175-
return;
176-
}
138+
// Get item container or element at index
139+
var itemContainer = listViewBase.ContainerFromIndex(i) as Control;
140+
itemContainer ??= listViewBase.Items[i] as Control;
177141

178-
int index = (int)args.Index;
179-
for (int i = index; i < sender.Count; i++)
142+
if (itemContainer is not null)
180143
{
181-
var itemContainer = listViewBase.ContainerFromIndex(i) as Control;
182-
if (itemContainer != null)
183-
{
184-
SetItemContainerBackground(listViewBase, itemContainer, i);
185-
}
144+
SetRowBackground(listViewBase, itemContainer, i);
186145
}
187146
}
188147
}
189148

190-
private static void SetItemContainerBackground(ListViewBase sender, Control itemContainer, int itemIndex)
149+
private static void SetRowBackground(ListViewBase sender, Control itemContainer, int itemIndex)
191150
{
192-
if (itemIndex % 2 == 0)
151+
var brush = itemIndex % 2 == 0 ? GetAlternateColor(sender) : null;
152+
var rootBorder = itemContainer.FindDescendant<Border>();
153+
154+
itemContainer.Background = brush;
155+
if (rootBorder is not null)
193156
{
194-
itemContainer.Background = GetAlternateColor(sender);
195-
var rootBorder = itemContainer.FindDescendant<Border>();
196-
if (rootBorder != null)
197-
{
198-
rootBorder.Background = GetAlternateColor(sender);
199-
}
200-
}
201-
else
202-
{
203-
itemContainer.Background = null;
204-
var rootBorder = itemContainer.FindDescendant<Border>();
205-
if (rootBorder != null)
206-
{
207-
rootBorder.Background = null;
208-
}
157+
rootBorder.Background = brush;
209158
}
210159
}
160+
161+
private static void OnListViewBaseUnloaded_AltRow(object sender, RoutedEventArgs e)
162+
{
163+
if (sender is not ListViewBase listViewBase)
164+
return;
165+
166+
// Untrack the list view
167+
_trackedListViews.Remove(listViewBase.Items);
168+
169+
// Unsubscribe from events
170+
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
171+
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
172+
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
173+
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;
174+
}
211175
}

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
}

0 commit comments

Comments
 (0)