Skip to content

Commit

Permalink
Merge pull request #6381 from AvaloniaUI/feature/nth-child
Browse files Browse the repository at this point in the history
NthChild and NthLastChild selectors support
  • Loading branch information
jmacato authored and danwalmsley committed Oct 22, 2021
1 parent 0f8b4d4 commit 608cd98
Show file tree
Hide file tree
Showing 21 changed files with 1,542 additions and 21 deletions.
24 changes: 20 additions & 4 deletions samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ItemsRepeaterPage">
<UserControl.Styles>
<Style Selector="ItemsRepeater TextBlock.oddTemplate">
<Setter Property="Background" Value="Yellow" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="ItemsRepeater TextBlock.evenTemplate">
<Setter Property="Background" Value="Wheat" />
<Setter Property="Foreground" Value="Black" />
</Style>
<Style Selector="ItemsRepeater TextBlock:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="ItemsRepeater TextBlock:nth-last-child(5n+4)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</UserControl.Styles>
<UserControl.Resources>
<RecyclePool x:Key="RecyclePool" />
<DataTemplate x:Key="odd">
<TextBlock Background="Yellow"
Foreground="Black"
<TextBlock Classes="oddTemplate"
Height="{Binding Height}"
Text="{Binding Text}"/>
</DataTemplate>
<DataTemplate x:Key="even">
<TextBlock Background="Wheat"
Foreground="Black"
<TextBlock Classes="evenTemplate"
Height="{Binding Height}"
Text="{Binding Text}"/>
</DataTemplate>
Expand Down
11 changes: 11 additions & 0 deletions samples/ControlCatalog/Pages/ListBoxPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ListBoxPage">
<DockPanel>
<DockPanel.Styles>
<Style Selector="ListBox ListBoxItem:nth-child(5n+3)">
<Setter Property="Foreground" Value="Red" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="ListBox ListBoxItem:nth-last-child(5n+4)">
<Setter Property="Foreground" Value="Blue" />
<Setter Property="FontWeight" Value="Bold" />
</Style>
</DockPanel.Styles>
<StackPanel DockPanel.Dock="Top" Margin="4">
<TextBlock Classes="h1">ListBox</TextBlock>
<TextBlock Classes="h2">Hosts a collection of ListBoxItem.</TextBlock>
<TextBlock Classes="h2">Each 5th item is highlighted with nth-child(5n+3) and nth-last-child(5n+4) rules.</TextBlock>
</StackPanel>
<StackPanel DockPanel.Dock="Right" Margin="4">
<CheckBox IsChecked="{Binding Multiple}">Multiple</CheckBox>
Expand Down
43 changes: 42 additions & 1 deletion src/Avalonia.Controls/ItemsControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace Avalonia.Controls
/// Displays a collection of items.
/// </summary>
[PseudoClasses(":empty", ":singleitem")]
public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener
public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionChangedListener, IChildIndexProvider
{
/// <summary>
/// The default value for the <see cref="ItemsPanel"/> property.
Expand Down Expand Up @@ -56,6 +56,7 @@ public class ItemsControl : TemplatedControl, IItemsPresenterHost, ICollectionCh
private IEnumerable _items = new AvaloniaList<object>();
private int _itemCount;
private IItemContainerGenerator _itemContainerGenerator;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;

/// <summary>
/// Initializes static members of the <see cref="ItemsControl"/> class.
Expand Down Expand Up @@ -145,11 +146,28 @@ public IItemsPresenter Presenter
protected set;
}

event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}

/// <inheritdoc/>
void IItemsPresenterHost.RegisterItemsPresenter(IItemsPresenter presenter)
{
if (Presenter is IChildIndexProvider oldInnerProvider)
{
oldInnerProvider.ChildIndexChanged -= PresenterChildIndexChanged;
}

Presenter = presenter;
ItemContainerGenerator.Clear();

if (Presenter is IChildIndexProvider innerProvider)
{
innerProvider.ChildIndexChanged += PresenterChildIndexChanged;
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
}
}

