1

I have a DataGrid which looks like:

<DataGrid Grid.Row="3" Grid.Column="1" ItemsSource="{Binding Purchases}" SelectionMode="Single" SelectionUnit="FullRow"
          SelectedItem="{Binding SelectedPurchase, Source={x:Static ex:ServiceLocator.Instance}}" 
          AutoGenerateColumns="False" CanUserAddRows="False">

    <e:Interaction.Triggers>
        <e:EventTrigger EventName="CellEditEnding">
            <e:InvokeCommandAction Command="{Binding DataContext.CellEditEndingCommand, 
                                                     RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Page}}}"/>
        </e:EventTrigger>
    </e:Interaction.Triggers>

    <DataGrid.Columns>
        .......
        ........
    <DataGrid.Columns>

</DataGrid>

Property SelectedPurchase looks like:

private Purchase _selectedPurchase;
public Purchase SelectedPurchase
{
    get
    {
        return _selectedPurchase;
    }
    set
    {
        _selectedPurchase = value;
        NotifyPropertyChanged("SelectedPurchase");
    }
}

CellEditEndingCommand

public ICommand CellEditEndingCommand { get; set; }
private void CellEditEndingMethod(object obj)
{
    XDocument xmlPurchases = XDocument.Load(DirectoryPaths.DataDirectory + "Purchases.xml");
    var currentPurchaseInData = (from purchase in xmlPurchases.Element("Purchases").Elements("Purchase")
                                 where Convert.ToInt32(purchase.Attribute("Id").Value) == ServiceLocator.Instance.SelectedPurchase.Id
                                 select purchase).FirstOrDefault();

    currentPurchaseInData.SetElementValue("CreditorId", ServiceLocator.Instance.SelectedPurchase.Creditor.Id);
    currentPurchaseInData.SetElementValue("AnimalId", ServiceLocator.Instance.SelectedPurchase.Animal.Id);
    currentPurchaseInData.SetElementValue("QuantityInLitre", ServiceLocator.Instance.SelectedPurchase.Litre);
    currentPurchaseInData.SetElementValue("FAT", ServiceLocator.Instance.SelectedPurchase.FAT);
    currentPurchaseInData.SetElementValue("RatePerLitre", ServiceLocator.Instance.SelectedPurchase.RatePerLitre);

    xmlPurchases.Save(DirectoryPaths.DataDirectory + "Purchases.xml");
}

Now If I change any value in DataGridCell and then I hit Enter CellEditEndingCommand is fired and CellEditEndingMethod is fired. But If I keep a breakpoint inside CellEditEndingMethod and take a look at it, then I can see that Values of any property of SelectedPurchase does not change to new values.

Let me give an example to explain the above line more correctly:

When I keep a breakpoint on any line inside CellEditEndingMethod and take a look at Properties like Litre, FAT etc., these properties values does not change. I mean I expect the property to take new value but it holds old value. Also, In view I can see the new values but in XML file there are still old values.

Update:

Purchases = new ObservableCollection<Purchase>(
    from purchase in XDocument.Load(DirectoryPaths.DataDirectory + "Purchases.xml")
                              .Element("Purchases").Elements("Purchase")
    select new Purchase
    {
        Id = Convert.ToInt32(purchase.Attribute("Id").Value),
        Creditor = (
                        from creditor in XDocument.Load(DirectoryPaths.DataDirectory + "Creditors.xml")
                                                  .Element("Creditors").Elements("Creditor")
                        where creditor.Attribute("Id").Value == purchase.Element("CreditorId").Value
                        select new Creditor
                        {
                            Id = Convert.ToInt32(creditor.Attribute("Id").Value),
                            NameInEnglish = creditor.Element("NameInEnglish").Value,
                            NameInGujarati = creditor.Element("NameInGujarati").Value,
                            Gender = (
                                        from gender in XDocument.Load(DirectoryPaths.DataDirectory + @"Basic\Genders.xml")
                                                                .Element("Genders").Elements("Gender")
                                        where gender.Attribute("Id").Value == creditor.Element("GenderId").Value
                                        select new Gender
                                        {
                                            Id = Convert.ToInt32(gender.Attribute("Id").Value),
                                            Type = gender.Element("Type").Value,
                                            ImageData = gender.Element("ImageData").Value
                                        }
                                     ).FirstOrDefault(),
                            IsRegisteredMember = creditor.Element("IsRegisteredMember").Value == "Yes" ? true : false,
                            Address = creditor.Element("Address").Value,
                            City = creditor.Element("City").Value,
                            ContactNo1 = creditor.Element("ContactNo1").Value,
                            ContactNo2 = creditor.Element("ContactNo2").Value
                        }
                   ).FirstOrDefault(),
        Animal = (
                    from animal in XDocument.Load(DirectoryPaths.DataDirectory + @"Basic\Animals.xml")
                                            .Element("Animals").Elements("Animal")
                    where animal.Attribute("Id").Value == purchase.Element("AnimalId").Value
                    select new Animal
                    {
                        Id = Convert.ToInt32(animal.Attribute("Id").Value),
                        Type = animal.Element("Type").Value,
                        ImageData = animal.Element("ImageData").Value,
                        Colour = animal.Element("Colour").Value
                    }
                 ).FirstOrDefault(),
        Litre = Convert.ToDouble(purchase.Element("QuantityInLitre").Value),
        FAT = Convert.ToDouble(purchase.Element("FAT").Value),
        RatePerLitre = Convert.ToDouble(purchase.Element("RatePerLitre").Value)
    }
  );
