2

I created a somewhat large input form in a WPF application, using nested Grids. I'm using VS2010 and VS2012 Ultimate.

Here is the code:

<Window x:Class="Gridtest.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow">

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="16*"/>
        <ColumnDefinition Width="10*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Grid Grid.Column="0" Grid.Row="0" Grid.RowSpan="2">
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="136*"/>
            <ColumnDefinition Width="271*" />
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Grid.Column="0" Content="Battery Name"/>
        <Label Grid.Row="1" Grid.Column="0" Content="FDC Callsign"/>
        <Label Grid.Row="2" Grid.Column="0" Content="Battery Type"/>
        <Label Grid.Row="3" Grid.Column="0" Content="GRID"/>
        <Label Grid.Row="4" Grid.Column="0" Content="ALT"/>
        <Label Grid.Row="5" Grid.Column="0" Content="Dir. of Fire"/>
        <Label Grid.Row="6" Grid.Column="0" Content="Target Prefix"/>
        <Label Grid.Row="7" Grid.Column="0" Content="Target # Start"/>

        <Grid Grid.Column="1" Grid.ColumnSpan="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <ComboBox x:Name="EBattName"  IsEditable="True" ItemsSource="{Binding BatteryList}" SelectedItem="{Binding SelectedBattery, Mode=TwoWay}" DisplayMemberPath="Name"/>
            <Button x:Name="EBattSave" Grid.Column="1" Content="Add"/>
        </Grid>
        <TextBox x:Name="EBattCallsign" Grid.Row="1" Grid.Column="1" DataContext="{Binding SelectedBattery}" Text="{Binding Callsign}" Grid.ColumnSpan="2"/>
        <ComboBox x:Name="EBattType" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedBattery}" SelectedItem="{Binding BWeapon}" DisplayMemberPath="Designation"/>
        <TextBox x:Name="EBattGrid" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedBattery}" Text="{Binding Coords.Grid}"/>
        <TextBox x:Name="EBattAlt" Grid.Row="4" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedBattery}" Text="{Binding Coords.Altitude}"/>
        <TextBox x:Name="EBattDir" Grid.Row="5" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedBattery}" Text="{Binding Dir}"/>
        <TextBox x:Name="EBattPre" Grid.Row="6" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedBattery}" Text="{Binding Prefix}"/>
        <TextBox x:Name="EBattStart" Grid.Row="7" Grid.Column="1" Grid.ColumnSpan="2" DataContext="{Binding SelectedBattery}" Text="{Binding Start}"/>
    </Grid>

    <Grid Grid.Column="0" Grid.Row="2">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
            <ColumnDefinition/>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Label Grid.ColumnSpan="3" Content="NEW MISSION"/>

        <Button x:Name="BMissionGrid" Grid.Row="1" Content="GRID"/>
        <Button x:Name="BMissionPolar" Grid.Column="1"  Grid.Row="1" Content="POLAR"/>
        <Button x:Name="BMissionShift" Grid.Column="2"  Grid.Row="1" Content="SHIFT"/>
    </Grid>

    <!-- Adding negative bottom margin to this grid helps... -->
    <Grid Grid.Column="1" Grid.Row="0" Grid.RowSpan="3" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <Label Grid.Row="0" Grid.ColumnSpan="2" Content="OBSERVER INFORMATION"/>

        <Label Grid.Row="1" Grid.Column="0" Content="Select"/>
        <Label Grid.Row="2" Grid.Column="0" Content="Name"/>
        <Label Grid.Row="3" Grid.Column="0" Content="Grid"/>
        <Label Grid.Row="4" Grid.Column="0" Content="Alt"/>


        <Grid  Grid.Row="1" Grid.Column="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <ComboBox Grid.Column="0" x:Name="EObsSelect" ItemsSource="{Binding SelectedBattery.Observers}" SelectedItem="{Binding SelectedObserver, Mode=TwoWay}" DisplayMemberPath="Name" />
            <Button Grid.Column="1" x:Name="BObserverAdd" Content="New"/>
        </Grid>

        <TextBox x:Name="EObsName" Grid.Row="2" Grid.Column="1" Text="{Binding SelectedObserver.Name}"          />
        <TextBox x:Name="EObsGrid" Grid.Row="3" Grid.Column="1" Text="{Binding SelectedObserver.Coord.Grid}"    />
        <TextBox x:Name="EObsAlt" Grid.Row="4" Grid.Column="1"  Text="{Binding SelectedObserver.Coord.Altitude}"/>

        <Label Grid.ColumnSpan="2" Grid.Row="5" Content="KNOWN POINTS"/>

        <Label Grid.Row="6" Grid.Column="0" Content="Select"/>
        <Label Grid.Row="7" Grid.Column="0" Content="Name"/>
        <Label Grid.Row="8" Grid.Column="0" Content="Grid"/>
        <Label Grid.Row="9" Grid.Column="0" Content="Alt"/>

        <Grid  Grid.Row="6" Grid.Column="1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <ComboBox x:Name="EKPSelect" Grid.Column="0" ItemsSource="{Binding SelectedBattery.Knownpoints}" SelectedItem="{Binding SelectedPoint, Mode=TwoWay}" DisplayMemberPath="Name"/>
            <Button Grid.Column="1" x:Name="BKnownpointAdd" Content="New"/>
        </Grid>

        <TextBox x:Name="EKPName" Grid.Row="7" Grid.Column="1"  Text="{Binding SelectedItem.Name, ElementName=EKPSelect}"          />
        <TextBox x:Name="EKPGrid" Grid.Row="8" Grid.Column="1"  Text="{Binding SelectedItem.Coord.Grid, ElementName=EKPSelect}"    />
        <TextBox x:Name="EKPAlt" Grid.Row="9" Grid.Column="1"   Text="{Binding SelectedItem.Coord.Altitude, ElementName=EKPSelect}"/>
    </Grid>


    <Grid Grid.Column="0" Grid.Row="3" Grid.ColumnSpan="2">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>

        <Label Content="MISSION LIST"/>

        <ListView Grid.Row="1" SelectionMode="Single"
            DataContext="{Binding SelectedBattery}"
            ItemsSource="{Binding Missions}"
                  >
            <ListView.View>
                <GridView>
                    <GridViewColumn DisplayMemberBinding="{Binding TargetNumber}" Header="Target No"/>
                    <GridViewColumn DisplayMemberBinding="{Binding TargetDescription}" Header="Description"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Coords.Grid}" Header="Location"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Coords.Altitude}" Header="Altitude"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Attitude}" Header="Attitude"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Length}" Header="Length"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Radius}" Header="Radius"/>
                    <GridViewColumn DisplayMemberBinding="{Binding Notes}" Header="Remarks"/>
                </GridView>
            </ListView.View>
        </ListView>

    </Grid>

