Search code examples
c#wpfxamldatagrid

WPF DataGrid prefill a Cell default value on edit


I have very simlistic (partof) data-bound ViewModel that exposes a Nullable property, which is later rendered in a DataGrid:

public class MyViewModel : ViewModelBase
{
   private DateTime? _date;
   public DateTime? Date {
     get => _date;
     set => Set(ref _date, value);
   }
}

<DataGrid ItemsSource="{Binding MyViewModels}">
   <DataGrid.Columns>
      <DataGridTextColumn Header="Date" Binding="{Binding Date}"/>
   </DataGrid.Columns>
</DataGrid>

Everything works as expected, Dates which has a value are displayed, cells for null values are completely blank.
However, how can I prefill bound null values with DateTime.Now when entering cell edit mode? Bonus point: the DateTime.Now is reverted back to null in case cell edit was aborted.


Attempt 2 (based on comments), using DataGridTemplateColumn and swich TextBlock without TargetNullValue for a TextBox with one, sadly that is not a valid binding:

TargetNullValue '6/25/2019 0:00:00' (type 'DateTime') cannot be converted for use in 'Text'

<DataGridTemplateColumn SortMemberPath="Date" Header="Date">
   <DataGridTemplateColumn.CellTemplate>
      <DataTemplate>
         <TextBlock Text="{Binding Date}"/>
      </DataTemplate>
   </DataGridTemplateColumn.CellTemplate>
   <DataGridTemplateColumn.CellEditingTemplate>
      <DataTemplate>
         <TextBox Text="{Binding Date, TargetNullValue={x:Static sys:DateTime.Today}}"/>
      </DataTemplate>                            
   </DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>

Solution

  • You could use the TargetNullValue property if you bind to the SelectedDate property of an invisible DatePicker:

    <DataGridTemplateColumn SortMemberPath="Date" Header="Date">
        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Date}"/>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
        <DataGridTemplateColumn.CellEditingTemplate>
            <DataTemplate>
                <StackPanel>
                    <DatePicker x:Name="dp" SelectedDate="{Binding Date, TargetNullValue={x:Static sys:DateTime.Today}}" Visibility="Collapsed" />
                    <TextBox Text="{Binding Text, ElementName=dp}" />
                </StackPanel>
            </DataTemplate>
        </DataGridTemplateColumn.CellEditingTemplate>
    </DataGridTemplateColumn>
    

    The other option would be to implement a value converter that converts DateTime.Today to a string.

    Thanks for the answer, its definitely much simpler plus it allows non-static values. However, it is not possible to accept the pre-filled DateTime.Today unless user changes it to a different date and back ...

    If you want to persist the value, you should set the source property explicitly. You could do this by handling the CellEditEnding event. Something like this:

    private void Dgm_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
    {
        ContentPresenter cp = e.EditingElement as ContentPresenter;
        if (cp != null)
        {
            Panel panel = VisualTreeHelper.GetChild(cp, 0) as Panel;
            if (panel != null)
            {
                TextBox tb = panel.Children.OfType<TextBox>().FirstOrDefault();
                if (tb != null)
                {
                    DateTime dt;
                    if (DateTime.TryParse(tb.Text, out dt))
                    {
                        e.Row.DataContext.GetType().GetProperty("Date").SetValue(e.Row.DataContext, dt);
                    }
                }
            }
        }
    }