void ICollectionChangedListener.PreChanged(INotifyCollectionChanged sender, NotifyCollectionChangedEventArgs e)
Expand Down Expand Up @@ -506,5 +524,28 @@ protected static IInputElement GetNextControl(

return null;
}

private void PresenterChildIndexChanged(object sender, ChildIndexChangedEventArgs e)
{
_childIndexChanged?.Invoke(this, e);
}

int IChildIndexProvider.GetChildIndex(ILogical child)
{
return Presenter is IChildIndexProvider innerProvider
? innerProvider.GetChildIndex(child) : -1;
}

bool IChildIndexProvider.TryGetTotalCount(out int count)
{
if (Presenter is IChildIndexProvider presenter
&& presenter.TryGetTotalCount(out count))
{
return true;
}

count = ItemCount;
return true;
}
}
}
24 changes: 23 additions & 1 deletion src/Avalonia.Controls/Panel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Styling;

namespace Avalonia.Controls
{
Expand All @@ -14,7 +16,7 @@ namespace Avalonia.Controls
/// Controls can be added to a <see cref="Panel"/> by adding them to its <see cref="Children"/>
/// collection. All children are layed out to fill the panel.
/// </remarks>
public class Panel : Control, IPanel
public class Panel : Control, IPanel, IChildIndexProvider
{
/// <summary>
/// Defines the <see cref="Background"/> property.
Expand All @@ -30,6 +32,8 @@ static Panel()
AffectsRender<Panel>(BackgroundProperty);
}

private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;

/// <summary>
/// Initializes a new instance of the <see cref="Panel"/> class.
/// </summary>
Expand All @@ -53,6 +57,12 @@ public IBrush Background
set { SetValue(BackgroundProperty, value); }
}

event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}

/// <summary>
/// Renders the visual to a <see cref="DrawingContext"/>.
/// </summary>
Expand Down Expand Up @@ -137,6 +147,7 @@ protected virtual void ChildrenChanged(object sender, NotifyCollectionChangedEve
throw new NotSupportedException();
}

_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
InvalidateMeasureOnChildrenChanged();
}

Expand All @@ -160,5 +171,16 @@ private static void AffectsParentMeasureInvalidate<TPanel>(AvaloniaPropertyChang
var panel = control?.VisualParent as TPanel;
panel?.InvalidateMeasure();
}

int IChildIndexProvider.GetChildIndex(ILogical child)
{
return child is IControl control ? Children.IndexOf(control) : -1;
}

public bool TryGetTotalCount(out int count)
{
count = Children.Count;
return true;
}
}
}
41 changes: 40 additions & 1 deletion src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
using Avalonia.Controls.Generators;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.LogicalTree;
using Avalonia.Styling;

namespace Avalonia.Controls.Presenters
{
/// <summary>
/// Base class for controls that present items inside an <see cref="ItemsControl"/>.
/// </summary>
public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedControl
public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedControl, IChildIndexProvider
{
/// <summary>
/// Defines the <see cref="Items"/> property.
Expand All @@ -36,6 +37,7 @@ public abstract class ItemsPresenterBase : Control, IItemsPresenter, ITemplatedC
private IDisposable _itemsSubscription;
private bool _createdPanel;
private IItemContainerGenerator _generator;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;

/// <summary>
/// Initializes static members of the <see cref="ItemsPresenter"/> class.
Expand Down Expand Up @@ -129,6 +131,12 @@ public IPanel Panel

protected bool IsHosted => TemplatedParent is IItemsPresenterHost;

event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}

/// <inheritdoc/>
public override sealed void ApplyTemplate()
{
Expand All @@ -149,6 +157,8 @@ void IItemsPresenter.ItemsChanged(NotifyCollectionChangedEventArgs e)
if (Panel != null)
{
ItemsChanged(e);

_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs());
}
}

Expand All @@ -169,9 +179,21 @@ protected virtual IItemContainerGenerator CreateItemContainerGenerator()
result.ItemTemplate = ItemTemplate;
}

