John the Ripper
John the Ripper

Reputation: 2439

Make the last TextBox in a DataTemplate Stretch

I have an ItemsControl:

<Border Grid.Row="1" Margin="20" BorderBrush="AliceBlue" BorderThickness="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
   <ItemsControl Margin="10" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" ItemsSource="{Binding SelectedEventHistoryEntryCollection}" ItemTemplateSelector="{StaticResource computerEventHistoryDataTemplateSelector}"/>
</Border>

With some datatemplates. I'm testing the first template:

<DataTemplate x:Key="DetailsDataTemplate">
        <Grid>
            <Label Width="150" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{x:Static resx:Resources.Label_ServiceDept}"/>
            <TextBox Margin="110,0,0,0" Width="200" IsReadOnly="True" Text="{Binding ServiceDepartment}" VerticalAlignment="Top" HorizontalAlignment="Left"/>

            <Label Width="150" Margin="0,40,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{x:Static resx:Resources.Label_SLA}"/>
            <TextBox Margin="110,40,0,0" Width="200" IsReadOnly="True" Text="{Binding SLA}" VerticalAlignment="Top" HorizontalAlignment="Left"/>

            <Label Width="150" Margin="0,80,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" Content="{x:Static resx:Resources.Label_Details}"/>
            <TextBox Margin="110,80,10,10" IsReadOnly="True" TextWrapping="Wrap" Text="{Binding Details}"/>
        </Grid>
    </DataTemplate>

I would like the last Textbox in the datatemplate to use up the remaining space, but nothing I tried works. How can I get this uncooperateive TextBox to stretch?

Edit: Removed the Height Property on the Textbox.

Upvotes: 0

Views: 791

Answers (4)

John Laffoon
John Laffoon

Reputation: 2915

I was a little slow on the uptake with my first answer. After realizing what you were after I don't think that approach was correct. Also, I don't think you can easily achieve what you're after using DataTemplates. However, I do think you have a few options:

  1. Check into Prism since is is good at doing things like building composite applications. However, it seems like WAY overkill for this problem. So, a more direct approach may be...
  2. Build out custom controls for each separate detail view you have and then write some custom logic to load each view as needed. You would set it up like this...

You main grid and your details view host (i.e. the ContentControl) would be set up something like this:

<Grid Background="Transparent">

    <Grid.RowDefinitions>
        <RowDefinition Height="1*" />
        <RowDefinition Height="8" />
        <RowDefinition Height="1.5*" />
    </Grid.RowDefinitions>

    <DataGrid Grid.Row="0" />

    <GridSplitter Grid.Row="1" Background="Transparent"
                    HorizontalAlignment="Stretch" VerticalAlignment="Stretch"  />

    <Border Grid.Row="2" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="3" Padding="8">
        <ContentControl Grid.Row="2" x:Name="myContent" />            
    </Border>

</Grid>

And each of your custom controls for your individual detail views would be set up something like this:

<UserControl x:Class="WpfApplication1.CustomUserControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Content="Status" />
        <Label Grid.Row="1" Grid.Column="0" Content="Description" />

        <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Status}" />
        <TextBox Grid.Row="1" Grid.Column="1" TextWrapping="Wrap" Text="{Binding Description}" />

    </Grid>
</UserControl>

At run time a row is selected in your DataGrid, you would have to load the correct user control with some code like this:

myContent.Content = new CustomUserControl();

Each of your custom controls would have to use star sizing, etc. to get the layouts to look right - which is what you were after with your question. Obviously there is still a lot of wireup that would need to be done.

That should give you what you are after. Sorry for the run-around on the first answer!

Upvotes: 0

John Laffoon
John Laffoon

Reputation: 2915

Generally, I use <Grid.RowDefinitions> and <Grid.ColumnDefinitions> in conjunction with star sizing * instead of margins for this type of layout.

UPDATE 1: (Removed for clarity)

UPDATE 2: When I wind up in situations like this where I can’t figure out where to apply a binding or a template I try to back up and look at the problem differently. I almost always take it back to the MVVM pattern. In this case, your Model is your EventHistory object. Your ViewModel has an ObservableCollection<EventHistory>. And your View is simply binding to that collection. So, to get something like this:

enter image description here

You would use something like this for your View:

<Grid>

    <Grid.RowDefinitions>
        <RowDefinition Height="1*" />
        <RowDefinition Height="8" />
        <RowDefinition Height="1.5*" />
    </Grid.RowDefinitions>

    <DataGrid Grid.Row="0" AutoGenerateColumns="True" 
                ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" 
                HorizontalGridLinesBrush="DarkGray" VerticalGridLinesBrush="DarkGray" />

    <GridSplitter Grid.Row="1" 
                    Background="Transparent"
                    HorizontalAlignment="Stretch" VerticalAlignment="Stretch"   />

    <Border Grid.Row="2" BorderBrush="DarkGray" BorderThickness="1" CornerRadius="3" Padding="8">

        <Grid>

            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>

            <Label Grid.Row="0" Grid.Column="0" Content="Status" />
            <TextBox Grid.Row="0" Grid.Column="1" Margin="0,0,0,8" Text="{Binding Path=Status}" />

            <Label Grid.Row="1" Grid.Column="0" Content="Detailed Description" />
            <TextBox Grid.Row="1" Grid.Column="1" Text="{Binding Path=Description}" />

        </Grid>
    </Border>
</Grid>

And that is just fine -- because that is what you are trying to achieve. The bindings on the 2 labels and textboxes at the bottom of the screen don’t have to be part of any data template. They are part of the view (everything inside the red border in the screenshot). All of the resizing works and everything is good. If you really want to move things into a DataTemplate, it is probably possible, but this seemed more natural at this point.

NOTE: After creating the View (area inside the red border) I hosted it in the main window leaving an area to the right as per your screenshot. I also took a few liberties with a grid splitter, star resizing and margins so things would take up all of the available space while maintaining the pictured proportions.

Hopefully that helps!

Upvotes: 2

Jay
Jay

Reputation: 57919

Use a <DockPanel> instead of a <Grid>.

The last item in the DockPanel uses remaining space.

Upvotes: 2

SLaks
SLaks

Reputation: 887453

Change the grid to a DockPanel with LastChildFill="true".
You can then get rid of all of the Margins and let WPF do the layout automatically.

Upvotes: 2

Related Questions