</Grid>

I extracted it from my project and removed all the event handlers and bindings pointing into my project namespace so you can take this and paste it into a new WPF Application project and see for yourself.

Here is the resulting window:

Design view: (extra spacing marked in red there)
enter image description here Runtime view: enter image description here

ASCII View of what it should look like:

---------------
|   1   |     |
--------|  3  |
|   2   |     |
---------------
|      4      |
---------------

It creates humongous amounts of vertical spacing that I don't want. Where is this coming from? How can I fix it? As I marked in the code above, adding Margin="0,0,0,-200" to one of the grids helps somewhat, but that seems very ugly.

I'm probably going to rebuild the grid from the ground up to make it less nested, but still, it seems to me like this shouldn't be happening.

EDIT: I rebuilt the grid with minimal nesting, so it's just one big grid with all the elements put into it:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/><!-- Left Labels -->
        <ColumnDefinition Width="10*"/><!-- Left Edit controls --> 
        <ColumnDefinition Width="Auto"/><!-- Right labels -->
        <ColumnDefinition Width="7*"/><!-- Right Edit controls -->
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto" /> <!-- Everything in its own row -->
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="Auto" />
        <RowDefinition Height="*"    /> <!-- Listview in this row -->
    </Grid.RowDefinitions>

    <!-- Elements here -->

</Grid>

And that works without any apparent issues.

H.B.
  • 166,899
  • 29
  • 327
  • 400
duk3luk3
  • 21
  • 3

1 Answers1

1

I was testing with your code and noticed some strange behavior. Your panels should render like this:

---------------
|   1   |     |
--------|  3  |
|   2   |     |
---------------
|      4      |
---------------

But they're currently rendering like this, with extra space being added to the bottom of both the 2 and 3 cells.

---------------
|   1   |     |
--------|  3  |
|   2   |     |
|       |     |
---------------
|      4      |
---------------

For some reason, elements added in cell #3 are extending the cell height by adding extra space at the bottom.

Strangely enough, I found this only occurs when your RowSpan for cell #3 is set to an odd number. If you set that to 2 or 4, it appears to render just fine.

