Click here to Skip to main content
13,148,427 members (66,946 online)
Click here to Skip to main content
Add your own
alternative version

Stats

10.2K views
6 bookmarked
Posted 15 Apr 2016

Space Children In A Panel

, 28 Feb 2017
Rate this:
Please Sign up or sign in to vote.
How to apply spacing to all children in a panel

Introduction

How to apply spacing to all children in a panel.

Preview

Using the Code

The previous version of this article defined a control (called Spacer) that inherited StackPanel. For convenience, I decided to translate this logic to attached properties so that any Panel can space its children. Consider the previous usage:

<Controls:Spacer Orientation="Horizontal" CellPadding="15,0">
    <!-- child elements -->
</Controls:Spacer>

Using attached properties, you can now simply do this:

<StackPanel Extensions:PanelExtensions.Spacing="15,0">
    <!-- child elements -->
</StackPanel>

This...

<Grid Extensions:PanelExtensions.Spacing="15,0">
    <!-- child elements -->
</Grid>

... and any other control that inherits Panel. For even more convenience, I have defined additional attached properties for horizontally and vertically aligning children as well as skipping the first or last element when applying spacing.

Why Do This

Compare this to:

<StackPanel>
    <!-- lots of child elements with same margin (yuck!) -->
</StackPanel>

Of course, you could always do this:

<StackPanel>
    <StackPanel.Resources>
        <Style TargetType="Button">
            <Setter Property="Margin" Value="15,0"/>
        </Style>
    </StackPanel.Resources>
</StackPanel>

But the question is, do you really want to?

PanelExtensions

public static class PanelExtensions
{
    #region HorizontalContentAlignment

    public static readonly DependencyProperty HorizontalContentAlignmentProperty = 
                           DependencyProperty.RegisterAttached("HorizontalContentAlignment", 
                           typeof(HorizontalAlignment), typeof(PanelExtensions), 
                           new FrameworkPropertyMetadata(HorizontalAlignment.Left, 
                           OnHorizontalContentAlignmentChanged));
    public static void SetHorizontalContentAlignment(Panel d, HorizontalAlignment value)
    {
        d.SetValue(HorizontalContentAlignmentProperty, value);
    }
    public static HorizontalAlignment GetHorizontalContentAlignment(Panel d)
    {
        return (HorizontalAlignment)d.GetValue(HorizontalContentAlignmentProperty);
    }
    static void OnHorizontalContentAlignmentChanged
                (object sender, DependencyPropertyChangedEventArgs e)
    {
        var Panel = sender as Panel;

        Panel.SizeChanged -= OnHorizontalContentAlignmentUpdated;
        Panel.SizeChanged += OnHorizontalContentAlignmentUpdated;

        OnHorizontalContentAlignmentUpdated(Panel, null);
    }
    static void OnHorizontalContentAlignmentUpdated(object sender, SizeChangedEventArgs e)
    {
        var p = sender as Panel;
        var a = GetHorizontalContentAlignment(p);

        for (int i = 0, Count = p.Children.Count; i < Count; i++)
            p.Children[i].As<FrameworkElement>().HorizontalAlignment = a;
    }

    #endregion

    #region Spacing

    public static readonly DependencyProperty SpacingProperty = 
       DependencyProperty.RegisterAttached("Spacing", typeof(Thickness), 
       typeof(PanelExtensions), new FrameworkPropertyMetadata(default(Thickness), OnSpacingChanged));
    public static void SetSpacing(Panel d, Thickness value)
    {
        d.SetValue(SpacingProperty, value);
    }
    public static Thickness GetSpacing(Panel d)
    {
        return (Thickness)d.GetValue(SpacingProperty);
    }
    static void OnSpacingChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var Panel = sender as Panel;

        Panel.SizeChanged -= OnSpacingUpdated;
        Panel.SizeChanged += OnSpacingUpdated;

