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

5

18

I've narrowed down the problem to the following example that has a DataGrid with three columns.

XAML:

<Window x:Class="DataGridColumnTemplate_NotFiringAddingNewItem.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">
    <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">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="DateWorks">
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding InvoiceDate}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="DateDoesn'tWork">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding InvoiceDate}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding InvoiceDate}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

C#:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        List<JobCostEntity> l = new List<JobCostEntity>()
        { 
            new JobCostEntity() { Id = 0, InvoiceDate = DateTime.Now, Description = "A"},
            new JobCostEntity() { Id = 0, InvoiceDate = DateTime.Now, Description = "B"}
        };

        dg.ItemsSource = l;
    }
    private void dg_AddingNewItem(object sender, AddingNewItemEventArgs e)
    {
        MessageBox.Show("AddingNewItem");
    }
}

public partial class JobCostEntity
{
    public int Id { get; set; }
    public int JobId { get; set; }
    public Nullable<int> JobItemId { get; set; }
    public Nullable<System.DateTime> InvoiceDate { get; set; }
    public Nullable<System.DateTime> ProcessedDate { get; set; }
    public int PackageId { get; set; }
    public int DelegateId { get; set; }
    public string Description { get; set; }
    public Nullable<decimal> LabourCost { get; set; }
    public Nullable<decimal> PlantOrMaterialCost { get; set; }
    public Nullable<decimal> SubcontractorCost { get; set; }
    public Nullable<decimal> TotalCost { get; set; }
    public bool Paid { get; set; }
}

If the first column you click on in the new item row is 'DateWorks' or 'Text', then you will raise the AddingNewItem event.

If instead you click the 'DateDoesntWork' column first, you can select a date, but no new item is added until you move to one of the other columns, at which point the value in the 'DateDoesntWork' DatePicker gets cleared.

What on earth is going on?


It's arguably(!) desirable to have the DatePicker already visible to the user (hence both a CellTemplate and a CellEditingTemplate), rather than them have to click the cell to 'reveal' the control.

Is there some way I have to inform the DataGrid that my DataGridTemplateColumn Control has just set a value on a new row? If so, how so?!


EDIT:

Inspired by this post: https://social.msdn.microsoft.com/Forums/vstudio/en-US/93d66047-1469-4bed-8fc8-fa5f9bdd2166/programmatically-beginning-edit-in-datagrid-cell?forum=wpf

I have tried to hack my way around the problem by adding the following to the 'DateDoesntWork' column DatePicker, which does cause the AddingNewItem event to fire, but the selected date still doesn't get added to the underlying entity.

private void DatePicker_GotFocus(object sender, RoutedEventArgs e)
{
    if (dg.SelectedIndex == dg.Items.Count - 1)
    {
        DataGridCellInfo dgci = dg.SelectedCells[0];
        DataGridCell dgc = DataGridHelper.GetCell(dg, GetRowIndex(dg, dgci), GetColIndex(dg, dgci));
        dgc.Focus();
        dg.BeginEdit();
    }

}

It seems like the DatePicker is still trying to target the NewItemPlaceholder, if that makes any sense?!


Stranger still, if you select a date in the DateDoesntWork column on the new row, then start editing the Text column on the new row, then without entering any text, select the row above ... now another new row is added and that newly added row shows the date i selected for the row before!!!

Total. Madness.


As Maxime Tremblay-Savard has metioned, it seems like the CellTemplate is blocking the 'layer' below and stopping the AddingNewItem event firing, though the built in DataGridColumn types don't suffer from this problem.