Vishal
  • 6,238
  • 10
  • 82
  • 158

3 Answers3

1

The CellEditEnding Event is not meant to update the datarow but to validate the single cell and keep it in editing mode if the content is not valid. The real update is done when the whole row is committed. Try it by adding the code in the HandleMainDataGridCellEditEnding method in http://codefluff.blogspot.de/2010/05/commiting-bound-cell-changes.html to your CellEditEndingMethod. It is good explained there. You may replace the if (!isManualEditCommit) {} by if (isManualEditCommit) return;.

UPDATE

You can extend your Purchase class by interface IEditableObject. DataGrid will call the method EndEdit() of this interface after the data has been committed and so you can do the XML stuff there. So you don't need any further buttons because a cell goes in edit mode automatically and the commit is done when you leave the row. I think the CollectionChanged solution does not work because if you edit a dataset all changes take place inside the single object (Purchase) and not in the collection. CollectionChanged will be called by adding or removing an object to the collection

2nd UPDATE

Another try by putting it all together:

I simplified your Purchase class for demonstration:

class Purchase
{
    public string FieldA { get; set; }
    public string FieldB { get; set; }
}

Create a derived class to keep the real Purchase class clean:

class EditablePurchase : Purchase, IEditableObject
{
    public Action<Purchase> Edited { get; set; }

    private int numEdits;
    public void BeginEdit()
    {
        numEdits++;
    }

    public void CancelEdit()
    {
        numEdits--;
    }

    public void EndEdit()
    {
        if (--numEdits == 0)
        {
            if (Edited != null)
                Edited(this);
        }
    }
}

This is explained in SO WPF DataGrid calls BeginEdit on an IEditableObject two times?

And create the Purchases collection:

   ObservableCollection<EditablePurchase> Purchases = new ObservableCollection<EditablePurchase>()
        {
            new EditablePurchase {FieldA = "Field_A_1", FieldB = "Field_B_1", Edited = UpdateAction},
            new EditablePurchase {FieldA = "Field_A_2", FieldB = "Field_B_2", Edited = UpdateAction}
        };

    Purchases.CollectionChanged += Purchases_CollectionChanged;

    private void Purchases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null)
            foreach (EditablePurchase item in e.NewItems)
                item.Edited = UpdateAction;
    }

    void UpdateAction(Purchase purchase)
    {
        // Save XML
    }

This provides that the calls to Edited are catched for all EditablePurchase elements from initialization and for newly created ones. Be sure to have the Edited property set in initializer

Community
  • 1
  • 1
Fratyx
  • 5,717
  • 1
  • 12
  • 22
  • Ok, I will check it and let you know – Vishal Sep 26 '14 at 23:14
  • Thanks for the response & welcome to StackOverflow! One tip for a good answer: links for more info are awesome, but it's always best to paste the relevant code into your answer directly as well-- StackOverflow aims to be here for the long-term, whereas reference links have a way of moving or disappearing. Best to you! – Robert N Sep 26 '14 at 23:17
  • @Fratyx: CollectionChanged is only used to add/remove PropertyChanged from the Purchase. I recommend you try the code or study it in more detail before claiming it doesn't work. – Mike Fuchs Sep 28 '14 at 08:33
  • The IEditableObject might be a good way to replace the save button. – Mike Fuchs Sep 28 '14 at 08:36
  • @adabyron: Yes, you are right. I just tried to understand why CollectionChanged is not fired in Vishals code. But now I see that the `Purchases.CollectionChanged += Purchases_CollectionChanged` seems to be to late because the filling of Purchases is done in the initialization. So the order should be: create (initialize), add event handler, fill items. – Fratyx Sep 29 '14 at 06:04
