Styling first and last items of a StackPanel or Grid in WPF

Here’s a good StackOverflow question: Xamarin.Forms: Equivalent to CSS :last-of-type selector.

He wanted a Style Trigger that applies on the first or last item of a StackPanel. You can’t do that out of the box, but you can write a behavior in five or ten minutes that decorates the control’s children with their relative positions via an attached property. Panel is the base class for StackPanel, Grid, and UniformGrid, so Panel is our target for the behavior.

This is WPF, not Xamarin, but I wrote it in the hope that the guy asking the question would be able to convert it to Xamarin without any real trouble. That turned out to be true, and he found that it worked as intended. His Xamarin version is at the link.

using System;
using System.Windows;
using System.Windows.Controls;
namespace HollowEarth.AttachedProperties
{
    public static class PanelBehaviors
    {
        public static void UpdateChildFirstLastProperties(Panel panel)
        {
            for (int i = 0; i < panel.Children.Count; ++i)
            {
                var child = panel.Children[i];
                SetIsFirstChild(child, i == 0);
                SetIsLastChild(child, i == panel.Children.Count - 1);
            }
        }
        #region PanelExtensions.IdentifyFirstAndLastChild Attached Property
        public static bool GetIdentifyFirstAndLastChild(Panel panel)
        {
            return (bool)panel.GetValue(IdentifyFirstAndLastChildProperty);
        }
        public static void SetIdentifyFirstAndLastChild(Panel panel, bool value)
        {
            panel.SetValue(IdentifyFirstAndLastChildProperty, value);
        }
        /// <summary>
        /// Behavior which causes the Panel to identify its first and last children with attached properties. 
        /// </summary>
        public static readonly DependencyProperty IdentifyFirstAndLastChildProperty =
            DependencyProperty.RegisterAttached("IdentifyFirstAndLastChild", typeof(bool), typeof(PanelBehaviors),
                new PropertyMetadata(false, IdentifyFirstAndLastChild_PropertyChanged));
        private static void IdentifyFirstAndLastChild_PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            Panel panel = (Panel)d;
            ((Panel)d).LayoutUpdated += (s, e2) => UpdateChildFirstLastProperties(panel);
        }
        #endregion PanelExtensions.IdentifyFirstAndLastChild Attached Property
        #region PanelExtensions.IsFirstChild Attached Property
        public static bool GetIsFirstChild(UIElement obj)
        {
            return (bool)obj.GetValue(IsFirstChildProperty);
        }
        public static void SetIsFirstChild(UIElement obj, bool value)
        {
            obj.SetValue(IsFirstChildProperty, value);
        }
        /// <summary>
        /// True if UIElement is first child of a Panel
        /// </summary>
        public static readonly DependencyProperty IsFirstChildProperty =
            DependencyProperty.RegisterAttached("IsFirstChild", typeof(bool), typeof(PanelBehaviors),
                new PropertyMetadata(false));
        #endregion PanelExtensions.IsFirstChild Attached Property
        #region PanelExtensions.IsLastChild Attached Property
        public static bool GetIsLastChild(UIElement obj)
        {
            return (bool)obj.GetValue(IsLastChildProperty);
        }
        public static void SetIsLastChild(UIElement obj, bool value)
        {
            obj.SetValue(IsLastChildProperty, value);
        }
        /// <summary>
        /// True if UIElement is last child of a Panel
        /// </summary>
        public static readonly DependencyProperty IsLastChildProperty =
            DependencyProperty.RegisterAttached("IsLastChild", typeof(bool), typeof(PanelBehaviors),
                new PropertyMetadata(false));
        #endregion PanelExtensions.IsLastChild Attached Property
    }
}

And here’s a usage example in XAML:

<StackPanel 
    xmlns:heap="clr-namespace:HollowEarth.AttachedProperties"
    heap:PanelBehaviors.IdentifyFirstAndLastChild="True"
    HorizontalAlignment="Left"
    Orientation="Vertical"
    >
    <StackPanel.Resources>
        <Style TargetType="Label">
            <Setter Property="Content" Value="Blah blah" />
            <Setter Property="Background" Value="SlateGray" />
            <Setter Property="Margin" Value="4" />
            <Style.Triggers>
                <Trigger Property="heap:PanelBehaviors.IsFirstChild" Value="True">
                    <Setter Property="Background" Value="DeepSkyBlue" />
                    <Setter Property="Content" Value="First Child" />
                </Trigger>
                <Trigger Property="heap:PanelBehaviors.IsLastChild" Value="True">
                    <Setter Property="Background" Value="SeaGreen" />
                    <Setter Property="Content" Value="Last Child" />
                </Trigger>
            </Style.Triggers>
        </Style>
    </StackPanel.Resources>
    <Label />
    <Label />
    <Label />
    <Label />
    </StackPanel>

Here’s what makes XAML cool: You can apply this to an ItemsControl, or any ItemsControl descendant such as a ListBox or TreeView/TreeViewItem, by applying it to the ItemsPanel:

<ListBox.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel heap:PanelBehaviors.IdentifyFirstAndLastChild="True"
                    Orientation="Vertical" />
    </ItemsPanelTemplate>
</ListBox.ItemsPanel>
Advertisements