"There are two ways of constructing a software design. One way is to make it so simple that there are obviously no deficiencies and the other is to make it so complicated that there are no obvious deficiencies". C.A.R. Hoare
One of the things that I don't like about the WPF Grid is the fact that if you insert new rows into a grid layout control you have to update the Grid.Row attached property to ensure that the rows render in proper order.
For example, if you supplied xaml that showed contact information and looked something like this:
here is the xaml representation:
<Border BorderBrush="LightGray" BorderThickness="1" Padding="10" CornerRadius="10">
<Grid>
<Grid.Resources>
<Thickness x:Key="Rowspacing">7</Thickness>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Content="First Name:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right" />
<Label Grid.Row="1" Content="Last Name:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="2" Content="Street Address:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="3" Content="City:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="4" Content="State:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="5" Content="Zip:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="6" Content="Company:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Grid.Row="0" Text="Shannon" MinWidth="300" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="1" Text="Braun" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="2" Text="111 Main Street" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="3" Text="Minneapolis" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="4" Text="Mn" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="5" Text="55414" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="6" Text="Sysknowlogy" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
</Grid>
</Border>
If I wanted to insert a row between Grid.Row="0" (First Name) and Grid.Row="1" (Last Name) I would have to also update the Grid.Row attached property for all of the rows after the insert.
One of the ways to get around this is to bind the Grid.Row attached property on the Labels and Textboxes to the Index of the RowDefinition. When a RowDefinition is inserted or moved there is no need to update the indexes. Unfortunately the RowDefinition doesn't expose a Index, so using the Binding facilities of WPF won't work here. Instead we will leverage a MarkupExtension to translate the the reference to the RowDefinition name to the actual index of that RowDefinition, insulating us from any row inserts or changes in order.
The first step is to add a name to each of the RowDefinitions so that we can reference them.
<RowDefinition x:Name="Row_FirstName" Height="Auto" />
<RowDefinition x:Name="Row_LastName" Height="Auto" />
<RowDefinition x:Name="Row_Street" Height="Auto" />
<RowDefinition x:Name="Row_City" Height="Auto" />
<RowDefinition x:Name="Row_State" Height="Auto" />
<RowDefinition x:Name="Row_Zip" Height="Auto" />
<RowDefinition x:Name="Row_Company" Height="Auto" />
Write the MarkUpExtension that will support the translation from element name to row index... the result looks like the following:
[MarkupExtensionReturnType(typeof(int))]
public class MapTo : MarkupExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="MapTo"/> class.
/// </summary>
public MapTo()
}
/// <param name="rowName">Name of the row.</param>
public MapTo(string rowName)
_name = rowName;
private string _name;
/// Gets or sets the name of the element.
/// <value>The name of the element.</value>
public string ElementName
get { return _name; }
set { _name = value; }
/// When implemented in a derived class, returns an object that is set as the value of the target property for this markup extension.
/// <param name="serviceProvider">Object that can provide services for the markup extension.</param>
/// <returns>
/// The object value to set on the property where the extension is applied.
/// </returns>
public override object ProvideValue(IServiceProvider serviceProvider)
IProvideValueTarget ipvt = (IProvideValueTarget)serviceProvider.GetService(typeof(IProvideValueTarget));
int idx = -1;
if (ipvt != null)
FrameworkElement target = ipvt.TargetObject as FrameworkElement;
if (target != null)
Grid grd = target.Parent as Grid;
if (grd != null)
idx = GetRowIndex(grd);
else
throw new NullReferenceException("Grid was not found as the parent of element " + _name);
return idx;
/// Gets the index of the row based on the name of the element.
/// <param name="parent">The parent.</param>
/// <returns></returns>
private int GetRowIndex(Grid parent)
if (parent != null)
RowDefinition rowDefinition = parent.FindName(_name) as RowDefinition;
if (rowDefinition != null)
idx = parent.RowDefinitions.IndexOf(rowDefinition);
throw new NullReferenceException("RowDefinition was not found for name " + _name);
Next we use the MapTo MarkUpExtension in xaml to identify the row index for both the Labels and TextBoxes:
<Label Grid.Row="{local:MapTo Row_FirstName}" Content="First Name:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right" />
<Label Grid.Row="{local:MapTo Row_LastName}" Content="Last Name:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="{local:MapTo Row_Street}" Content="Street Address:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="{local:MapTo Row_City}" Content="City:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="{local:MapTo Row_State}" Content="State:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="{local:MapTo Row_Zip}" Content="Zip:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<Label Grid.Row="{local:MapTo Row_Company}" Content="Company:" Margin="{StaticResource Rowspacing}" HorizontalAlignment="Right"/>
<TextBox Grid.Column="1" Grid.Row="{local:MapTo Row_FirstName}" Text="Shannon" MinWidth="300" VerticalAlignment="Center" />
<TextBox Grid.Column="1" Grid.Row="{local:MapTo Row_LastName}" Text="Braun" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="{local:MapTo Row_Street}" Text="111 Main Street" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="{local:MapTo Row_City}" Text="Minneapolis" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="{local:MapTo Row_State}" Text="Mn" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="{local:MapTo Row_Zip}" Text="55414" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
<TextBox Grid.Column="1" Grid.Row="{local:MapTo Row_Company}" Text="Sysknowlogy" AcceptsReturn="True" VerticalAlignment="Center"></TextBox>
To add additional rows, add a new RowDefinition with a name and then map the Grid.Row attached property to the name of the row.
Posted in WPF Tips & Tricks |Comments [1]
Sysknowlogy