DataGridTemplateColumn (ComboBox, DatePicker) Resets/Clears and doesn't fire AddingNewItem DataGridTemplateColumn (ComboBox, DatePicker) Resets/Clears and doesn't fire AddingNewItem wpf wpf

DataGridTemplateColumn (ComboBox, DatePicker) Resets/Clears and doesn't fire AddingNewItem


In case you need a solution for your InvoiceDate, here is a way to have the behaviour you describe for DateWorks by creating a DataGridDateColumn like so:

public class DataGridDateColumn : DataGridBoundColumn{    public string DateFormatString { get; set; }    protected override void CancelCellEdit(FrameworkElement editingElement, object before)    {        var picker = editingElement as DatePicker;        if (picker != null)        {            picker.SelectedDate = DateTime.Parse(before.ToString());        }    }    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)    {        var element = new DatePicker();        var binding = new Binding(((Binding)this.Binding).Path.Path) {Source = dataItem};        if (DateFormatString != null)        {            binding.Converter = new DateTimeConverter();            binding.ConverterParameter = DateFormatString;        }        element.SetBinding(DatePicker.SelectedDateProperty, this.Binding);        return element;    }    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)    {        var element = new TextBlock();        var b = new Binding(((Binding) Binding).Path.Path) {Source = dataItem};        if (DateFormatString != null)        {            b.Converter = new DateTimeConverter();            b.ConverterParameter = DateFormatString;        }        element.SetBinding(TextBlock.TextProperty, b);        return element;    }    protected override object PrepareCellForEdit(FrameworkElement editingElement, RoutedEventArgs editingEventArgs)    {        var element = editingElement as DatePicker;        if (element != null)        {            if (element.SelectedDate.HasValue ) return element.SelectedDate.Value;        }        return DateTime.Now;    }}public class DateTimeConverter : IValueConverter{    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)    {        var date = (DateTime)value;        return date.ToString(parameter.ToString());    }    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)    {        DateTime resultDateTime;        if (DateTime.TryParse(value.ToString(), out resultDateTime))        {            return resultDateTime;        }        return value;    }}

I then added two more columns to your grid:

<custom:DataGridDateColumn Header="Custom" Binding="{Binding InvoiceDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>  <DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>

If I click into the Custom field now, I get the Message Box, select a date and then tab out, the value gets cleared until I implement INPC on InvoiceDate:

    private Nullable<System.DateTime> _invoiceDate;    public Nullable<System.DateTime> InvoiceDate    {        get { return _invoiceDate; }        set        {            _invoiceDate = value;            OnPropertyChanged();        }    }    public event PropertyChangedEventHandler PropertyChanged;    [NotifyPropertyChangedInvocator]    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)    {        var handler = PropertyChanged;        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));    }

Now, the date is showing according to the DateFormatString set.

Again, I am aware that does not answer your original question, but after my hasty comment from before, I felt obliged to at least come up with a specific workaround.


My take on the issue. The issue you're having with your second column is with the DataGridTemplateColumn. The DataGridTemplateColumn is the actual column, so it's where you should click to add a new line, when you put a control in a DataTemplate in the DataGridCTemplateColumn.CellTemplate, it becomes a "layer" above it. The controls in this "upper layer" are then usable without actually clicking on the Row, which means it does not create a new line.


I did some testing to prove this, if you create a checkbox column this way:

<DataGridCheckBoxColumn Header="Paid" Binding="{Binding Paid}"/>

If you click on the checkbox, it triggers the event to add a new line because this is the actual column, not a control over it.

But if you do the same but with the DataGridTemplateColumn, like this:

<DataGridTemplateColumn>    <DataGridTemplateColumn.CellTemplate>        <DataTemplate>            <CheckBox Content="Paid" IsChecked="{Binding Paid}" Margin="5"></CheckBox>        </DataTemplate>    </DataGridTemplateColumn.CellTemplate></DataGridTemplateColumn>

Note the margin, to be able to click on the actual cell and not on the control above the cell

With this way, if you click on the cell itself, it will trigger the add a new line event, while if you click on the checkbox that is "above" the cell, it will not trigger the event and will only check/uncheck it.


There is also a remark on the msdn documentation that might help you understand also:

