【问题标题】:Property bound to DataGrid's SelectedItem does not change its child properties when CellEditEnding is fired触发 CellEditEnding 时,绑定到 DataGrid 的 SelectedItem 的属性不会更改其子属性
【发布时间】:2014-09-26 19:42:52
【问题描述】:

我有一个 DataGrid,它看起来像:

<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>

属性 SelectedPurchase 看起来像:

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");
}

现在,如果我更改 DataGridCell 中的任何值,然后我点击 Enter 触发 CellEditEndingCommand 并触发 CellEditEndingMethod。但是如果我在 CellEditEndingMethod 中保留一个断点并查看它,那么我可以看到 SelectedPurchase 的任何属性的 Values 都不会更改为新值。

让我举一个例子来更正确地解释上面的行:

当我在 CellEditEndingMethod 内的任何行上保留断点并查看 Litre、FAT 等属性时,这些属性值不会改变。我的意思是我希望该物业具有新的价值,但它具有旧的价值。另外,在视图中我可以看到新值,但在 XML 文件中仍然有旧值。

更新:

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)
    }
  );

【问题讨论】:

    标签: c# wpf datagrid


    【解决方案1】:

    CellEditEnding 事件的目的不是更新数据行,而是验证单个单元格并在内容无效时将其保持在编辑模式。当整行被提交时,真正的更新就完成了。通过将http://codefluff.blogspot.de/2010/05/commiting-bound-cell-changes.html 中的 HandleMainDataGridCellEditEnding 方法中的代码添加到您的 CellEditEndingMethod 来尝试。那里很好解释。您可以将if (!isManualEditCommit) {} 替换为if (isManualEditCommit) return;

    更新

    您可以通过接口 IEditableObject 扩展您的购买类。 DataGrid 将在提交数据后调用此接口的 EndEdit() 方法,因此您可以在那里执行 XML 操作。因此,您不需要任何其他按钮,因为单元格会自动进入编辑模式,并且在您离开该行时完成提交。 我认为 CollectionChanged 解决方案不起作用,因为如果您编辑数据集,所有更改都发生在单个对象(购买)内,而不是集合中。 CollectionChanged 将通过向集合中添加或删除对象来调用

    第二次更新

    再一次尝试将它们放在一起:

    我简化了您的采购类进行演示:

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

    创建一个派生类以保持真正的 Purchase 类干净:

    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);
            }
        }
    }
    

    这在 SO WPF DataGrid calls BeginEdit on an IEditableObject two times?中有解释

    并创建 Purchases 集合:

       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
        }
    

    这提供了从初始化和新创建的所有 EditablePurchase 元素捕获对 Edited 的调用。 确保在初始化程序中设置了 Edited 属性

    【讨论】:

    • 好的,我去查一下告诉你
    • 感谢您的回复,欢迎来到 StackOverflow!一个好答案的提示:更多信息的链接很棒,但最好也直接将相关代码粘贴到您的答案中——StackOverflow 的目标是长期存在,而参考链接有一种移动方式或消失。给你最好的!
    • @Fratyx:CollectionChanged 仅用于从购买中添加/删除 PropertyChanged。我建议您在声称它不起作用之前尝试代码或更详细地研究它。
    • IEditableObject 可能是替换保存按钮的好方法。
    • @adabyron:是的,你是对的。我只是想了解为什么在 Vishals 代码中没有触发 CollectionChanged。但现在我看到Purchases.CollectionChanged += Purchases_CollectionChanged 似乎为时已晚,因为购买的填充是在初始化中完成的。所以顺序应该是:创建(初始化)、添加事件处理程序、填充项目。
    【解决方案2】:

    这是 WPF 的耻辱。没有DataGrid.CellEditEnded 事件?可笑,到目前为止我还不知道。这是一个有趣的问题。

    正如 Fratyx 所说,您可以致电

    dataGrid.CommitEdit(DataGridEditingUnit.Row, true);
    

    在 CellEditEnding 方法后面的代码中。虽然它有效,但我发现它很丑陋。不仅因为背后有代码(可以使用行为来绕过它),而且您的 ViewModel CellEditEndingMethod 将被调用两次,一次没有充分的理由,因为编辑尚未提交。

    如果您还没有,我可能会选择在您的采购类中实现INotifyPropertyChanged(我建议使用base class,以便您可以再次在一行上编写属性),并改用PropertyChanged 事件:

    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...
    }
    

    【讨论】:

    • 对不起,我无法检查您的答案。我的项目发生了一些事情。例如如果我更改任何对象的背景,我可以在设计时看到更改,但在运行时看不到这些更改。我认为 Windows 已经缓存了我的项目。我已经重新启动了我的电脑。但是这个问题依然存在。
    • 哎呀...抱歉以上评论。我刚刚清理了解决方案并再次构建它,现在它工作正常。现在,我将检查您的代码。
    • Purchases_CollectionChanged 永远不会被解雇。
    • 如何填充购买?请让我看看你的代码。
    • 我已经更新了我的问题。请看一下。
    【解决方案3】:

    在 DataGrid 更改集合之前,您不会收到任何 CollectionChanged 事件。这在提交数据集之前不会发生。 如果您在一个单元格中按“Enter”,您会在一种真实数据集的副本中更改该单元格的值。因此可以通过回滚来跳过更改。只有在完成一行后,例如通过更改到另一行或直接提交,您更改的数据将被写回原始数据。然后绑定将被更新并更改集合。 如果您想逐个更新单元格,则必须按照我建议的代码强制提交。 但是,如果您想拥有一个没有代码的纯粹 MVVM 解决方案,您必须满足 DataGrid 的预期行为。也就是在行完成后更新。

    【讨论】:

    • 好的。我想坚持使用 MVVM 模式。我真正想要的是一个在所有行上都有一个编辑按钮的 datGrid。当我单击该编辑按钮时,其内容应更改为保存,并且我希望所有列都处于编辑模式。当用户完成编辑,他应该点击保存按钮,它应该被保存到 XML 文件。你能为此展示一个样本吗?
    • 对不起,我认为你真的需要一个单元一个单元的提交。我更新了我的第一篇文章。也许这会对你有所帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-11
    • 2010-11-04
    • 2016-06-12
    相关资源
    最近更新 更多