Home > OS >  WPF XAML : How to create a layout for multiple rows, each row different column layout
WPF XAML : How to create a layout for multiple rows, each row different column layout

Time:11-19

I'm struggling getting what should be a simple layout that follows the constraints:

1: Controls in a row shouldn't vertically expand to occupy the entire window, but stay the height of the entire row; this is usually remedied by using VerticalAlignment="Top"

2: Each row could have any number of fixed-width items; each item could have a different fixed width

3: Each row could have 1 or more user controls that should horizontally expand to fit the remaining width, even on window resize

4: Each control from #3 could specified to evenly distribute, or n*, the width

5: Each row could have different columnar lines (doesn't overlay a perfect grid)

6: I should be able to have blank lines / spacers between rows

Using Grid doesn't seem to work:

  • I can't predefine each column
  • Width doesn't allow a * specifier at a component level

StackPanel and DockPanel get me somewhat there, but not really

In general I end up with components stacked on top of each other, controls that don't expand to fill remaining space.

enter image description here enter image description here


enter image description here enter image description here

Here's a visual to help. Remember, according to #5, columns won't necessarily line up (this picture only has that as a side-effect from using Excel to mock it) enter image description here

Every "how to" or "example" shows 1 line, not multiple, doesn't address variable column definitions, or skips controls occupying variable widths/expansion. What is it I'm missing?

Thank you for any help or direction.

CodePudding user response:

Since you don't need to match the column widths, instead of one common Grid, in my opinion, you should use a separate one-line Grid for each row.

To simplify the markup, you can create your own custom Grid that adds columns according to the number of children and receives the column width from them too.

Here is an implementation example.
Just don't take it as a complete solution. This is just a demonstration of a possible option.
There may be errors or bugs in this implementation.
I haven't fully tested this code.

using System;
using System.Windows;
using System.Windows.Controls;

namespace Core2022.SO.Adam_L
{
    public partial class RowStackGrid : Grid
    {
        protected override Size ArrangeOverride(Size arrangeSize)
        {
            Dispatcher.BeginInvoke((Action)ReValidate);
            return base.ArrangeOverride(arrangeSize);
        }

        private void ReValidate()
        {
            var columns = ColumnDefinitions;
            int columnsCount = columns.Count;
            var children = Children;
            int childrenCount = children.Count;

            if (RowDefinitions.Count > 1)
            {
                RowDefinitions.Clear();
            }


            if (columnsCount > childrenCount)
            {
                for (int i = columnsCount; i > childrenCount; i--)
                {
                    ColumnDefinitions.RemoveAt(i);
                }
            }
            else if (columnsCount < childrenCount)
            {
                for (int i = childrenCount; i > columnsCount; i--)
                {
                    ColumnDefinitions.Add(new ColumnDefinition());
                }
            }


            for (int i = 0; i < childrenCount; i  )
            {
                GridLength columnWidth = GetColumnWidth(children[i]);
                if (columns[i].Width != columnWidth)
                {
                    columns[i].Width = columnWidth;
                }
                int column = GetColumn(children[i]);
                if (column != i)
                {
                    SetColumn(children[i], i);
                }
                if (children[i] is FrameworkElement fwElement &&
                    fwElement.ReadLocalValue(VerticalAlignmentProperty) == DependencyProperty.UnsetValue)
                {
                    fwElement.VerticalAlignment = VerticalAlignment.Top;
                }
            }
        }
    }
    public partial class RowStackGrid : Grid
    {
        public static GridLength GetColumnWidth(UIElement uIElement)
        {
            return (GridLength)uIElement.GetValue(ColumnWidthProperty);
        }

        public static void SetColumnWidth(UIElement uIElement, GridLength value)
        {
            uIElement.SetValue(ColumnWidthProperty, value);
        }

        // Using a DependencyProperty as the backing store for ColumnWidth.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColumnWidthProperty =
            DependencyProperty.RegisterAttached("ColumnWidth", typeof(GridLength), typeof(RowStackGrid), new PropertyMetadata(new GridLength(0, GridUnitType.Auto)));
    }
}
    <StackPanel>
        <adam_l:RowStackGrid>
            <Button Content="Button" Padding="15" Margin="20"/>
            <TextBox adam_l:RowStackGrid.ColumnWidth="1*" Margin="5"/>
            <TextBox adam_l:RowStackGrid.ColumnWidth="3*" Margin="5"/>
        </adam_l:RowStackGrid>
        <adam_l:RowStackGrid>
            <Button Content="Button" adam_l:RowStackGrid.ColumnWidth="1*"/>
            <TextBox Margin="5" Width="20" Height="40"/>
            <TextBox adam_l:RowStackGrid.ColumnWidth="3*" Margin="5"/>
        </adam_l:RowStackGrid>
    </StackPanel>

enter image description here

  • Related