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.
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)
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>