result.Materialized += ContainerActionHandler;
result.Dematerialized += ContainerActionHandler;
result.Recycled += ContainerActionHandler;

return result;
}

private void ContainerActionHandler(object sender, ItemContainerEventArgs e)
{
for (var i = 0; i < e.Containers.Count; i++)
{
_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(e.Containers[i].ContainerControl));
}
}

/// <inheritdoc/>
protected override Size MeasureOverride(Size availableSize)
{
Expand Down Expand Up @@ -248,5 +270,22 @@ private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)
{
(e.NewValue as IItemsPresenterHost)?.RegisterItemsPresenter(this);
}

int IChildIndexProvider.GetChildIndex(ILogical child)
{
if (child is IControl control && ItemContainerGenerator is { } generator)
{
var index = ItemContainerGenerator.IndexFromContainer(control);

return index;
}

return -1;
}

bool IChildIndexProvider.TryGetTotalCount(out int count)
{
return Items.TryGetCountFast(out count);
}
}
}
31 changes: 29 additions & 2 deletions src/Avalonia.Controls/Repeater/ItemsRepeater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.LogicalTree;
using Avalonia.Utilities;
using Avalonia.VisualTree;

Expand All @@ -19,7 +20,7 @@ namespace Avalonia.Controls
/// Represents a data-driven collection control that incorporates a flexible layout system,
/// custom views, and virtualization.
/// </summary>
public class ItemsRepeater : Panel
public class ItemsRepeater : Panel, IChildIndexProvider
{
/// <summary>
/// Defines the <see cref="HorizontalCacheLength"/> property.
Expand Down Expand Up @@ -61,8 +62,9 @@ public class ItemsRepeater : Panel
private readonly ViewportManager _viewportManager;
private IEnumerable _items;
private VirtualizingLayoutContext _layoutContext;
private NotifyCollectionChangedEventArgs _processingItemsSourceChange;
private EventHandler<ChildIndexChangedEventArgs> _childIndexChanged;
private bool _isLayoutInProgress;
private NotifyCollectionChangedEventArgs _processingItemsSourceChange;
private ItemsRepeaterElementPreparedEventArgs _elementPreparedArgs;
private ItemsRepeaterElementClearingEventArgs _elementClearingArgs;
private ItemsRepeaterElementIndexChangedEventArgs _elementIndexChangedArgs;
Expand Down Expand Up @@ -163,6 +165,25 @@ private LayoutContext LayoutContext
}
}

event EventHandler<ChildIndexChangedEventArgs> IChildIndexProvider.ChildIndexChanged
{
add => _childIndexChanged += value;
remove => _childIndexChanged -= value;
}

int IChildIndexProvider.GetChildIndex(ILogical child)
{
return child is IControl control
? GetElementIndex(control)
: -1;
}

bool IChildIndexProvider.TryGetTotalCount(out int count)
{
count = ItemsSourceView.Count;
return true;
}

/// <summary>
/// Occurs each time an element is cleared and made available to be re-used.
/// </summary>
Expand Down Expand Up @@ -545,6 +566,8 @@ internal void OnElementPrepared(IControl element, VirtualizationInfo virtInfo)

ElementPrepared(this, _elementPreparedArgs);
}

_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
}

internal void OnElementClearing(IControl element)
Expand All @@ -562,6 +585,8 @@ internal void OnElementClearing(IControl element)

ElementClearing(this, _elementClearingArgs);
}

_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
}

internal void OnElementIndexChanged(IControl element, int oldIndex, int newIndex)
Expand All @@ -579,6 +604,8 @@ internal void OnElementIndexChanged(IControl element, int oldIndex, int newIndex

ElementIndexChanged(this, _elementIndexChangedArgs);
}

_childIndexChanged?.Invoke(this, new ChildIndexChangedEventArgs(element));
}

private void OnDataSourcePropertyChanged(ItemsSourceView oldValue, ItemsSourceView newValue)
Expand Down
Loading

0 comments on commit 608cd98

Please sign in to comment.