@@ -10,202 +10,166 @@ namespace CommunityToolkit.WinUI;
1010/// </summary>
1111public 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}
0 commit comments