Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can the WPF Datagrid control be made to behave like the WinForms DataGridControl in regard to column layout?

Basically, I'd like to make the WPF DataGrid control layout its columns exactly the way the WinForms DataGridView does

And more specifically, here is the behaviour I'm looking for:

  • The grid control should take up the space it's given (i.e. however much space is available in its parent control for it to use). Here I am referring just to the control, and not to the columns.

  • The columns created (whether automatically or manually) may or may not take up all this space.

  • If there is extra space left over after the columns are created, the last column should not be expanded to fill this space

  • If there is extra space left over after the columns are created, an empty column with nothing in it should not be created to fill this extra space

From what I can tell, in WPF the last two bullet points seem to be mutually exclusive and you must choose one or the other. Has anyone found a way to do both? I've searched quite a bit and haven't found quite what I'm looking for, however all the posts I'm finding tend to be a couple years old so I'm hoping someone has figured this thing out by now.

EDIT: sa_ddam213, here's a quick xaml project I put together to test your suggestion.

<Window x:Class="DataGridFix.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataGridFix"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <ObjectDataProvider x:Key="data"
                    ObjectType="{x:Type local:TestObject}"
                    MethodName="GetTestData" />
    </Window.Resources>
    <StackPanel>
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="Auto" Height="150" VerticalAlignment="Top" ItemsSource="{Binding Source={StaticResource data}}" />
    </StackPanel>
</Window>

And here's the code behind:

namespace DataGridFix { public class TestObject { public int Id { get; set; } public string Name { get; set; }

    public static List<TestObject> GetTestData()
    {
        var items = new List<TestObject>();

        items.Add(new TestObject() { Id = 1, Name = "Joe" });
        items.Add(new TestObject() { Id = 2, Name = "Matt" });
        items.Add(new TestObject() { Id = 3, Name = "Hal" });

        return items;
    }
}

}

Really the only noteable thing I see from your suggestion is to set HorizontalAlignment to Left. I did that, and tried setting the ColumnWidth to the various settings but had the same problem with each (except * of course... technically I can mess that one up to but I won't go into that).

If you use your mouse to expand any of the columns, and then decrease the column size then the empty filler column appears. The only other difference I noted from your post was that you put your DataGrids in a StackPanel since you had more than one of them. I tried that just for the heck of it but same result. If you see any other difference between what I'm doing and what you suggested please let me know.

like image 364
Brandon Moore Avatar asked Dec 30 '12 09:12

Brandon Moore


People also ask

What features are supported by the WPF DataGrid control?

DataGrid can be customized in appearance, such as cell font, color, and size. DataGrid supports all styling and templating functionality of other WPF controls. DataGrid also includes default and customizable behaviors for editing, sorting, and validation.

What is difference between DataGridView and DataGrid control in Winforms?

The DataGrid control is limited to displaying data from an external data source. The DataGridView control, however, can display unbound data stored in the control, data from a bound data source, or bound and unbound data together.

What is the purpose of using DataGrid control?

A common use of the DataGrid control is to display a single table of data from a dataset. However, the control can also be used to display multiple tables, including related tables. The display of the grid is adjusted automatically according to the data source.

What is the difference between grid and DataGrid in WPF?

A Grid is a control for laying out other controls on the form (or page). A DataGrid is a control for displaying tabular data as read from a database for example.


2 Answers

In order to get the behavior you want, you probably have to modify the control template of the DataGrid.

Take a look at the code. I have gotten pretty close to the WinForms DataGridView look i think.

To remove the extra column you have to remove the filler column from the DataGridColumnHeadersPresenter. I have just commented it out. The rest is just the default template.

The other modification is to the template of the DataGrid.
By setting HorizontalAlignment="Left" on the ScrollContentPresenter, the rows no longer take up all the width of the control.

Those are the only 2 changes I have made to the default templates.

enter image description here

<Style TargetType="{x:Type DataGridColumnHeadersPresenter}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
                <Grid>
                    <!-- Remove this filler column -->
                    <!--<DataGridColumnHeader x:Name="PART_FillerColumnHeader" IsHitTestVisible="False" />-->
                    <ItemsPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

<Style TargetType="{x:Type DataGrid}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGrid}">
                <Border Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                SnapsToDevicePixels="True"
                                Padding="{TemplateBinding Padding}">
                    <ScrollViewer Focusable="false" Name="DG_ScrollViewer">
                        <ScrollViewer.Template>
                            <ControlTemplate TargetType="{x:Type ScrollViewer}">
                                <Grid>
                                    <Grid.RowDefinitions>
                                        <RowDefinition Height="Auto"/>
                                        <RowDefinition Height="*"/>
                                        <RowDefinition Height="Auto"/>
                                    </Grid.RowDefinitions>

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

                                    <Button Command="{x:Static DataGrid.SelectAllCommand}"
                                                    Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=CellsPanelHorizontalOffset}"
                                                    Style="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type DataGrid}, ResourceId=DataGridSelectAllButtonStyle}}"
                                                    Focusable="false"
                                                    Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.All}}" />

                                    <DataGridColumnHeadersPresenter Grid.Column="1" Name="PART_ColumnHeadersPresenter"
                                                    Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=HeadersVisibility, Converter={x:Static DataGrid.HeadersVisibilityConverter}, ConverterParameter={x:Static DataGridHeadersVisibility.Column}}"/>

                                    <!-- Set HorizontalAlignment="Left" to have the rows only take up the width they need and not fill the entire width of the DataGrid -->
                                    <ScrollContentPresenter HorizontalAlignment="Left" x:Name="PART_ScrollContentPresenter" Grid.Row="1" Grid.ColumnSpan="2" CanContentScroll="{TemplateBinding CanContentScroll}" />

                                    <ScrollBar Grid.Row="1" Grid.Column="2" Name="PART_VerticalScrollBar"
                                                        Orientation="Vertical"
                                                        Maximum="{TemplateBinding ScrollableHeight}"
                                                        ViewportSize="{TemplateBinding ViewportHeight}"
                                                        Value="{Binding Path=VerticalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                                                        Visibility="{TemplateBinding ComputedVerticalScrollBarVisibility}"/>

                                    <Grid Grid.Row="2" Grid.Column="1">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=NonFrozenColumnsViewportHorizontalOffset}"/>
                                            <ColumnDefinition Width="*"/>
                                        </Grid.ColumnDefinitions>
                                        <ScrollBar Grid.Column="1"
                                                    Name="PART_HorizontalScrollBar"
                                                    Orientation="Horizontal"
                                                    Maximum="{TemplateBinding ScrollableWidth}"
                                                    ViewportSize="{TemplateBinding ViewportWidth}"
                                                    Value="{Binding Path=HorizontalOffset, RelativeSource={RelativeSource TemplatedParent}, Mode=OneWay}"
                                                    Visibility="{TemplateBinding ComputedHorizontalScrollBarVisibility}"/>
                                    </Grid>
                                </Grid>
                            </ControlTemplate>
                        </ScrollViewer.Template>
                        <ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
                    </ScrollViewer>
                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