0

This is a disgrace for WPF. No DataGrid.CellEditEnded event? Ridiculous, and I didn't know about that so far. It's an interesting question.

As Fratyx mentioned, you can call

dataGrid.CommitEdit(DataGridEditingUnit.Row, true);

in a code behind CellEditEnding method. While it works, I find it's quite ugly. Not only because of having code behind (could use a behavior to circumnavigate that), but your ViewModel CellEditEndingMethod will be called twice, once for no good reason because the edit is not yet committed.

I would probably opt to implement INotifyPropertyChanged in your Purchase class (I recommend using a base class so you can write properties on one line again) if you haven't already, and use the PropertyChanged event instead:

public MyViewModel()
{
    Purchases = new ObservableCollection<Purchase>();
    Purchases.CollectionChanged += Purchases_CollectionChanged;
}

private void Purchases_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    if (e.NewItems != null)
        foreach (Purchase item in e.NewItems)
            item.PropertyChanged += Purchase_PropertyChanged;

    if (e.OldItems != null)
        foreach (Purchase item in e.OldItems)
            item.PropertyChanged -= Purchase_PropertyChanged;
}

private void Purchase_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    // save the xml...
}
Mike Fuchs
  • 12,081
  • 6
  • 58
  • 71
  • Sorry, I cannot check your answer. Something happend to my Project. For e.g. If I change background of any object, I can see the changes at design time but I cannot see those changes at runtime. I think Windows has cached my Project. I have restarted my computer. But still this problem exists. – Vishal Sep 27 '14 at 00:29
  • Oops... Sorry for the above comment. I just cleaned the solution and Build it again and now it works fine. Now, I will check your code. – Vishal Sep 27 '14 at 00:31
  • `Purchases_CollectionChanged` is never fired. – Vishal Sep 27 '14 at 00:48
  • How do you populate Purchases? Let me have a look at your code, please. – Mike Fuchs Sep 27 '14 at 07:27
  • I have updated my question. Please have a look at it. – Vishal Sep 27 '14 at 11:47
  • And where are you attaching the `Purchases_CollectionChanged` event handler? It must be after you instantiate your collection. – Mike Fuchs Sep 27 '14 at 12:08
  • Yes it is declared after i populate Purchases. – Vishal Sep 27 '14 at 12:11
  • Let me rephrase. You would need to instantiate the collection. Then add CollectionChanged event handler. Then add items to the collection. This way the CollectionChanged event is fired. It is true that you will only get PropertyChanged events then when you press enter or go to a different row. However, if you are ready to press a save button, you don't have to do any of the above, I'll add another answer. – Mike Fuchs Sep 27 '14 at 12:45
  • Sorry, just realized you want the save button inside the datagrid. – Mike Fuchs Sep 27 '14 at 13:05
  • I dont have save button right now. But if you say that how can i put all the cells of a row in edit mode by clicking on edit button then i can implement the above concept using save button. – Vishal Sep 27 '14 at 13:05
0

You will not get any CollectionChanged event before DataGrid is changing the collection. And this does not before happen a dataset is committed. If you press 'Enter' in a cell you change the value of this cell in a kind of copy of the real dataset. So it is possible to skip the changes by rollback. Only after finishing a row e.g. by changing to another row or direct commit your changed data will be written back to the original data. THEN the bindings will be updated and the collection is changed. If you want to have an update cell by cell you have to force the commit as in the code I suggested. But if you want to have a puristic MVVM solution without code behind you have to be content with the behavior DataGrid is intended for. And that is to update after row is finished.

Fratyx
  • 5,717
  • 1
  • 12
  • 22
  • OK. I would like to stick with MVVM pattern. What I really want is a datGrid having an edit button on all the rows. When I click that edit button its content should be changed to Save and I want all the columns in edit mode. When user finishes editing, He should click the save button and it should be saved to XML file. Can you show a sample for this? – Vishal Sep 27 '14 at 11:52
  • Sorry I thought you really need a cell by cell commit. I updated my first post. Maybe that will help you. – Fratyx Sep 27 '14 at 15:01