Beaulahbeaulieu answered 28/6, 2015 at 11:16 Comment(11)
Have you tried with INotifyPropertyChanged on JobCostEntity and raising the PropertyChangedEvent when InvoiceDate changes?Manhattan
Thanks, but the problem is that the JobCostEntity property value doesn't actually get changed!! Try the code and you will see what i mean :o)Beaulahbeaulieu
yep, i totally missed the point. i'll post a more relevant answer, yet still not exactly what you asked.Manhattan
Much appreciated for revisiting my question :O)Beaulahbeaulieu
Not a solution, but if you set IsHitTestVisible="False" on the DateDoesntWork <DataGridTemplateColumn.CellTemplate> then you'll get the AddingNewItem event firing when you want and the date persists as you would expect. However you've now got a new problem which is that you have to click on the date picker three times to change the date, I'm thinking that might be an easier problem to fix though, so I though I'd add this suggestion. Maybe try setting IsHitTestVisible="True" on the fly on some mouse preview event.Emalia
@ 3-14159265358979323846. You start your question with "I've norrowed down the problem" while there is no explanation on what is your question before. That is not logical. You should ask a clear question and explain the situation which get you to the problem. I think you will improve your chances to get better answers if your ask a clear questions. I think you should inverse some text.Mucor
@EricOuellet Pretty sure the title is the question. And the 5 detailed answers I've already received seem to imply that people understand what I mean. Thanks though.Beaulahbeaulieu
@Emalia Setting IsHitTestVisible="False" should only make it require 2 clicks to open the date picker. I just tested to confirm this. The standard controls, such as checkbox also require two clicks, so this would be in line with the anticipated behavior of anyone using the control. IMO, your solution is the correct one if we are comparing the functionality of this template column to the built in controls.Overblouse
@Beaulahbeaulieu My two cents would probably be to not rely on autogeneration of new rows, at least not when you are using template columns. Its clearly Jenky. I don't know if for you that means subclassing the datagrid to directly control when a new row is being added (probably more work) or simply adding a button in the UI to add a new row. Or maybe some other option. I can say that #2 is the way we handle it at my company, though in all honesty some customers have said they wish it was "more like excel", most people don't seem to have a problem with clicking the button.Overblouse
@Overblouse agreed ... the add row button is always an option but it would be nice to make things work as the user expects them to! It would be nice if they worked as the programmer expects them to at the very least, hey?! :0)Beaulahbeaulieu
@EricOuellet ... just noticed one of the answers is from you! I really don't understand how you can say my question is unclear, and provide me with an answer! That's not to say I don't appreciate your efforts, I'm just a bit confused :0)Beaulahbeaulieu
M
2

If you use a control that handles mouse click within CellTemplate, the DataGrid never receive click event that triggers it to switch to Edit mode. So like eoinmullan mentioned, the solution is to set the control IsHitTestVisible=False. Below is the working code. I added INotifyPropertyChanged so we can actually see the changed value reflected in the UI. I also added red background for DateDoesn'tWork CellTemplate, so you can see when the DataGrid goes from display mode to edit mode.

<Window x:Class="WpfApplication1.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">
<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">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="DateWorks"  >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding InvoiceDate}"/>

                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <DatePicker SelectedDate="{Binding InvoiceDate}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
            <DataGridTemplateColumn Header="DateDoesn'tWork">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate >
                        <!--Differentiate visually between CellTemplate and CellEditTemplate by using red background-->
                        <DatePicker Background="Red" IsHitTestVisible="False" SelectedDate="{Binding InvoiceDate}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <DatePicker SelectedDate="{Binding InvoiceDate}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        List<JobCostEntity> l = new List<JobCostEntity>()
        {
            new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "A"},
            new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "B"}
        };

        dg.ItemsSource = l;
    }

    private void dg_AddingNewItem(object sender, AddingNewItemEventArgs e)
    {
        //MessageBox.Show("AddingNewItem");
    }
}

public partial class JobCostEntity : INotifyPropertyChanged
{
    private string _description;
    private DateTime? _invoiceDate;

    public int Id { get; set; }
    public int JobId { get; set; }
    public Nullable<int> JobItemId { get; set; }

    public Nullable<System.DateTime> InvoiceDate
    {
        get { return _invoiceDate; }
        set
        {
            if (value.Equals(_invoiceDate)) return;
            _invoiceDate = value;
            OnPropertyChanged();
        }
    }

    public Nullable<System.DateTime> ProcessedDate { get; set; }
    public int PackageId { get; set; }
    public int DelegateId { get; set; }

