tags:

views:

275

answers:

1

My LOB app has loads of screens that have columns of labels and textboxes, side-by-side.

I'm wanting to achive something in-between what a Grid and a WrapPanel layout - where when the Page/Window is wide enough, the label + textbox pairs re-organize into multiple columns. Imagine:

Narrow Window Layou:

Label            | TextBox
Looooong Label   | TextBox
Label            | TextBox
Label            | TextBox

Broad Window Layout:

Label            | TextBox      | Looooong Label   | TextBox
Label            | TextBox      | Label            | TextBox

Using a Grid is easy enough, since the label columns width can be "Auto"... but then it's hard to dynamically set the number of columns as the window width increases / decreases, since it nescessitates a style on every label/textbox.

Using a WrapPanel achieves the multi-column effect, but then each Label's width is different. I would prefer a solution that doesn't involve restricting all of the label's Width properties through styling or binding.

Do you know of an elegant solution to this problem, or have you come across any open-source/3rd party panel controls which specifically offer this?

I respect the Stack-Overflow community and will definately up-vote any reasonable suggestions.

A: 

I've found an answer, which seems to have several benefits in terms of improving XAML readability and maintainability.

By creating ControlTemplates for TextBox and Combobox, you can easily establish grid with SharedSizeGroup's on the columns, which helps keep the labels the same width. The control template I created uses the Tag property of the Logical control to determine the text for the label in the template-created visual.

Consider the following XAML, which creates two text boxes, both data-bound to an underlying business object:

<WrapPanel Orientation="Horizontal" Grid.IsSharedSizeScope="True">

    <TextBox Tag="On Hand:"
         Text="{Binding Path=Product.StockOnHand}"
         Template="{StaticResource LabeledTextBoxTemplate}"
         IsReadOnly="True"
         ToolTip="" />

    <TextBox Tag="On Order:"
         Text="{Binding Path=Product.StockOnOrder}"
         Template="{StaticResource LabeledTextBoxTemplate}"
         IsReadOnly="True"
         ToolTip="" />

</WrapPanel>

The boxes are inside a wrap panel where the SharedSizeGroup is set to true. This allows the grids (which the control templates create) to share column width information. Since the textboxes are inside a WrapPanel, they will use as much width as possible before wrapping to the next line.

The ControlTemplate (and style) that renders the above logical textboxes into grids, where each grid has two columns, one containing a label and the other containing a textbox, is as follows:

<!--Style and ControlTemplates to support aligned, labeled text boxes and combo boxes.-->
<Style TargetType="Grid"
       x:Key="LabelledDataGridStyle">
    <Setter Property="Margin"
        Value="0,0,12,4" />
</Style>

<Style TargetType="ColumnDefinition"
       x:Key="LabelingGridThirdColumnStyle">
    <Setter Property="Width"
        Value="150" />
    <Style.Triggers>
     <DataTrigger  Binding="{Binding Path=ItemWidth,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=WrapPanel,AncestorLevel=1}}"
            Value="{x:Null}">
      <Setter Property="Width"
          Value="Auto" />
      <Setter Property="SharedSizeGroup"
          Value="C" />
     </DataTrigger>
    </Style.Triggers>
</Style>

<ControlTemplate TargetType="TextBox" x:Key="LabeledTextBoxTemplate">
    <Grid Style="{StaticResource LabelledDataGridStyle}">
     <Grid.ColumnDefinitions>
      <ColumnDefinition SharedSizeGroup="A" Width="Auto" />
      <ColumnDefinition Style="{StaticResource LabelingGridThirdColumnStyle}" />
     </Grid.ColumnDefinitions>

     <TextBlock Text="{Binding Path=Tag,RelativeSource={RelativeSource TemplatedParent}}"
           VerticalAlignment="Top"
           Margin="0,4,8,0"
           HorizontalAlignment="Left" />

     <TextBox Text="{Binding Path=Text,RelativeSource={RelativeSource TemplatedParent}}"
        TextAlignment="{Binding Path=TextAlignment,RelativeSource={RelativeSource TemplatedParent}}"
        Style="{Binding Path=Style,RelativeSource={RelativeSource TemplatedParent}}"
        Background="{Binding Path=Background,RelativeSource={RelativeSource TemplatedParent}}"
        ToolTip="{Binding Path=ToolTip,RelativeSource={RelativeSource TemplatedParent}}"
        ContextMenu="{Binding Path=ContextMenu,RelativeSource={RelativeSource TemplatedParent}}"
        MinWidth="100"
        Grid.Column="1" />

    </Grid>

</ControlTemplate>

It's not perfect (a Panel control that does the job would have been ideal), but for a quick fix, this works nicely.

Mark