        OnSpacingUpdated(Panel, null);
    }
    static void OnSpacingUpdated(object sender, SizeChangedEventArgs e)
    {
        var p = sender as Panel;
        var s = GetSpacing(p);

        var tf = GetTrimFirst(p);
        var tl = GetTrimLast(p);

        for (int i = 0, Count = p.Children.Count; i < Count; i++)
        {
            var Element = p.Children[i] as FrameworkElement;
            if ((i == 0 && tf) || (i == (Count - 1) && tl))
            {
                Element.Margin = new Thickness(0);
                continue;
            }
            Element.Margin = s;
        }
    }

    #endregion

    #region TrimFirst

    public static readonly DependencyProperty TrimFirstProperty = 
          DependencyProperty.RegisterAttached("TrimFirst", typeof(bool), typeof(PanelExtensions), 
          new FrameworkPropertyMetadata(false, OnSpacingChanged));
    public static void SetTrimFirst(Panel d, bool value)
    {
        d.SetValue(TrimFirstProperty, value);
    }
    public static bool GetTrimFirst(Panel d)
    {
        return (bool)d.GetValue(TrimFirstProperty);
    }

    #endregion

    #region TrimLast

    public static readonly DependencyProperty TrimLastProperty = 
              DependencyProperty.RegisterAttached("TrimLast", typeof(bool), typeof(PanelExtensions), 
              new FrameworkPropertyMetadata(false, OnSpacingChanged));
    public static void SetTrimLast(Panel d, bool value)
    {
        d.SetValue(TrimLastProperty, value);
    }
    public static bool GetTrimLast(Panel d)
    {
        return (bool)d.GetValue(TrimLastProperty);
    }

    #endregion

    #region VerticalContentAlignment

    public static readonly DependencyProperty VerticalContentAlignmentProperty = 
           DependencyProperty.RegisterAttached("VerticalContentAlignment", typeof(VerticalAlignment), 
           typeof(PanelExtensions), new FrameworkPropertyMetadata(VerticalAlignment.Top, 
           OnVerticalContentAlignmentChanged));
    public static void SetVerticalContentAlignment(Panel d, VerticalAlignment value)
    {
        d.SetValue(VerticalContentAlignmentProperty, value);
    }
    public static VerticalAlignment GetVerticalContentAlignment(Panel d)
    {
        return (VerticalAlignment)d.GetValue(VerticalContentAlignmentProperty);
    }
    static void OnVerticalContentAlignmentChanged(object sender, DependencyPropertyChangedEventArgs e)
    {
        var Panel = sender as Panel;

        Panel.SizeChanged -= OnVerticalContentAlignmentUpdated;
        Panel.SizeChanged += OnVerticalContentAlignmentUpdated;

        OnVerticalContentAlignmentUpdated(Panel, null);
    }
    static void OnVerticalContentAlignmentUpdated(object sender, SizeChangedEventArgs e)
    {
        var p = sender as Panel;
        var a = GetVerticalContentAlignment(p);

        for (int i = 0, Count = p.Children.Count; i < Count; i++)
            p.Children[i].As<FrameworkElement>().VerticalAlignment = a;
    }

    #endregion

Although each attached property is somewhat straightforward, allow me to briefly explain what each does.

HorizontalContentAlignment

Align all children horizontally.

Spacing

Apply outer thickness (margin) to all children.

TrimFirst

If spacing is applied and TrimFirst is true, do not apply margin to first element.

TrimLast

If spacing is applied and TrimLast is true, do not apply margin to last element.

VerticalContentAlignment

Align all children vertically.

Points of Interest

  • SizeChanged event ensures all attached property values update when necessary. Anything else could either mean a performance hit (like LayoutUpdated) or produce an incorrect result (like Loaded; added/removed elements would never be taken into account).
  • TrimFirst and TrimLast is very similar to :first-child and :last-child CSS selectors in that it observes only the first or last element when making a decision. The difference is the former applies only to margin whereas the latter could apply to any rule.

History

  • 30th of April, 2016
    • Added properties TrimFirst and TrimLast. If TrimFirst == true, no margin is applied to the first element. If TrimLast == true, no margin is applied to the last element.
    • Changed class name from ItemSpacer to Spacer and the property CellPadding to Spacing
    • Updated SetPadding method to accommodate TrimFirst and TrimLast behavior
  • 28th of February, 2017
    • Scrapped inherited control in favor of attached properties so logic can apply to all types of Panels.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

James J M
Software Developer Imagin
United States United States
No Biography provided

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionFor those who want VB Pin
Bart_Rennes18-Apr-16 14:10
memberBart_Rennes18-Apr-16 14:10 
QuestionWhy not just use a grid? Pin
John Simmons / outlaw programmer16-Apr-16 4:35
memberJohn Simmons / outlaw programmer16-Apr-16 4:35 
AnswerRe: Why not just use a grid? Pin
Alex Y16-Apr-16 8:20
memberAlex Y16-Apr-16 8:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.170924.2 | Last Updated 28 Feb 2017
Article Copyright 2016 by James J M
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid