Jan Solo
Jan Solo

Reputation: 193

C# WPF - Make DataGrid stretch to available size, not expand the available size

I use Visual Studio 2010. I've been struggling trying to get a datagrid to fill the existing space. If necessary, to show a scrollbar on the datagrid, not on the page.

Example code:

<Page x:Class="TestApp.Page1"
  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" 
  mc:Ignorable="d" 
  d:DesignHeight="300" d:DesignWidth="300"
Title="Page1">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="20"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="20"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition Height="200"/>
        <RowDefinition Height="20"/>
    </Grid.RowDefinitions>
    <Expander x:Name="MyExpander"
              Grid.Column="1"
              Grid.Row="1"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Top"
              Header="My Expander"
              Expanded="MyExpander_Expanded"
              Collapsed="MyExpander_Collapsed">
        <Viewbox Stretch="Uniform">
        <StackPanel>
            <DataGrid Margin="5"
                      Height="100"
                      HorizontalAlignment="Stretch"
                      VerticalAlignment="Stretch"
                      HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto">
            <DataGrid.Columns>
                <DataGridTextColumn Header="MyFirstColumn"/>
                <DataGridTextColumn Header="MySecondColumn"/>
                <DataGridTextColumn Header="MyThirdColumn"/>
                <DataGridTextColumn Header="MyFourthColumn"/>
                <DataGridTextColumn Header="MyFifthColumn"/>
                <DataGridTextColumn Header="MySixthColumn"/>
                <DataGridTextColumn Header="MySeventhColumn"/>
                <DataGridTextColumn Header="MyEighthColumn"/>
                <DataGridTextColumn Header="MyNinthColumn"/>
                <DataGridTextColumn Header="MyTenthColumn"/>
            </DataGrid.Columns>
        </DataGrid>
        <Button x:Name="DoSomething"
                Margin="5"
                Width="100"
                Height="30"
                Content="Do Something"
                Click="DoSomething_Click"/>
        </StackPanel>
        </Viewbox>
    </Expander>
</Grid>

The problem is, it seems, whatever I do, the datagrid will always stretch until all columns are completely visible, even if it stretches way past the window (or page) size. Note: I have a panel on the right, showing status, dates, info, ..., which should stay on the screen.

I have tried too many things to list them all. The only working 'solution' I found is adding another control (invisible) like a separator on the grid and binding the expander width to the width of the separator. It works, but it's crappy programming.

MaxWidth="{Binding ElementName=MySeparator, Path=ActualWidth}"

Binding the width of the stackpanel to the (actual) width of the column just make the stackpanel disappear.

Can someone please help me. Thank you.

-- Update -- This is what I like to achieve: enter image description here

It seems that if I use hardcoded column widths, this problem doesn't occur. Only if there is a column that has a star-length, the datagrid takes as much space as it needs.

Upvotes: 2

Views: 4044

Answers (4)

Jan Solo
Jan Solo

Reputation: 193

Thanks everyone for the efforts. The solution was proposed by Mike Strobel in a comment. Since I can't mark comments as, I'll repeat it here.

I had a window, containing a scrollviewer, where all page are placed on. I removed the scrollviewer from the window and placed the scrollbars on the page.

<Page x:Class=...>
    <ScrollViewer HorizontalScrollBarVisibility="Disabled"
                  VerticalScrollBarVisibility="Visible">
        <Grid>

This actually solves the problem! Thanks Mike!