    public string Description
    {
        get { return _description; }
        set
        {
            if (value == _description) return;
            _description = value;
            OnPropertyChanged();
        }
    }

    public Nullable<decimal> LabourCost { get; set; }
    public Nullable<decimal> PlantOrMaterialCost { get; set; }
    public Nullable<decimal> SubcontractorCost { get; set; }
    public Nullable<decimal> TotalCost { get; set; }
    public bool Paid { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;


    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
Mummery answered 11/7, 2015 at 10:2 Comment(3)
Thanks for your help. This definitely solves the issue with AddingNewItem not firing when the first cell i edit in the new item row is a date! As i mentioned in a response to another answer, i do have to click the cell 3 times to edit a date, but this is definitely a minor issue in comparison. Cheers!Beaulahbeaulieu
I awarded the bounty to your good self, because your answer, though pretty much identical to @Bucksaw was first after all.Beaulahbeaulieu
Thanks. If anyone deserves the bounty it's probably eoinmullan (Whose comment has been removed). But I'll take it :). I was trying to highlight the switch between CellTemplate and CellEditingTemplate. Since the templates were identical it can be hard to see. @Bucksaw gave a great solution to the multiple clicks required.Mummery
S
7

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;
            }
        }
Sumter answered 9/7, 2015 at 20:29 Comment(4)
Thanks for your detailed answer ... nice to see I'm not the only one experiencing this! The problem I have is that i can't use the margin solution you've offered because my users (quite rightly imo) expect to be able to start editing the value without clicking in a 'magic' part of the cell first :O). Having said that, I really do appreciate your input. Thanks.Beaulahbeaulieu
I think a possible solution might lie in raising the AddingNewItem event manually, and then updating the relevant value, but I am unsure how/unwilling to do this (unless it's a last resort) as it seems like a massive hack! I'm surprised i couldn't find the 'same' question being asked elsewhere!Beaulahbeaulieu
I was also thinking about manually adding the new item, that's what i was currently writting but you beat me to it. I'll edit my answer to add how to do it.Sumter
Thanks again for the update. I reckon that should work, but I would like to find out if anyone else has any other methods for getting the CellTemplate to fire the AddingNewItem event ... because let's face it ... manually raising the event isn't necessary with DataGridCheckBoxColumn so there must be some magic going on in the background!Beaulahbeaulieu
M
7

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.

Manhattan answered 9/7, 2015 at 22:25 Comment(2)
Thanks for the answer! I'll have a look at this tomorrow :0)Beaulahbeaulieu
Thanks for your help. I've finally had chance to try this out but it seems to suffer from the same issue where i select a date on the new item row, and then when i start editing another cell, the date disappears and the AddingNewItem event only fires after the date has been cleared. I do like the idea of creating a custom column type ... certainly i will probably need to do that to get all the functionality i need!! :0)Beaulahbeaulieu
B
3

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);
}
Bucksaw answered 13/7, 2015 at 11:2 Comment(7)
Thanks! I can see this works ... it certainly solves the problem of the AddingNewItem event not firing when editing a date for the first time on the new item row. It would be nice if I (my fussy users) didn't have to click the cell three times before they can select a date, but maybe I can create some sort of hybrid approach using some of the other answers.Beaulahbeaulieu
I've awarded the bounty to @Tedy because his answer arrived before yours, even though they are basically the same. I can see that some other questions you have answered before are very useful for me, so i will defnitely upvote them as they help me with my other problems :0)Beaulahbeaulieu
Yeah, Tedy deserved the bounty better ;) Thanks for the upvotes.Bucksaw
Also, don't worry about the three-clicks-to-edit problem, I got you covered! Had to struggle with this some time ago, I'll edit my answer adding a little piece of code that helped me back then.Bucksaw
Done, check points 3 and 4 and the additional code. The first click is the one that selects the cell and changes the CurrentCell of the DataGrid; the second one puts the cell in editing mode; and the third one puts the focus on the control. Handling the right events allows you to chain all those actions so they happen automatically after the first click.Bucksaw
I ended up creating my own DataGrid, and handling all those events on it so I didn't have to add this code to each view. If you'd like to do this, then point 4 is not so straight-forward and you'll have to override OnPreparingCellForEdit and look for IInputElements inside (e.EditingElement as ContentPresenter).ContentTemplateBucksaw
Thanks for the extra info :0) I'll definitely be heading down this route. Congrats on the 2k by the way ;0)Beaulahbeaulieu
M
2

