Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I set the width of a DataGridColumn to fit contents ("Auto"), but completely fill the available space for the DataGrid in MVVM?

I have a WPF DataGrid that contains some data. I would like to set the width of the columns such that the content fits in and never gets cropped (instead, a horizontal scroll bar should become visible). Additionally, I want the DataGrid to fill the whole place available (I am working with a DockPanel). I am using the following code (simplified):

<DataGrid ItemsSource="{Binding Table}">
    <DataGrid.Columns>
        <DataGridTextColumn MinWidth="100" Width="Auto" Header="Column 1" Binding="{Binding Property1}" />
        <DataGridTextColumn MinWidth="200" Width="Auto" Header="Column 2" Binding="{Binding Property2}" />
    </DataGrid.Columns>
</DataGrid>

This apparently does not work out of the box with Width="Auto" as it always looks something like this:

DataGrid: only the first part of the row is marked as "selected"

This obviously looks ugly. I would like to have the whole row selected, or, which would be much better, the columns to fill the whole width, but as one can see, this does not work.

If I use Width="*" instead, the content of the columns gets cropped which is even worse for me.

I found a similar question here, and a workaround was posted there. This may work, but I am working with the MVVM pattern, so the ItemsSource gets updated in the ViewModel and I cannot think about a way of doing it from there, because I cannot access the ActualWidth property of the DataGridColumn. Also, I would like to do it only in XAML if possible.

I would appreciate any help. Thanks!

Edit: As I still don't have a clue what to do about it, I start a small bounty. I would be very happy about a suggestion what one could do about my problem. Thanks again!

Edit 2: After saus' answer I thought about the options again. The problem is that I need to update the Width and the MinWidth properties also during the application is running, so not only after loading the window. I already tried to do something like

column.Width = new DataGridLength(1, DataGridLengthUnitType.Auto);
column.MinWidth = column.ActualWidth;
column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);

in some event that is fired when the underlying ItemsSource of the DataGrid is updating. However, this does not work, as the ActualWidth property does not seem to change after setting the Width on Auto. Is there an option to somehow "repaint" it in order to get the ActualWidth property updated? Thanks!

like image 678
Sören Avatar asked Feb 14 '11 21:02

Sören


3 Answers

I would suggest using the workaround that you linked to. It does work. In the codebehind of your view, add the following to the constructor after InitializeComponent():

Griddy.Loaded += SetMinWidths; // I named my datagrid Griddy

and define SetMinWidths as:

public void SetMinWidths(object source, EventArgs e )
        {
            foreach (var column in Griddy.Columns)
            {
                column.MinWidth = column.ActualWidth;
                column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
            }
        }

I'm assuming that the reason you don't want to use this solution is that you believe that MVVM prohibits code in the codebehind. But since this is entirely view specific logic I believe that it is justified in this case. The whole "MVVM prohibits code in the codebehind" thing is a bit of a misconception.

But if you are bound by style guides, or you want this logic to be available for all datagrids in your app, you can make an attached behaviour that does the job like this:

public class SetMinWidthToAutoAttachedBehaviour 
    {
        public static bool GetSetMinWidthToAuto(DependencyObject obj)
        {
            return (bool)obj.GetValue(SetMinWidthToAutoProperty);
        }

        public static void SetSetMinWidthToAuto(DependencyObject obj, bool value)
        {
            obj.SetValue(SetMinWidthToAutoProperty, value);
        }

        // Using a DependencyProperty as the backing store for SetMinWidthToAuto.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SetMinWidthToAutoProperty =
            DependencyProperty.RegisterAttached("SetMinWidthToAuto", typeof(bool), typeof(SetMinWidthToAutoAttachedBehaviour), new UIPropertyMetadata(false, WireUpLoadedEvent));

        public static void WireUpLoadedEvent(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var grid = (DataGrid)d;

            var doIt = (bool)e.NewValue;

            if (doIt)
            {
                grid.Loaded += SetMinWidths;
            }
        }

        public static void SetMinWidths(object source, EventArgs e)
        {
            var grid = (DataGrid)source;

            foreach (var column in grid.Columns)
            {
                column.MinWidth = column.ActualWidth;
                column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
            }
        }
    }

and then for any datagrid that you want to apply this behaviour to, add the following:

<DataGrid ItemsSource="{Binding Table}" local:SetMinWidthToAutoAttachedBehaviour.SetMinWidthToAuto="true">

And then your conscience, as well as your codebehind, will be clear.

like image 188
saus Avatar answered Nov 01 '22 02:11

saus


Use a TextBlock with text wrapping inside template column:

<DataGrid ItemsSource="{Binding Table}">      
    <DataGrid.Columns>          
        <DataGridTextColumn MinWidth="100" Width="Auto" Header="Column 1"
                            Binding="{Binding Property1}" />          
        <DataGridTemplateColumn Width="*" Header="Column 2">
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding Property2}" TextWrapping="Wrap" />  
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>
    </DataGrid.Columns>  
</DataGrid>
like image 4
Rafa Castaneda Avatar answered Nov 01 '22 01:11

Rafa Castaneda


how about this:

for your columns' MinWidth, you setup a binding to the dataGrid's ScrollContent's Width, with a converter that divide this width by the number of columns

you need some code behind for the converter, but this would keep your MVVM structure intact.

might be a long shot though, did not have the time to try it.

Edit: I put some more thought into this and there a pb: it will not work well if you have, say, one huge column and a few small ones. I assumed that all cols are the same width, which is obviously not the case here.

You might want to explore this way though. Using bindings on the Widths with converters is about the only thing I can think of that could work: since you basically have 2 conditions to take into account when calculating a column's width, there will be no easy way to do this.

like image 1
David Avatar answered Nov 01 '22 01:11

David