(If you run into this problem as well, please give mike's comment a thumbs up as well).

Upvotes: 1

Mike Strobel
Mike Strobel

Reputation: 25623

Since your diagram only addressed the scrolling issue, I have focused on that for now. It's still not entirely clear to me how you want this layout to look before scrolling is required, but let's use this as a starting point. Give it a try, and if the behavior isn't what you had in mind, please try to be as specific as possible about what needs to change (a picture is worth a thousand words).

For brevity, I'm only including the Expander and its content. The ancestor elements are exactly as they were in your original post.

<Expander x:Name="MyExpander"
          Grid.Column="1"
          Grid.Row="1"
          VerticalAlignment="Top"
          Header="My Expander"
          Expanded="MyExpander_Expanded"
          Collapsed="MyExpander_Collapsed">
  <StackPanel>
    <DataGrid Margin="5"
              xmlns:s="clr-namespace:System;assembly=mscorlib">
      <!-- Temporary items source to demonstrate grid measuring to fit rows -->
      <DataGrid.ItemsSource>
        <x:Array Type="s:String">
          <s:String>Dummy Row 1</s:String>
          <s:String>Dummy Row 2</s:String>
          <s:String>Dummy Row 3</s:String>
        </x:Array>
      </DataGrid.ItemsSource>
      <DataGrid.Columns>
        <DataGridTextColumn Header="MyFirstColumn" />
        <DataGridTextColumn Header="MySecondColumn" />
        <DataGridTextColumn Header="MyThirdColumn" />
        <DataGridTextColumn Header="MyFourthColumn" />
        <DataGridTextColumn Header="MyFifthColumn" />
        <DataGridTextColumn Header="MySixthColumn" />
        <DataGridTextColumn Header="MySeventhColumn" />
        <DataGridTextColumn Header="MyEighthColumn" />
        <DataGridTextColumn Header="MyNinthColumn" />
        <DataGridTextColumn Header="MyTenthColumn" />
      </DataGrid.Columns>
    </DataGrid>
    <Button x:Name="DoSomething"
            Margin="5"
            Width="100"
            Height="30"
            Content="Do Something"
            Click="DoSomething_Click" />
  </StackPanel>
</Expander>

This is how it looks with two columns:

[Grid with 2 Columns[2]

This is how it looks with ten columns:

Grid with 10 Columns

Update

According to your comments on another answer, your page is loaded inside a ScrollViewer at runtime. If that ScrollViewer supports horizontal scrolling, then that is the root of you problems: it will give your page as much horizontal space as it needs, and that need is being driven by the DataGrid. One (hacky) workaround would be to wrap your DataGrid in a custom decorator that fakes its desired width when measured and then fills whatever horizontal space is available when arranged. If you want to try this out, use my example from above, but wrap the DataGrid in a decorator like so:

<l:ZeroWidthDecorator xmlns:l="clr-namespace:TestApp">
  <DataGrid><!-- ... --></DataGrid>
</l:ZeroWidthDecorator>

The code for the decorator is as follows:

public class ZeroWidthDecorator : Border
{
    private Size _lastSize;
    private Size _idealSize;

    protected override void OnVisualChildrenChanged(DependencyObject added, DependencyObject removed)
    {
        base.OnVisualChildrenChanged(added, removed);
        _idealSize = new Size();
        _lastSize = new Size();
    }

    protected override Size MeasureOverride(Size constraint)
    {
        var child = this.Child;
        if (child == null)
            return new Size();
        if (child.IsMeasureValid)
            child.Measure(new Size(_lastSize.Width, Math.Max(_lastSize.Height, constraint.Height)));
        else
            child.Measure(new Size(0d, constraint.Height));
        _idealSize = child.DesiredSize;
        return new Size(0d, _idealSize.Height);
    }

    protected override Size ArrangeOverride(Size arrangeSize)
    {
        var child = this.Child;
        if (child != null)
        {
            if (arrangeSize != _lastSize)
            {
                // Our parent will assume our measure is the same if the last
                // arrange bounds are still available, so force a reevaluation.
                this.InvalidateMeasure();
            }
            child.Arrange(new Rect(arrangeSize));
        }
        _lastSize = arrangeSize;
        return arrangeSize;
    }
}

I haven't tested this extensively, but I tried a few different scenarios, and it seems to work. Use it at your own risk.

Upvotes: 1

mm8
mm8

Reputation: 169200

Get rid of the StackPanel:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="20"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="20"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="20"/>
        <RowDefinition Height="200"/>
        <RowDefinition Height="20"/>
    </Grid.RowDefinitions>
    <Expander x:Name="MyExpander"
              Grid.Column="1"
              Grid.Row="1"
              HorizontalAlignment="Stretch"
              VerticalAlignment="Top"
              Header="My Expander"
              Expanded="MyExpander_Expanded"
              Collapsed="MyExpander_Collapsed">
        <Viewbox Stretch="Uniform">
            <DataGrid Margin="5"
                      Height="100"
                      HorizontalAlignment="Stretch"
                      VerticalAlignment="Stretch"
                      HorizontalScrollBarVisibility="Auto"
                      VerticalScrollBarVisibility="Auto">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="MyFirstColumn"/>
                    <DataGridTextColumn Header="MySecondColumn"/>
                    <DataGridTextColumn Header="MyThirdColumn"/>
                    <DataGridTextColumn Header="MyFourthColumn"/>
                    <DataGridTextColumn Header="MyFifthColumn"/>
                    <DataGridTextColumn Header="MySixthColumn"/>
                    <DataGridTextColumn Header="MySeventhColumn"/>
                    <DataGridTextColumn Header="MyEighthColumn"/>
                    <DataGridTextColumn Header="MyNinthColumn"/>
                    <DataGridTextColumn Header="MyTenthColumn"/>
                </DataGrid.Columns>
            </DataGrid>
            <Button x:Name="DoSomething"
                Margin="5"
                Width="100"
                Height="30"
                Content="Do Something"
                Click="DoSomething_Click"/>
        </Viewbox>
    </Expander>
</Grid>

StackPanels and ScrollViewers don't work well together because of the way a StackPanel measures its children:

XAML/WPF - ScrollViewer which has StackPanel inside is not scrolling

And since the default control template of the DataGrid includes a ScrollViewer you don't need to add your own one.

Upvotes: 0

Kacper Stachowski
Kacper Stachowski

Reputation: 975

Try wrapping your DataGrid with ScrollViewer like this:

<ScrollViewer VerticalScrollBarVisibility="Auto"
              HorizontalScrollBarVisibility="Auto">
    <DataGrid>
        // SOME CONTENT
    </DataGrid>
</ScrollViewer>

Also I don't know if you need that ViewBox at all. Try removing it and see if it fits your needs.

Upvotes: 0

Related Questions