Search code examples
c#wpfxamlwrappanel

WPF Wrappanel nicely aligned Textblocks and Textboxes


I have been banging my head for a few hours trying to figure out how can I have my textblocks and textboxes aligned in a Wrappanel. I have tried everything that I could find on stackoverflow but the truth is this in my first WPF app and I am having a hard time figuring things out.

What I am trying to achieve is described in detail in this post but I couldn't get it to work using the steps outlined here: http://badecho.com/2012/07/wpf-grid-like-wrappanels/

Here's what I have so far:

<TabControl HorizontalAlignment="Left" 
    Margin="10,150,0,10"
    VerticalAlignment="Top">
<TabItem Header="Repairs">

<Grid Margin="0,0,10,0">

    <WrapPanel Grid.IsSharedSizeScope="True" Orientation="Horizontal" Margin="0,10,10,10">
        <TextBlock TextWrapping="WrapWithOverflow" Margin="20, 5, 0, 20">
         Regular Paid Hours
        </TextBlock>
        <TextBox Margin="20, 0, 10, 20" Width="45"></TextBox>

        <TextBlock TextWrapping="WrapWithOverflow" Margin="20, 5, 0, 20">
         Overtime Hours
        </TextBlock>
        <TextBox Margin="20, 0, 10, 20" Width="45"></TextBox>

        <TextBlock TextWrapping="WrapWithOverflow" Margin="20, 5, 0, 20">
         Repair Labor
        </TextBlock>
        <TextBox Margin="20, 0, 10, 20" Width="45"></TextBox>\

        <!-- There are a lot more -->  
    </WrapPanel>

    <DataGrid AutoGenerateColumns="False" ColumnHeaderStyle="{StaticResource lowCase}" Margin="20,180,10,70" Name="dtGrid" HorizontalAlignment="Left" CanUserResizeRows="False" ItemsSource="{Binding}" VerticalAlignment="Top" GridLinesVisibility="All">
        <DataGrid.Columns>
            <DataGridTextColumn Binding="{Binding Path=Location}" Header="Location"/>
            <DataGridTextColumn Binding="{Binding Path=Date, StringFormat ='MM-dd-yy'}" Header="Date"/>
            <DataGridTextColumn Binding="{Binding Path=RegularPaidHours}" Header="Regular Repair Hours"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

What I am trying to achieve: Textboxes should be aligned when the window is resized and the datagrid should move down in order to fit all the textblocks and textboxes. What am I missing?

Can someone please offer me a dumbproof solution? I would be so grateful for an easy fix.


Solution

  • Here's an approach using implicit styles and HeaderedContentControls. You can set margins, widths and so on in the ControlTemplate. I left it pretty bare-bones. The "InputCol" cell will by default stretch its content horizontally, but you can see in the example how to defeat that for a particular control. "SharedSizeGroup" and "IsSharedSizeScope" on the container are the magic that make all the labels the same width, but no wider than they need to be.

    <Window.Resources>
        <Style TargetType="WrapPanel" x:Key="WrapForm">
            <Setter Property="Orientation" Value="Horizontal" />
            <Setter Property="Grid.IsSharedSizeScope" Value="True" />
            <Style.Resources>
                <!-- Implicit style for all HeaderedContentControls -->
                <Style TargetType="HeaderedContentControl">
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="HeaderedContentControl">
                                <Grid>
                                    <Grid.ColumnDefinitions>
                                        <ColumnDefinition Width="Auto" SharedSizeGroup="LabelCol" />
                                        <ColumnDefinition Width="120" SharedSizeGroup="InputCol" />
                                    </Grid.ColumnDefinitions>
                                    <Label
                                        VerticalContentAlignment="Top"
                                        Grid.Column="0"
                                        Content="{TemplateBinding Header}" 
                                        />
                                    <ContentControl
                                        VerticalContentAlignment="Top"
                                        Grid.Column="1"
                                        Content="{TemplateBinding Content}"
                                        />
                                </Grid>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Style.Resources>
        </Style>
    </Window.Resources>
    <Grid>
        <WrapPanel Style="{StaticResource WrapForm}">
            <HeaderedContentControl Header="Regular Paid Hours">
                <TextBox />
            </HeaderedContentControl>
            <HeaderedContentControl Header="Overtime Hours">
                <TextBox />
            </HeaderedContentControl>
            <HeaderedContentControl Header="Repair Labor">
                <ComboBox HorizontalAlignment="Left" />
            </HeaderedContentControl>
            <HeaderedContentControl Header="This One Has Two">
                <StackPanel Orientation="Vertical">
                    <CheckBox>One Thing</CheckBox>
                    <CheckBox>Or Another</CheckBox>
                </StackPanel>
            </HeaderedContentControl>
        </WrapPanel>
    </Grid>
    

    Update

    Simple two-row grid layout:

    <Grid>
        <Grid.RowDefinitions>
            <!-- 
            This 2* + 1* means "divide the grid vertically into three equal parts,
            and give two of them to row zero and one of them to row one". 
            You could give them both Height="*" and it'll be divided evenly, or 
            make one of them Height="Auto" and it'll get the height it takes up, 
            while the other one will get all the remainder. 
            -->
            <RowDefinition Height="2*" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>
        <ScrollViewer
            Grid.Row="0"
            >
            <WrapPanel 
                Style="{StaticResource WrapForm}"
                >
                <HeaderedContentControl Header="Regular Paid Hours">
                    <TextBox />
                </HeaderedContentControl>
                <HeaderedContentControl Header="Overtime Hours">
                    <TextBox />
                </HeaderedContentControl>
                <HeaderedContentControl Header="Repair Labor">
                    <ComboBox HorizontalAlignment="Left" />
                </HeaderedContentControl>
                <HeaderedContentControl Header="This One Has Two">
                    <StackPanel Orientation="Vertical">
                        <CheckBox>One Thing</CheckBox>
                        <CheckBox>Or Another</CheckBox>
                    </StackPanel>
                </HeaderedContentControl>
            </WrapPanel>
        </ScrollViewer>
        <DataGrid
            Grid.Row="0">
            <!-- stuff -->
        </DataGrid>
    </Grid>
    

    The Grid here is the main Grid in the Window. It may take some finicking around to get it just the way you want it.