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.

  • Have you tried with INotifyPropertyChanged on JobCostEntity and raising the PropertyChangedEvent when InvoiceDate changes? – mindandmedia Jul 09 '15 at 12:54
  • 1
    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) – 3-14159265358979323846264 Jul 09 '15 at 13:32
  • 2
    yep, i totally missed the point. i'll post a more relevant answer, yet still not exactly what you asked. – mindandmedia Jul 09 '15 at 22:09
  • 1
    Much appreciated for revisiting my question :O) – 3-14159265358979323846264 Jul 09 '15 at 22:11
  • Not a solution, but if you set `IsHitTestVisible="False"` on the `DateDoesntWork` `` 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. – eoinmullan Jul 09 '15 at 23:14
  • @ 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. – Eric Ouellet Jul 14 '15 at 14:47
  • @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. – 3-14159265358979323846264 Jul 14 '15 at 17:42
  • @eoinmullan 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. – Taekahn Jul 14 '15 at 17:42
  • @3-14159265358979323846264 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. – Taekahn Jul 14 '15 at 17:56
  • 2
    @Taekahn 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) – 3-14159265358979323846264 Jul 14 '15 at 18:03
  • @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) – 3-14159265358979323846264 Jul 14 '15 at 18:06

5 Answers5

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;
            }
        }
  • 1
    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. – 3-14159265358979323846264 Jul 09 '15 at 20:35
  • 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! – 3-14159265358979323846264 Jul 09 '15 at 20:40
  • 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. – Maxime Tremblay-Savard Jul 09 '15 at 20:45
  • 1
    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! – 3-14159265358979323846264 Jul 09 '15 at 20:53
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.

mindandmedia
  • 6,800
  • 1
  • 24
  • 33
  • 1
    Thanks for the answer! I'll have a look at this tomorrow :0) – 3-14159265358979323846264 Jul 09 '15 at 22:58
  • 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) – 3-14159265358979323846264 Jul 16 '15 at 12:54
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);
}
almulo
  • 4,918
  • 1
  • 20
  • 29
  • 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. – 3-14159265358979323846264 Jul 16 '15 at 12:46
  • 1
    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) – 3-14159265358979323846264 Jul 16 '15 at 13:07
  • Yeah, Tedy deserved the bounty better ;) Thanks for the upvotes. – almulo Jul 16 '15 at 13:24
  • 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. – almulo Jul 16 '15 at 13:25
  • 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. – almulo Jul 16 '15 at 13:34
  • 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).ContentTemplate` – almulo Jul 16 '15 at 13:51
  • Thanks for the extra info :0) I'll definitely be heading down this route. Congrats on the 2k by the way ;0) – 3-14159265358979323846264 Jul 17 '15 at 10:57
  • @3-14159265358979323846264 Thanks! – almulo Jul 17 '15 at 12:43
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));
    }
}
Tedy Pranolo
  • 1,285
  • 1
  • 14
  • 22
  • 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! – 3-14159265358979323846264 Jul 16 '15 at 12:49
  • I awarded the bounty to your good self, because your answer, though pretty much identical to @almulo was first after all. – 3-14159265358979323846264 Jul 16 '15 at 13:02
  • 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. @almulo gave a great solution to the multiple clicks required. – Tedy Pranolo Jul 21 '15 at 03:21
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
    }
}
Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119
  • 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) – 3-14159265358979323846264 Jul 16 '15 at 12:41
  • 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. – Eric Ouellet Jul 16 '15 at 17:54
  • Thanks :0). I'm definitely keen to minimise code and work!! Also ... 'not use' is best written as 'exclude' rather than 'flush'! :0) Cheers. – 3-14159265358979323846264 Jul 16 '15 at 17:57
  • 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... :-/ – Eric Ouellet Jul 16 '15 at 19:38