Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NthChild and NthLastChild selectors support #6381

Merged
merged 6 commits into from
Oct 22, 2021
Merged
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
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>
maxkatz6 marked this conversation as resolved.
Show resolved Hide resolved
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>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will suggest just writing Items are highlighted with nth-child(5n+3) and nth-last-child(5n+4) rules.

</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;
maxkatz6 marked this conversation as resolved.
Show resolved Hide resolved
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