Frecklefoot Frecklefoot - 3 months ago 18
C# Question

XAML element fill all remaining space

I have a WPF application and I'm trying to get the elements positioned correctly. There are just four elements, so it should be pretty straight-forward, but I just can't get it working.

One wrinkle is that the window resizes itself to (about) the size of the desktop window when it appears, so it doesn't have a fixed size.

The elements are supposed to be stacked from top to bottom, so a Stack Panel seemed natural. But The third element has to take up all the remaining space that the top two and bottom ones don't. No matter what I tried, it either took up too much space, or too little. I could only seem to get it working if I gave it a concrete pixel size which, as explained above, won't work.

The latest thing I've tried is a Dock Panel. While it looks correct in the Visual Studio designer, when executed, the third element--a Canvas--completely covers the bottom element.

My XAML:

<DockPanel>
<Button x:Name="btnClose" DockPanel.Dock="Top" Content="X"
HorizontalAlignment="Right" Margin="0,5,5,0" VerticalAlignment="Top"
Width="Auto" Height="Auto" Background="Black"
Foreground="White" Click="btnClose_Click"/>
<Label x:Name="lblTitle" DockPanel.Dock="Top" Content="My Title"
HorizontalAlignment="Center" VerticalAlignment="Top" Width="Auto"
Foreground="White" FontWeight="Bold" FontSize="22"/>

<Label x:Name="lblControls" DockPanel.Dock="Bottom" Content="Placeholder"
HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="Auto"
Height="Auto" Foreground="White" FontWeight="Bold" FontSize="22"/>

<Border x:Name="CanvasBorder" BorderBrush="White" BorderThickness="5" >
<Canvas x:Name="cvsChart" Grid.Row="0" HorizontalAlignment="Stretch"
VerticalAlignment="Top" Width="Auto">
</Canvas>
</Border>
</DockPanel>


Any idea about how to get that Canvas to stretch and fill all the space the other three don't take?

UPDATE
Since @Peter Duniho pretty much proved to me that the code worked, I tried an experiment and removed the resizing code I have in place for when the window appears. Taking it out, the window appears absolutely correctly. This is what I do to resize it to (mostly) the desktop size:

public const int WINDOW_OFFSET = 10;
...

int screenWidth = (int)System.Windows.SystemParameters.PrimaryScreenWidth;
int screenHeight = (int)System.Windows.SystemParameters.PrimaryScreenHeight;

// center this window in desktop
Width = screenWidth - WINDOW_OFFSET;
Height = screenHeight - WINDOW_OFFSET;
Left = WINDOW_OFFSET/2;
Top = WINDOW_OFFSET/2;


So I did some poking around, and found a comment here on the 'Stack that said to get the WorkArea instead of the PrimaryScreenHeight. I tried that and voila!, the whole application window appears.

int screenWidth = (int)System.Windows.SystemParameters.WorkArea.Width;
int screenHeight = (int)System.Windows.SystemParameters.WorkArea.Height;


As it turns out, the bottom row was displaying, I just couldn't see it because it appeared below the bottom of the screen. Now I can see it, and I'm back to development heaven!

Thanks to everyone for their input!

Answer

There are a number of possible approaches to this. One of the most straightforward is to contain your elements in a Grid and set all but the third row height to Auto:

  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
      <RowDefinition/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <Button x:Name="btnClose" Content="X" Grid.Row="0"
            HorizontalAlignment="Right" Margin="0,5,5,0" VerticalAlignment="Top" 
            Width="Auto" Height="Auto" Background="Black" 
            Foreground="White" Click="btnClose_Click"/>
    <Label x:Name="lblTitle" Content="My Title" Grid.Row="1"
           HorizontalAlignment="Center" VerticalAlignment="Top" Width="Auto" 
           Foreground="White" FontWeight="Bold" FontSize="22"/>

    <Label x:Name="lblControls" Content="Placeholder" Grid.Row="3"
           HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="Auto" 
           Height="Auto" Foreground="White" FontWeight="Bold" FontSize="22"/>

    <Border x:Name="CanvasBorder" BorderBrush="White" BorderThickness="5" Grid.Row="2">
      <Canvas x:Name="cvsChart" Grid.Row="0" HorizontalAlignment="Stretch"
                VerticalAlignment="Top" Width="Auto">
      </Canvas>
    </Border>
  </Grid>

The default setting for a grid's row definition height is "*", which says to distribute all of the remaining space among all the rows with that setting. With only one row using that setting, it gets all of the leftover space.

This produces a window that looks like this:

four row grid with border and canvas filling the third row

(I set the window background to Gray so that your white text and border would be visible.)

Another option would in fact be to use DockPanel. It appears to me that the main problem in your attempt is that you set the lblControls element to DockPanel.Dock="Bottom" when it should be Top instead. When I change it to Top, it seems to work fine for me.

Based on your comment below, it seems you actually did want lblControls to be set to DockPanel.Dock="Bottom", and in fact the code you posted seems to also do what you want. It's not clear to me what is different from what the code you posted does and what you want it to do. It would be better if you would provide a good Minimal, Complete, and Verifiable code example that reliably reproduces the problem.