A WPF StackPanel-Surrogate with Shared-sizing Scope Ability





5.00/5 (5 votes)
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.
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:
/// <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