DataGrid causes BeginEdit() method to run twice

2019-09-08 12:44发布

I have implemented IEditableObject for my ViewModel and the below controls are bound to it.

Now when the first time I fire up the Window containing these controls, the BeginEdit() method runs and backs up the variables.

My problem is this: When I start editing the DataGrid again the BeginEdit() method runs and saves the changes to my already backed up variables! This ruins the purpose of the BeginEdit() and CancelEdit(). Now if I choose to cancel the Window, I won't have the original data. How can I prevent this?

<ComboBox ItemsSource="{Binding Path=CoatingFactors}"
          SelectedItem="{Binding Path=CoatingFactor}">
</ComboBox>

<DataGrid ItemsSource="{Binding Path=CustomCFactors}"                      
  ....
</DataGrid>

Here is how I have implemented the BeginEdit() and CancelEdit() methods:

private List<CustomCFactorItem> customCFactors_ORIGINAL;
private double coatingFactor_ORIGINAL;


public void BeginEdit()
{
    customCFactors_ORIGINAL = customCFactors.ConvertAll(o => o.Clone()).ToList();
    coatingFactor_ORIGINAL = coatingFactor;
}

public void CancelEdit()
{
    customCFactors = customCFactors_ORIGINAL.ConvertAll(o => o.Clone()).ToList();
    coatingFactor = coatingFactor_ORIGINAL;
}

UPDATE:

For now, I'm using a little hack like this:

    private List<CustomCFactorItem> customCFactors_ORIGINAL;
    private double coatingFactor_ORIGINAL;

    private int editNum = 0;

    public void BeginEdit()
    {
        if (editNum > 0) return;

        editNum++;

        customCFactors_ORIGINAL = customCFactors.ConvertAll(o => o.Clone());
        coatingFactor_ORIGINAL = coatingFactor;
    }

    public void EndEdit()
    {
        editNum = 0;
    }

    public void CancelEdit()
    {
        editNum = 0;

        customCFactors = customCFactors_ORIGINAL.ConvertAll(o => o.Clone());
        coatingFactor = coatingFactor_ORIGINAL;
    }

1条回答
贼婆χ
2楼-- · 2019-09-08 13:04

It's best not to fight with the WPF DataGrid control at the UI layer. Without writing your own controls designed to suit your own purposes, you simply won't win that fight. There will always be another Microsoft "gotcha" to deal with. I recommend implementing the desired behaviour from the safe-haven of an ObservableCollection.

public class ViewModel
{
    public ViewModel()
    {
        CustomCFactors.CollectionChanged += (s, e) => {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (CustomCFactorItem item in e.NewItems)
                        item.PropertyChanged += BackupLogicEventHandler;
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (CustomCFactorItem item in e.OldItems)
                        item.PropertyChanged -= BackupLogicEventHandler;
                    break;
            }
        };
        for (int i = 0; i < 10; ++i)
        {
            CustomCFactors.Add(new CustomCFactorItem("one", "two", "three"));
        }
        ExecuteBackupLogic();

    }

    public ObservableCollection<CustomCFactorItem> CustomCFactors { get; set; } = new ObservableCollection<CustomCFactorItem>();

    public void BackupLogicEventHandler(object sender, PropertyChangedEventArgs e){
        ExecuteBackupLogic();
    }
    public void ExecuteBackupLogic()
    {
        Console.WriteLine("changed");
    }
}

And Here's a sample of what CustomCFactorItem might look like

public class CustomCFactorItem : INotifyPropertyChanged
    {
        private string _field1 = "";
        public string Field1
        {
            get
            {
                return _field1;
            }
            set
            {
                _field1 = value;
                RaisePropertyChanged("Field1");
            }
        }
        private string _field2 = "";
        public string Field2
        {
            get
            {
                return _field2;
            }
            set
            {
                _field2 = value;
                RaisePropertyChanged("Field2");
            }
        }
        private string _field3 = "";
        public string Field3
        {
            get
            {
                return _field3;
            }
            set
            {
                _field3 = value;
                RaisePropertyChanged("Field1");
            }
        }
        public CustomCFactorItem() { }
        public CustomCFactorItem(string field1, string field2, string field3)
        {
            this.Field1 = field1;
            this.Field2 = field2;
            this.Field3 = field3;
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void RaisePropertyChanged(string property)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }

And this is a simple View that binds the collection to DataGrid. Notice that output is written to the console each time an edit is made.

<Window x:Class="WpfApplication2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApplication2"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Grid>
        <DataGrid ItemsSource="{Binding CustomCFactors}" AutoGenerateColumns="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Field 1" Binding="{Binding Field1, UpdateSourceTrigger=PropertyChanged}" />
                <DataGridTextColumn Header="Field 2" Binding="{Binding Field2, UpdateSourceTrigger=PropertyChanged}" />
                <DataGridTextColumn Header="Field 3" Binding="{Binding Field3, UpdateSourceTrigger=PropertyChanged}" />
            </DataGrid.Columns>

        </DataGrid>
    </Grid>
</Window>
查看更多
登录 后发表回答