<Grid>
    <Grid x:Name="1" Grid.Column="0" Grid.Row="0" ... />
    <Grid x:Name="2" Grid.Column="0" Grid.Row="1" ... />
    <Grid x:Name="3" Grid.Column="1" Grid.Row="0" Grid.RowSpan="2" ... />
    <Grid x:Name="4" Grid.Column="0" Grid.Row="3" Grod.ColumnSpan="2" ... />
</Grid>

<Grid>
    <Grid x:Name="1" Grid.Column="0" Grid.Row="0" Grid.RowSpan="3" ... />
    <Grid x:Name="2" Grid.Column="0" Grid.Row="3" ... />
    <Grid x:Name="3" Grid.Column="1" Grid.Row="0" Grid.RowSpan="4" ... />
    <Grid x:Name="4" Grid.Column="0" Grid.Row="4" Grod.ColumnSpan="2" ... />
</Grid>

Removing most of the elements in cell #3 correctly draws the cells to the correct height, and reducing the number of elements in #3 will shrink the height, but not eliminate it, so perhaps it has something to do with calculating the margin or padding?

So my suggestion would be to make the RowSpan=2 like the first example, or change your panel layout so it's like this:

<StackPanel>
    <DockPanel>
        <Grid x:Name="3" DockPanel.Dock="Right" ... />
        <Grid x:Name="2" DockPanel.Dock="Top" ... />
        <Grid x:Name="1" ... />
    </DockPanel>
    <Grid x:Name="4" ... />
</StackPanel>

A Grid is designed to make its children fill all available space if possible.

You are setting Height="Auto" on all your rows, which means rows will by default take up only the amount of space they need to render their controls. However because of the way a Grid works, it's going to try and stretch at least one of those rows to fill all available space.

Typically this stretch is done equally, with all the rows getting assigned an equal amount of the extra space, as you can see in your Design Time window. But since your 2nd column has only two objects, one with RowSpan=3, it looks like at runtime it decided to split the extra space equally between the combined 1-2-3 row, and row 4.

To avoid this behavior, make sure to specify at least one * height row to take up all remaining space, even if that is a blank row at the bottom.

<Grid.ColumnDefinitions>
    <ColumnDefinition Width="16*"/>
    <ColumnDefinition Width="10*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="Auto"/>
    <RowDefinition Height="*"/> <!-- 5th row to take up all available space -->
</Grid.RowDefinitions>

Or switch to a different Panel that doesn't have this behavior, such as a DockPanel with LastChildFill="False", or StackPanel

Rachel
  • 130,264
  • 66
  • 304
  • 490
  • Hmm, but where is the remaining space even coming from? I don't have any height or minheight specified anywhere. Is there a default height for the window or the grid? I tried to add some Height="*" rows in random places, but it doesn't change the behaviour much... – duk3luk3 May 01 '13 at 14:04
  • @duk3luk3 The default behavior of the `Window` object is also to make it's child take up all available space. In WPF, the `Panels` usually determine the size and layout of the children, not the child objects. Of course, the child can have properties that affect their positioning and size as well (Min/Max Height/Width, Horizontal/Vertical Alignment, etc). As for where to add the `*` sized row, you need to add that to the parent `Grid`, since that is the one allocating extra space to its rows. See the update to my answer :) – Rachel May 01 '13 at 14:09
  • Adding the Height="\*" row to the parent grid did absolutely nothing for me. Playing around with the parent grid row heights and making *several* of them Height="\*" changed the behaviour somewhat but still very weird. – duk3luk3 May 01 '13 at 14:38
  • @duk3luk3 Ahhh I see what you're saying now. That's strange, it looks like it's applying extra space to the column based on the margins or padding of elements in the right-side. If you remove all the elements there, the extra space goes away. And removing only some of the elements reduces the space, but doesn't eliminate it. I'd suggest using a `DockPanel` instead for the layout you want, and will update my answer to reflect that, however I am very curious why it's behaving like that :) – Rachel May 01 '13 at 14:57
  • @HighCore lol thanks, but in this case I think my answer is wrong, and I'm going to edit it :) – Rachel May 01 '13 at 14:57
  • At this point I'm beginning to suspect I hit some edge case in the WPF layouting algorithms. I rebuilt the grid with minimal nesting and now I get something that works and is much cleaner to boot, so I'm just going to go with that. I'm going to add that to my question. Thanks a lot for the help though. – duk3luk3 May 01 '13 at 15:02
  • And yes, your current answer pinpoints the symptoms of the problem nicely. As I said in my original question, applying negative bottom margin to the grid cell number 3 (as designated in your diagram) seems to mitigate the original problem. – duk3luk3 May 01 '13 at 15:11