Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,202 +10,166 @@ namespace CommunityToolkit.WinUI;
/// </summary>
public static partial class ListViewExtensions
{
private static Dictionary<IObservableVector<object>, ListViewBase> _itemsForList = new Dictionary<IObservableVector<object>, ListViewBase>();
private static readonly Dictionary<IObservableVector<object>, ListViewBase> _trackedListViews = [];

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

/// <summary>
/// Attached <see cref="DependencyProperty"/> for binding a <see cref="DataTemplate"/> as an alternate template to a <see cref="ListViewBase"/>
/// </summary>
public static readonly DependencyProperty AlternateItemTemplateProperty = DependencyProperty.RegisterAttached("AlternateItemTemplate", typeof(DataTemplate), typeof(ListViewExtensions), new PropertyMetadata(null, OnAlternateItemTemplatePropertyChanged));
public static readonly DependencyProperty AlternateItemTemplateProperty =
DependencyProperty.RegisterAttached("AlternateItemTemplate", typeof(DataTemplate), typeof(ListViewExtensions),
new PropertyMetadata(null, OnAlternateItemTemplatePropertyChanged));

/// <summary>
/// Gets the alternate <see cref="Brush"/> associated with the specified <see cref="ListViewBase"/>
/// </summary>
/// <param name="obj">The <see cref="ListViewBase"/> to get the associated <see cref="Brush"/> from</param>
/// <returns>The <see cref="Brush"/> associated with the <see cref="ListViewBase"/></returns>
public static Brush GetAlternateColor(ListViewBase obj)
{
return (Brush)obj.GetValue(AlternateColorProperty);
}
public static Brush? GetAlternateColor(ListViewBase obj) => (Brush?)obj.GetValue(AlternateColorProperty);

/// <summary>
/// Sets the alternate <see cref="Brush"/> associated with the specified <see cref="DependencyObject"/>
/// </summary>
/// <param name="obj">The <see cref="ListViewBase"/> to associate the <see cref="Brush"/> with</param>
/// <param name="value">The <see cref="Brush"/> for binding to the <see cref="ListViewBase"/></param>
public static void SetAlternateColor(ListViewBase obj, Brush value)
{
obj.SetValue(AlternateColorProperty, value);
}
public static void SetAlternateColor(ListViewBase obj, Brush? value) => obj.SetValue(AlternateColorProperty, value);

/// <summary>
/// Gets the <see cref="DataTemplate"/> associated with the specified <see cref="ListViewBase"/>
/// </summary>
/// <param name="obj">The <see cref="ListViewBase"/> to get the associated <see cref="DataTemplate"/> from</param>
/// <returns>The <see cref="DataTemplate"/> associated with the <see cref="ListViewBase"/></returns>
public static DataTemplate GetAlternateItemTemplate(ListViewBase obj)
{
return (DataTemplate)obj.GetValue(AlternateItemTemplateProperty);
}
public static DataTemplate? GetAlternateItemTemplate(ListViewBase obj) => (DataTemplate?)obj.GetValue(AlternateItemTemplateProperty);

/// <summary>
/// Sets the <see cref="DataTemplate"/> associated with the specified <see cref="ListViewBase"/>
/// </summary>
/// <param name="obj">The <see cref="ListViewBase"/> to associate the <see cref="DataTemplate"/> with</param>
/// <param name="value">The <see cref="DataTemplate"/> for binding to the <see cref="ListViewBase"/></param>
public static void SetAlternateItemTemplate(ListViewBase obj, DataTemplate value)
{
obj.SetValue(AlternateItemTemplateProperty, value);
}
public static void SetAlternateItemTemplate(ListViewBase obj, DataTemplate? value) => obj.SetValue(AlternateItemTemplateProperty, value);

private static void OnAlternateColorPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
if (sender is ListViewBase listViewBase)
{
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
listViewBase.Unloaded -= OnListViewBaseUnloaded;

_itemsForList[listViewBase.Items] = listViewBase;
if (AlternateColorProperty != null)
{
listViewBase.ContainerContentChanging += ColorContainerContentChanging;
listViewBase.Items.VectorChanged += ColorItemsVectorChanged;
listViewBase.Unloaded += OnListViewBaseUnloaded;
}
}
}
if (sender is not ListViewBase listViewBase)
return;