If you use a control that handles mouse click within CellTemplate, the DataGrid never receive click event that triggers it to switch to Edit mode. So like eoinmullan mentioned, the solution is to set the control IsHitTestVisible=False. Below is the working code. I added INotifyPropertyChanged so we can actually see the changed value reflected in the UI. I also added red background for DateDoesn'tWork CellTemplate, so you can see when the DataGrid goes from display mode to edit mode.

<Window x:Class="WpfApplication1.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">
<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">
        <DataGrid.Columns>
            <DataGridTemplateColumn Header="DateWorks"  >
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding InvoiceDate}"/>

                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <DatePicker SelectedDate="{Binding InvoiceDate}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
            <DataGridTemplateColumn Header="DateDoesn'tWork">
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate >
                        <!--Differentiate visually between CellTemplate and CellEditTemplate by using red background-->
                        <DatePicker Background="Red" IsHitTestVisible="False" SelectedDate="{Binding InvoiceDate}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
                <DataGridTemplateColumn.CellEditingTemplate>
                    <DataTemplate>
                        <DatePicker SelectedDate="{Binding InvoiceDate}" />
                    </DataTemplate>
                </DataGridTemplateColumn.CellEditingTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

 public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        List<JobCostEntity> l = new List<JobCostEntity>()
        {
            new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "A"},
            new JobCostEntity() {Id = 0, InvoiceDate = DateTime.Now, Description = "B"}
        };

        dg.ItemsSource = l;
    }

    private void dg_AddingNewItem(object sender, AddingNewItemEventArgs e)
    {
        //MessageBox.Show("AddingNewItem");
    }
}

public partial class JobCostEntity : INotifyPropertyChanged
{
    private string _description;
    private DateTime? _invoiceDate;

    public int Id { get; set; }
    public int JobId { get; set; }
    public Nullable<int> JobItemId { get; set; }

    public Nullable<System.DateTime> InvoiceDate
    {
        get { return _invoiceDate; }
        set
        {
            if (value.Equals(_invoiceDate)) return;
            _invoiceDate = value;
            OnPropertyChanged();
        }
    }

    public Nullable<System.DateTime> ProcessedDate { get; set; }
    public int PackageId { get; set; }
    public int DelegateId { get; set; }

    public string Description
    {
        get { return _description; }
        set
        {
            if (value == _description) return;
            _description = value;
            OnPropertyChanged();
        }
    }

    public Nullable<decimal> LabourCost { get; set; }
    public Nullable<decimal> PlantOrMaterialCost { get; set; }
    public Nullable<decimal> SubcontractorCost { get; set; }
    public Nullable<decimal> TotalCost { get; set; }
    public bool Paid { get; set; }

    public event PropertyChangedEventHandler PropertyChanged;


    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}
Mummery answered 11/7, 2015 at 10:2 Comment(3)
Thanks for your help. This definitely solves the issue with AddingNewItem not firing when the first cell i edit in the new item row is a date! As i mentioned in a response to another answer, i do have to click the cell 3 times to edit a date, but this is definitely a minor issue in comparison. Cheers!Beaulahbeaulieu
I awarded the bounty to your good self, because your answer, though pretty much identical to @Bucksaw was first after all.Beaulahbeaulieu
Thanks. If anyone deserves the bounty it's probably eoinmullan (Whose comment has been removed). But I'll take it :). I was trying to highlight the switch between CellTemplate and CellEditingTemplate. Since the templates were identical it can be hard to see. @Bucksaw gave a great solution to the multiple clicks required.Mummery
M
0

