31
<DataGridTextColumn Binding="{Binding Name}" Width="*"/>
<DataGridTextColumn Binding="{Binding Change}" Width="Auto"/>

When the value of Change updates, its column doesn't update to fit the new value. So the column stays too small and the value is clipped.
Any ideas?

Mong Zhu
  • 23,309
  • 10
  • 44
  • 76
user626528
  • 13,999
  • 30
  • 78
  • 146
  • The column is not going to extend forever until you define that in the style. – sajoshi Apr 05 '11 at 08:32
  • @sajoshi, how to define? – user626528 Apr 05 '11 at 08:41
  • If you change the Width to anything but `Auto`, the problem will go away, but if you need it, Scott's answer below is the right way to go! I looped over all auto columns `dataGrid.Columns.Where(c => c.Width.IsAuto)` and did what Scott did. – Shahin Dohan Dec 13 '18 at 15:07

5 Answers5

71

The DataGrid will increase column sizes to fit as the data becomes longer, but it does not automatically decrease column sizes when the length of the data decreases. In your example, you're right aligning the 'Change' column, and using the rest of the space for the 'Name' column.

Now, when a 'Change' property grows large enough that it should increase the column's width, the 'Name' column is refusing to shrink to accommodate, so you have to force a refresh yourself.

The following steps should do this for you (I've included a sample app to demo):

1) In your DataGridTextColumn Bindings (all except your * sized column) set NotifyTargetUpdated=True.
2) On your DataGrid, add a handler to the TargetUpdated event.
3) In your TargetUpdated event handler:
-- a) Set the DataGrid's * sized column's width to 0.
-- b) Call the UpdateLayout() method on the DataGrid.
-- c) Set the DataGrid's * sized column's width back to new DataGridLength(1, DataGridLengthUnitType.Star)

Example XAML:

<Window x:Class="DataGridTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <CollectionViewSource x:Key="MyObjectCollection" />
    </Window.Resources>
    <DockPanel>
        <Button DockPanel.Dock="Bottom" Content="Click to Make Item 1s Text Longer" Click="Button_Click" />
        <Grid>
            <DataGrid x:Name="dg" ItemsSource="{Binding Source={StaticResource MyObjectCollection}}" AutoGenerateColumns="False" TargetUpdated="dg_TargetUpdated">
                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding First}" Width="1*"/>
                    <DataGridTextColumn Binding="{Binding Last, NotifyOnTargetUpdated=True}"  Width="Auto" />
                </DataGrid.Columns>
            </DataGrid>
        </Grid>

    </DockPanel>
</Window>

Example Code Behind:

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.ComponentModel;

namespace DataGridTest
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private ObservableCollection<MyObject> myObjectList = new ObservableCollection<MyObject>();

        public MainWindow()
        {
            InitializeComponent();
            (this.FindResource("MyObjectCollection") as CollectionViewSource).Source = this.myObjectList;
            this.myObjectList.Add(new MyObject() { First = "Bob", Last = "Jones" });
            this.myObjectList.Add(new MyObject() { First = "Jane", Last = "Doe" });
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            this.myObjectList[0].Last = "BillyOBrian";
        }

        private void dg_TargetUpdated(object sender, DataTransferEventArgs e)
        {
            dg.Columns[0].Width = 0;
            dg.UpdateLayout();
            dg.Columns[0].Width = new DataGridLength(1, DataGridLengthUnitType.Star);
        }
    }

    public class MyObject : INotifyPropertyChanged
    {
        private string firstName;
        public string First
        {
            get { return this.firstName; }
            set
            {
                if (this.firstName != value)
                {
                    this.firstName = value;
                    NotifyPropertyChanged("First");
                }
            }
        }

        private string lastName;
        public string Last
        {
            get { return this.lastName; }
            set
            {
                if (this.lastName != value)
                {
                    this.lastName = value;
                    NotifyPropertyChanged("Last");
                }
            }
        }

        public MyObject() { }

        #region -- INotifyPropertyChanged Contract --

        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }

        #endregion INotifyPropertyChanged Contract
    }
}
Scott
  • 11,840
  • 6
  • 47
  • 54
  • You are a good man, Charlie Brown. This was the last little detail I hadn't figured out about how to make my WPF DataGrid beautiful! – Will Rogers Jun 29 '11 at 14:14
  • I implemented this, and it seemed to work when the columns are first shown. Although, when I click the datagrid, it will set the column to a very small size, even though the data in it is longer. – Logan B. Lehman May 15 '13 at 20:40
  • This worked for me, however if I had ScrollViewer.CanContentScroll="True" (aka virtualization), the program would crash with an exception stating "Cannot call StartAt when content generation is in progress." The STR was: 1: Populate the datagrid with a lot of rows 2: Select a row towards the bottom. 3. Press the up arrow key and hold until the exception is triggered – Matt Becker May 20 '14 at 21:05
  • 16
    Does anybody get the feeling that Microsoft dropped WPF on us half implemented and only by the group effort like this (thanks Scott and others) can we get it working? Sorry, I'm just venting :) – ebol2000 Feb 11 '15 at 20:17
  • 2
    I used `dg.Columns[0].Width = new DataGridLength(1, DataGridLengthUnitType.Auto);` with success. – Gerard Jan 19 '16 at 15:42
  • @ebol2000 increasingly more so. I'm almost half-tempted to just implement my own DataGrid at this point. –  Oct 30 '16 at 19:50
  • Really nice. Thanks a lot for the deatiled description and code examples. – AH. Nov 01 '19 at 07:48
  • This will sound crazy, but I just found another way that is working reliably on my latest datagrid. After I update the bound data, I use a "DispatcherTimer" at "DispatcherPriority.ApplicationIdle" to fire 25ms after it is idle and I take the first visible column and toggle its "Visibility" from "Collapsed" and then back to "Visible". – TonyM May 14 '21 at 14:28
  • Rrreally big – Egor Novikov Nov 29 '22 at 12:43
