Here is a simple trick for simulating the shared-sizing feature of the WPF Grid even in a StackPanel fashion.
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.
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); } //assing 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); } //assing 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); } }
Enjoy!