UPDATE
It does indeed look like there is a difference between .NET 4 and .NET 4.5.
I develop on a Windows 8 machine with Visual Studio 2012, so as a test I tried targeting .NET 4 to see if I could replicate the wrong behavior. But it still worked fine.

To be sure, I tried running the app on a different machine with only .NET 4 installed, and here the empty rows showed up when making a column bigger and then smaller again.

The issue seems to be that the DataGridRows are not behaving properly. When running on a machine with only .NET 4 installed, they keep their current size when making the column smaller. On .NET 4.5 they resize as expected.

The new solution to get the behavior you need, is actually much simpler than the previous one.

By simply setting the HorizontalAlignment on the DataGridRows to left, and removing the filler column, it works on both .NET 4 and .NET 4.5. And there is no longer a need to replace the entire template of the DataGrid.

<Style TargetType="DataGridRow">
    <Setter Property="HorizontalAlignment" Value="Left" />
</Style>
<Style TargetType="DataGridColumnHeadersPresenter">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridColumnHeadersPresenter}">
                <Grid>
                    <ItemsPresenter />
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
like image 111
Peter Hansen Avatar answered Dec 11 '22 07:12

Peter Hansen


There ar plenty of layout options for Columns in WPF, its just a matter of choosing what you want to be displayed.

  • Pixel
  • SizeToCells
  • SizeToHeader
  • Auto
  • Proportional(*)

And if you set the HorizontalAlignment to Left the DataGrid will resize to fit its contents based on the ColumnWidth you picked.

Here is a example of some of the avaliable column settings

   <StackPanel>
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="100" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="SizeToCells" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="SizeToHeader" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="Auto" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
        <DataGrid HorizontalAlignment="Left"  ColumnWidth="*" Height="64" VerticalAlignment="Top" ItemsSource="{Binding ElementName=UI, Path=GridItems}" />
    </StackPanel>

enter image description here

like image 42
sa_ddam213 Avatar answered Dec 11 '22 06:12

sa_ddam213