The DataGridTemplateColumn type enables you to create your own column types by specifying the cell templates used to display values and enable editing. Set the CellTemplate property to specify the contents of cells that display values, but do not allow editing. Set the CellEditingTemplate property to specify the contents of cells in editing mode. If you set the column IsReadOnly property to true, the CellEditingTemplate property value is never used.

I hope this gives you a better insight on what's going on with your DataGrid

EDIT

Something like this would permit you to manually add the line when you click "Enter" after selectionning your date.

private void DatePicker_KeyUp(object sender, KeyEventArgs e)        {            if (e.Key == Key.Enter)            {                List<JobCostEntity> tempList = (List<JobCostEntity>)dg.ItemsSource;                tempList.Add(new JobCostEntity() { InvoiceDate = ((DatePicker)sender).DisplayDate });                dg.ItemsSource = tempList;            }        }


EDIT - Added code to make one-click editing possible.

  1. Changed all column bindings with UpdateSourceTrigger=PropertyChanged - This is because the default value of LostFocus works at a row level, not cell level, which means that you have to leave the row completely before the bindings take effect. This works ok for many situations, but not when you have two columns bound to the same property, because the changes done to one of those columns won't show inmediately in the other column.
  2. Set IsHitTestVisible="False" to the non-editing template of the central column - My first approach was to make the column read-only and use only the CellTemplate... But this didn't trigger the AddingNewItem event. It seems you NEED to change from the regular cell to the editing cell for that event to fire, but since your non-editing template is not what you want the user to interact with, disabling hit testing makes all sense. That way you force the user to change to edit mode, hence triggering the event, before being able to enter input.
  3. Handled the CurrentCellChanged event of the DataGrid. In the handler, use the methods CommitEdit() to make sure the previously selected cell leaves editing mode, and an asynchronous call to BeginEdit() to start editing the current cell right away, without having to wait for a second click.
  4. Handled the Loaded event of the DatePickers inside the CellEditingTemplates. In the handler, used Keyboard.Focus() to give focus to the DatePicker as soon as it is loaded, saving the user the need to click a third time to put the focus on the control.

XAML:

<Grid>    <DataGrid x:Name="dg" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Height="299" AutoGenerateColumns="False" Width="497" AddingNewItem="dg_AddingNewItem" CanUserAddRows="True"              CurrentCellChanged="dg_CurrentCellChanged">        <DataGrid.Columns>            <DataGridTemplateColumn Header="DateWorks">                <DataGridTemplateColumn.CellEditingTemplate>                    <DataTemplate>                        <DatePicker Loaded="DatePicker_Loaded"                                     SelectedDate="{Binding InvoiceDate,                                                           UpdateSourceTrigger=PropertyChanged}" />                    </DataTemplate>                </DataGridTemplateColumn.CellEditingTemplate>            </DataGridTemplateColumn>            <DataGridTemplateColumn Header="DateDoesn'tWork">                <DataGridTemplateColumn.CellTemplate>                    <DataTemplate>                        <DatePicker IsHitTestVisible="False"                                     SelectedDate="{Binding InvoiceDate,                                                           UpdateSourceTrigger=PropertyChanged}" />                    </DataTemplate>                </DataGridTemplateColumn.CellTemplate>                <DataGridTemplateColumn.CellEditingTemplate>                    <DataTemplate>                        <DatePicker Loaded="DatePicker_Loaded"                                     SelectedDate="{Binding InvoiceDate,                                                            UpdateSourceTrigger=PropertyChanged}" />                    </DataTemplate>                </DataGridTemplateColumn.CellEditingTemplate>            </DataGridTemplateColumn>            <DataGridTextColumn Header="Text" Binding="{Binding Description,                                                                 UpdateSourceTrigger=PropertyChanged}"/>        </DataGrid.Columns>    </DataGrid></Grid>

Code-behind:

private void dg_CurrentCellChanged(object sender, EventArgs e){    var dataGrid = sender as DataGrid;    dataGrid.CommitEdit();    Dispatcher.BeginInvoke(new Action(() => dataGrid.BeginEdit()), System.Windows.Threading.DispatcherPriority.Loaded);}private void DatePicker_Loaded(object sender, RoutedEventArgs e){    Keyboard.Focus(sender as DatePicker);}