Click here to Skip to main content
15,886,362 members
Articles / Desktop Programming / WPF
Tip/Trick

A WPF StackPanel-Surrogate with Shared-sizing Scope Ability

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
14 Sep 2014CPOL1 min read 11.2K   93   7  
Here is a simple trick for simulating the shared-sizing feature of the WPF Grid even in a StackPanel fashion.

Introduction

Basically, you can have several panels, each one in a separate visual fragment, and “synchronize” their children height (or width, when horizontally-oriented).
A short video explains better than thousands of words.

Background

This article explains pretty well the feature available for the Grid control. The goal is having a StackPanel-like layout control, which offers similar "synchronization" features.

Image 1

Using the Code

The solution is pretty simple. Since the Grid already offers such a feature, the trick is leveraging it instead a “real” StackPanel. Otherwise, the mechanism for managing the shared-size scopes is rather complex. As for “complex”, I mean that you should keep all the scrolling and virtualization features which is part of a StackPanel, and that’s rather complex.

The resulting StackPanel-surrogate code is very simple:

C#
/// <summary>
/// Represent a StackPanel surrogate whose children width/height can be
/// shared with other homogeneous panel's children
/// </summary>
public class StackPanel3S
    : Grid
{
    /// <summary>
    /// Gets or sets a value that identifies the panel as a member
    /// of a defined group that shares sizing properties.
    /// </summary>
    public string SharedSizeGroup { get; set; }

    #region DP Orientation

    /// <summary>
    /// Identifies the StackPanelEx.Orientation dependency property.
    /// </summary>
    public static readonly DependencyProperty OrientationProperty = DependencyProperty.Register(
        "Orientation",
        typeof(Orientation),
        typeof(StackPanel3S),
        new FrameworkPropertyMetadata(
            Orientation.Vertical,
            FrameworkPropertyMetadataOptions.AffectsMeasure,
            (obj, args) =>
            {
                var ctl = (StackPanel3S)obj;
                ctl.OrientationChanged(args);
            }));

    /// <summary>
    /// Gets or sets a value that indicates the dimension by which child elements are stacked.
    /// </summary>
    public Orientation Orientation
    {
        get { return (Orientation)GetValue(OrientationProperty); }
        set { SetValue(OrientationProperty, value); }
    }

    #endregion

    private void OrientationChanged(
        DependencyPropertyChangedEventArgs args
        )
    {
        //flush any current row/column definition
        this.RowDefinitions.Clear();
        this.ColumnDefinitions.Clear();
    }

    protected override Size MeasureOverride(Size constraint)
    {
        //retrieve the number of children
        int count = this.InternalChildren.Count;

        if (this.Orientation == System.Windows.Controls.Orientation.Vertical)
        {
            //add the missing row-defintions
            for (int i = this.RowDefinitions.Count; i < count; i++)
            {
                this.RowDefinitions.Add(
                    new RowDefinition()
                    {
                        Height = GridLength.Auto,
                        SharedSizeGroup = this.SharedSizeGroup + "__R" + i
                    });
            }

            //remove the unnecessary row-definitions
            for (int i = this.RowDefinitions.Count - 1; i >= count; i--)
            {
                this.RowDefinitions.RemoveAt(i);
            }

            //passing a progressive index to each child
            for (int i = 0; i < count; i++)
            {
                UIElement child;
                if ((child = this.InternalChildren[i]) != null)
                {
                    Grid.SetRow(child, i);
                }
            }
        }
        else
        {
            //add the missing column-defintions
            for (int i = this.ColumnDefinitions.Count; i < count; i++)
            {
                this.ColumnDefinitions.Add(
                    new ColumnDefinition()
                    {
                        Width = GridLength.Auto,
                        SharedSizeGroup = this.SharedSizeGroup + "__C" + i
                    });
            }

            //remove the unnecessary column-definitions
            for (int i = this.ColumnDefinitions.Count - 1; i >= count; i--)
            {
                this.ColumnDefinitions.RemoveAt(i);
            }

            //passing a progressive index to each child
            for (int i = 0; i < count; i++)
            {
                UIElement child;
                if ((child = this.InternalChildren[i]) != null)
                {
                    Grid.SetColumn(child, i);
                }
            }
        }

        //yield the default measuring pass
        return base.MeasureOverride(constraint);
    }
}

In order to test (and demonstrate) the functionality, the code comes with a small application, which is the one seen in the video.

The app shows three ways to use the StackPanel3S, each one targets a different way to host several children elements:

  • Direct declaration from within the XAML document (leftmost column)
  • Direct declaration from the underlying C# code (center column)
  • Indirect creation via MVVM (rightmost column)

Above each column, there are three sliders for rotating the shapes hosted as "children elements". The rotation angle leads a rectangle to take more or less space (height, in this case), thus the stacked elements are arranged differently.

The left-top checkbox allows the user to enable/disable the size sharing. When the function is disabled, each column arranges their children independently. However, when is enabled, the actual height of every "row" is dependent to all set.

History

  • Initial release

License

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


Written By
Software Developer (Senior) CET Electronics
Italy Italy
Played with transistors and ICs before being ten. First approaches to programming (PET Commodore) in the early '80.
Then AppleSoft, TurboPascal, Assembler and VisualBasic.
Currently employed at CET Electronics as lead software developer, involved in creation of industrial control systems.
Loving graphics technologies, I had some great time with SVG.
Since 2006 my primary language is C#, where I am focusing on WPF.

Comments and Discussions

 
-- There are no messages in this forum --