private static void ColorContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
var itemContainer = args.ItemContainer as Control;
SetItemContainerBackground(sender, itemContainer, args.ItemIndex);
}
// Cleanup existing subscriptions
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;

private static void OnAlternateItemTemplatePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
if (sender is ListViewBase listViewBase)
// Resubscribe to events as necessary
if (GetAlternateColor(listViewBase) is not null)
{
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
listViewBase.Unloaded -= OnListViewBaseUnloaded;

if (AlternateItemTemplateProperty != null)
{
listViewBase.ContainerContentChanging += ItemTemplateContainerContentChanging;
listViewBase.Unloaded += OnListViewBaseUnloaded;
}
}
}
listViewBase.ContainerContentChanging += ColorContainerContentChanging;
listViewBase.Items.VectorChanged += ColorItemsVectorChanged;
listViewBase.Unloaded += OnListViewBaseUnloaded_AltRow;

private static void ItemTemplateContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (args.ItemIndex % 2 == 0)
{
args.ItemContainer.ContentTemplate = GetAlternateItemTemplate(sender);
_trackedListViews[listViewBase.Items] = listViewBase;
}
else
{
args.ItemContainer.ContentTemplate = sender.ItemTemplate;
_trackedListViews.Remove(listViewBase.Items);
}
}

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

if (ItemContainerStretchDirectionProperty != null)
{
listViewBase.ContainerContentChanging += ItemContainerStretchDirectionChanging;
listViewBase.Unloaded += OnListViewBaseUnloaded;
}
// Update the row background if the item was found
if (control is not null)
{
SetRowBackground(sender, control, args.ItemIndex);
}
}

private static void ItemContainerStretchDirectionChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
private static void OnAlternateItemTemplatePropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var stretchDirection = GetItemContainerStretchDirection(sender);
if (sender is not ListViewBase listViewBase)
return;

if (stretchDirection == ItemContainerStretchDirection.Vertical || stretchDirection == ItemContainerStretchDirection.Both)
{
args.ItemContainer.VerticalContentAlignment = VerticalAlignment.Stretch;
}
// Cleanup existing subscriptions
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;

if (stretchDirection == ItemContainerStretchDirection.Horizontal || stretchDirection == ItemContainerStretchDirection.Both)
// Resubscribe to events as necessary
if (GetAlternateItemTemplate(listViewBase) is not null)
{
args.ItemContainer.HorizontalContentAlignment = HorizontalAlignment.Stretch;
listViewBase.ContainerContentChanging += ItemTemplateContainerContentChanging;
listViewBase.Unloaded += OnListViewBaseUnloaded_AltRow;
}
}

private static void OnListViewBaseUnloaded(object sender, RoutedEventArgs e)
private static void ItemTemplateContainerContentChanging(ListViewBase sender, ContainerContentChangingEventArgs args)
{
if (sender is ListViewBase listViewBase)
{
_itemsForList.Remove(listViewBase.Items);

listViewBase.ContainerContentChanging -= ItemContainerStretchDirectionChanging;
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
listViewBase.Unloaded -= OnListViewBaseUnloaded;
}
var template = args.ItemIndex % 2 == 0 ? GetAlternateItemTemplate(sender) : sender.ItemTemplate;
args.ItemContainer.ContentTemplate = template;
}

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

// Only need to handle Inserted and Removed because we'll handle everything else in the
// ColorContainerContentChanging method
if ((args.CollectionChange == CollectionChange.ItemInserted) || (args.CollectionChange == CollectionChange.ItemRemoved))
// This function is for updating indirectly affected items
// Therefore we only need to handle items inserted and removed where every
// item beneath would potentially change if they are even or odd.
if (args.CollectionChange is not (CollectionChange.ItemInserted or CollectionChange.ItemRemoved))
return;

// Attempt to get the list view for the affected items
_trackedListViews.TryGetValue(sender, out ListViewBase? listViewBase);
if (listViewBase is null)
return;

int index = (int)args.Index;
for (int i = index; i < sender.Count; i++)
{
_itemsForList.TryGetValue(sender, out ListViewBase? listViewBase);
if (listViewBase == null)
{
return;
}
// Get item container or element at index
var itemContainer = listViewBase.ContainerFromIndex(i) as Control;
itemContainer ??= listViewBase.Items[i] as Control;

int index = (int)args.Index;
for (int i = index; i < sender.Count; i++)
if (itemContainer is not null)
{
var itemContainer = listViewBase.ContainerFromIndex(i) as Control;
if (itemContainer != null)
{
SetItemContainerBackground(listViewBase, itemContainer, i);
}
SetRowBackground(listViewBase, itemContainer, i);
}
}
}