2

i have had similar problem with my listview, the solution i found on how-to-autosize-and-right-align-gridviewcolumn-data-in-wpf here on stackoverflow.

In my case it was adding this piece of code into collectionchanged event handler of the observable collection the list view was bound to:

void listview_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) {
        // this is a listview control
        GridView view = this.View as GridView;
        foreach(GridViewColumn c in view.Columns) {
            if(double.IsNaN(c.Width)) {
                c.Width = c.ActualWidth;
            }
            c.Width = double.NaN;
        }
    }

It works for me, although sometimes the user can notice "blink" over the columns.

Community
  • 1
  • 1
Zee
  • 901
  • 10
  • 21
1

WPF will just resize a datagrid's column width set to Auto if needed, i.e: the content cannot be displayed entirely. So when the content's width shrinks, the column does not resize as the content can still been seen entirely.

the only way I can see to force wpf to recalculate the columns' widths would be to force them all to 0 and then back to auto in the code behind, with one or two updateLayout() thrown in, but this is not very nice programming :-/

basically, in your code behind:

foreach (DataGridColumn c in dg.Columns)
    c.Width = 0;

// Update your DG's source here

foreach (DataGridColumn c in dg.Columns)
    c.Width = DataGridLength.Auto;

and you probably need a dg.UpdateLayout() or two somewhere in there (after the update and the setting back to auto probably)

Anand Thangappan
  • 3,068
  • 2
  • 28
  • 31
0

One way you could solve this is by defining the width property of the column in a style setting and binding that setting to a property of the object you are binding to.

<DataGridTextColumn Binding="{Binding Change}" ElementStyle="{StaticResource ChangeColumnStyle}"/>

In your ResourceDictionary:

<Style TargetType="{x:Type DataGridTextColumn }" x:Key="ChangeColumnStyle">
   <Setter Property="Width" Value="{Binding ColumnWidth}"
</Style>

ColumnWidth should be a property of your object. Now if you update this property from the setter of your 'Change' property (by using some self-defined algorithm, taking stuff like font into account), and calling:

RaisePropertyChanged("ColumnWidth");

It should update your column width.

 public int Change
   {
      get { return m_change; }
      set
      {
         if (m_change != value)
         {
            m_change = value;
            ColumnWidth = WidthAlgo(numberOfCharacters);
            RaisePropertyChanged("Change");
            RaisePropertyChanged("ColumnWidth");
         }
      }
   }
Edwin de Koning
  • 14,209
  • 7
  • 56
  • 74
  • 1
    But this will not resolve issue because calculating ColumnWidth is complicated as it is based on the text style applied. – Akash Kava Apr 12 '11 at 07:23
-2

Have you tried this?

<DataGridTextColumn Binding="{Binding Path= Id}"  Header="ID" IsReadOnly="True" Width="1*" />
yoozer8
  • 7,361
  • 7
  • 58
  • 93
CPM
  • 855
  • 3
  • 12
  • 14