First part of the code is only to show the date in 'Working column". To fix the click twice to edit, then you can use the helper class.

Hope it helps...

<Window x:Class="WpfApplicationAnswerForStackOverflow.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">
    <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">
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="DateWorks">

                    <!-- Here -->
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding InvoiceDate, StringFormat='d'}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>

                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding InvoiceDate}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="DateDoesn'tWork">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding InvoiceDate}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                    <DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                            <DatePicker SelectedDate="{Binding InvoiceDate}" />
                        </DataTemplate>
                    </DataGridTemplateColumn.CellEditingTemplate>
                </DataGridTemplateColumn>
                <DataGridTextColumn Header="Text" Binding="{Binding Description}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>

Fix for single click edit:

Usage:

<Window ...
        xmlns:WpfUtil="clr-namespace:HQ.Util.Wpf.WpfUtil;assembly=WpfUtil">

<DataGrid ... util:DataGridCellHelper.IsSingleClickInCell="True">

Class

using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;

namespace HQ.Wpf.Util
{
    public static class DataGridCellHelper
    {
        #region IsSingleClickInCell
        public static readonly DependencyProperty IsSingleClickInCellProperty =
            DependencyProperty.RegisterAttached("IsSingleClickInCell", typeof(bool), typeof(DataGrid), new FrameworkPropertyMetadata(false, OnIsSingleClickInCellSet)); public static void SetIsSingleClickInCell(UIElement element, bool value) { element.SetValue(IsSingleClickInCellProperty, value); }

        public static bool GetIsSingleClickInCell(UIElement element)
        {
            return (bool)element.GetValue(IsSingleClickInCellProperty);
        }

        private static void OnIsSingleClickInCellSet(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            if (!(bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
            {
                if ((bool)e.NewValue)
                {
                    var dataGrid = sender as DataGrid;
                    Debug.Assert(dataGrid != null);
                    EventManager.RegisterClassHandler(typeof(DataGridCell),
                        DataGridCell.PreviewMouseLeftButtonUpEvent,
                        new RoutedEventHandler(OnPreviewMouseLeftButtonDown));
                }
            }
        }

        private static void OnPreviewMouseLeftButtonDown(object sender, RoutedEventArgs e)
        {
            DataGridCell cell = sender as DataGridCell;
            if (cell != null && !cell.IsEditing && !cell.IsReadOnly)
            {
                var checkBoxes = ControlHelper.FindVisualChildren<CheckBox>(cell);
                if (checkBoxes != null && checkBoxes.Count() > 0)
                {
                    foreach (var checkBox in checkBoxes)
                    {
                        if (checkBox.IsEnabled)
                        {
                            checkBox.Focus();
                            checkBox.IsChecked = !checkBox.IsChecked;
                            var bindingExpression = checkBox.GetBindingExpression(CheckBox.IsCheckedProperty); if (bindingExpression != null) { bindingExpression.UpdateSource(); }
                        }
                        break;
                    }
                }
            }
        }
        #endregion
    }
}
Mucor answered 14/7, 2015 at 14:56 Comment(4)
Thanks for your help. I've tried the example you posted. Unfortunately, if I select a date in DateDoesn'tWork and then start editing the next cell, the date i selected still disappears! Can you confirm this, or have I made a mistake when I copied your code over? Thanks :0)Beaulahbeaulieu
I will give a try to the full sample. But in the meantime, I suggested to flush (not use) column "DateDoen'tWork". Use DateTime into column of type "DateDoesWork" as suggested (if it fits your requirements). I think that if you do as suggested, you will stay closer to the expected usage from Microsoft and then will have less code to do and maintain.Mucor
Thanks :0). I'm definitely keen to minimise code and work!! Also ... 'not use' is best written as 'exclude' rather than 'flush'! :0) Cheers.Beaulahbeaulieu
You are right. My code works only for boolean column with checkbox. I did n't answer the question properly. Sorry. The choosen answer seams to be the right one. I should have verified my answer before posting... :-/Mucor

© 2022 - 2024 — McMap. All rights reserved.