private static void SetItemContainerBackground(ListViewBase sender, Control itemContainer, int itemIndex)
private static void SetRowBackground(ListViewBase sender, Control itemContainer, int itemIndex)
{
if (itemIndex % 2 == 0)
var brush = itemIndex % 2 == 0 ? GetAlternateColor(sender) : null;
var rootBorder = itemContainer.FindDescendant<Border>();

itemContainer.Background = brush;
if (rootBorder is not null)
{
itemContainer.Background = GetAlternateColor(sender);
var rootBorder = itemContainer.FindDescendant<Border>();
if (rootBorder != null)
{
rootBorder.Background = GetAlternateColor(sender);
}
}
else
{
itemContainer.Background = null;
var rootBorder = itemContainer.FindDescendant<Border>();
if (rootBorder != null)
{
rootBorder.Background = null;
}
rootBorder.Background = brush;
}
}

private static void OnListViewBaseUnloaded_AltRow(object sender, RoutedEventArgs e)
{
if (sender is not ListViewBase listViewBase)
return;

// Untrack the list view
_trackedListViews.Remove(listViewBase.Items);

// Unsubscribe from events
listViewBase.ContainerContentChanging -= ItemTemplateContainerContentChanging;
listViewBase.ContainerContentChanging -= ColorContainerContentChanging;
listViewBase.Items.VectorChanged -= ColorItemsVectorChanged;
listViewBase.Unloaded -= OnListViewBaseUnloaded_AltRow;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,66 +12,54 @@ namespace CommunityToolkit.WinUI;
public static partial class ListViewExtensions
{
/// <summary>
/// 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.
/// 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.
/// </summary>
public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ListViewExtensions), new PropertyMetadata(null, OnCommandPropertyChanged));
public static readonly DependencyProperty CommandProperty =
DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(ListViewExtensions),
new PropertyMetadata(null, OnCommandPropertyChanged));

/// <summary>
/// Gets the <see cref="ICommand"/> associated with the specified <see cref="ListViewBase"/>
/// </summary>
/// <param name="obj">The <see cref="ListViewBase"/> to get the associated <see cref="ICommand"/> from</param>
/// <returns>The <see cref="ICommand"/> associated with the <see cref="ListViewBase"/></returns>
public static ICommand GetCommand(ListViewBase obj)
{
return (ICommand)obj.GetValue(CommandProperty);
}
public static ICommand GetCommand(ListViewBase obj) => (ICommand)obj.GetValue(CommandProperty);

/// <summary>
/// Sets the <see cref="ICommand"/> associated with the specified <see cref="ListViewBase"/>
/// </summary>
/// <param name="obj">The <see cref="ListViewBase"/> to associate the <see cref="ICommand"/> with</param>
/// <param name="value">The <see cref="ICommand"/> for binding to the <see cref="ListViewBase"/></param>
public static void SetCommand(ListViewBase obj, ICommand value)
{
obj.SetValue(CommandProperty, value);
}
public static void SetCommand(ListViewBase obj, ICommand value) => obj.SetValue(CommandProperty, value);

private static void OnCommandPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
var listViewBase = sender as ListViewBase;

if (listViewBase == null)
{
if (sender is not ListViewBase listViewBase)
return;
}

var oldCommand = args.OldValue as ICommand;
if (oldCommand != null)
if (oldCommand is not null)
{
listViewBase.ItemClick -= OnListViewBaseItemClick;
}

var newCommand = args.NewValue as ICommand;
if (newCommand != null)
if (newCommand is not null)
{
listViewBase.ItemClick += OnListViewBaseItemClick;
}
}

private static void OnListViewBaseItemClick(object sender, ItemClickEventArgs e)
{
if (sender is ListViewBase listViewBase)
{
var command = GetCommand(listViewBase);
if (listViewBase == null || command == null)
{
return;
}
if (sender is not ListViewBase listViewBase)
return;

if (command.CanExecute(e.ClickedItem))
{
command.Execute(e.ClickedItem);
}
}
var command = GetCommand(listViewBase);
if (command is null)
return;

if (command.CanExecute(e.ClickedItem))
command.Execute(e.ClickedItem);
}
}
Loading
Loading