Two-way binding requires Path. On update datagrid after adding new value in c# wpf

66 Views Asked by At

I am building an application where users can create multiple projects, and each project has its unique data fields that the user needs to provide. Currently, I am focusing on the data entry part of the application.

In this implementation, I am using a datagrid with three static columns (read-only), while the remaining columns are dynamically generated from the database based on the user's project requirements.

However, I am encountering an issue when a user attempts to enter a new value into a cell that hasn't been added to the database yet. It throws a System.InvalidOperationException: 'Two-way binding requires Path or XPath.' error. Despite the error, the data is successfully saved in the database, and upon reopening the program, it displays the correct values. I suspect that this exception is thrown when the datagrid attempts to update the row.

I have debugged the program and verified that the database is correctly populated. Additionally, I have confirmed that the ObservableCollection, which serves as the ItemSource for the datagrid, is correctly filled before the exception is thrown.

I have researched this error, and it seems that many people who encounter this issue have misspelled their bindings. However, I have carefully reviewed my code, and I cannot find any bindings that are incorrect. I suspect that the issue lies in the bindings of the dynamic columns within the AddColumns method. Unfortunately, I haven't been able to determine what exactly is causing the problem.

Could you kindly assist me in understanding why this error is occurring and how I can resolve it?

<DataGrid
            x:Name="FillDataDataGrid"
            AutoGenerateColumns="False"
            CanUserAddRows="True"
            CellEditEnding="DataRow_CellEditEnding"
            PreparingCellForEdit="DataRow_CellEditStart"
            SelectionMode="Single"
            SelectionUnit="Cell">
            <!--  ItemsSource="{Binding DataRows}"  -->
            <DataGrid.Columns>
                <DataGridCheckBoxColumn Width="Auto" CanUserReorder="False">
                    <DataGridCheckBoxColumn.HeaderTemplate>
                        <DataTemplate>
                            <CheckBox />
                        </DataTemplate>
                    </DataGridCheckBoxColumn.HeaderTemplate>
                </DataGridCheckBoxColumn>

                <DataGridTextColumn
                    Width="auto"
                    Binding="{Binding Path=ID, Mode=OneWay}"
                    CanUserResize="False"
                    Header="#"
                    IsReadOnly="True" />

                <DataGridTextColumn
                    Width="*"
                    Binding="{Binding Path=Created, StringFormat={}{0:yyyy-MM-dd HH:mm:ss}, Mode=OneWay}"
                    Header="Senast Ändrad"
                    IsReadOnly="true" />
            </DataGrid.Columns>
        </DataGrid>

i have the following code in main window

public partial class MainWindow : Window
    {
        private readonly ObservableCollection<DataRow> _dataRows;
        private readonly DataRowRepository _dataRowRepository;
        private readonly FieldRepository _fieldRepository;
        public IEnumerable<DataRow> DataRows => _dataRows;
        private int activeProject = 1;
        private Dictionary<string, int> _columns { get; set; }
        private string cellOrginalValue;

        public MainWindow()
        {
            InitializeComponent();

            _dataRowRepository = new DataRowRepository(FieldData.Config.Config.SQL_CONNECTION_STRING);
            _fieldRepository = new FieldRepository(FieldData.Config.Config.SQL_CONNECTION_STRING);
            _dataRows = new ObservableCollection<DataRow>(_dataRowRepository.GetAllDataRows(1));
            _columns = _dataRowRepository.GetColumns(activeProject);
            AddColumns();
            FillDataDataGrid.ItemsSource = DataRows;
            
        }

        private void AddColumns()
        {
            List<string> sortedDynamicColumnNames = _columns.OrderBy(pair => pair.Value).Select(pair => pair.Key).ToList();
            int dynamicColumnIndex = 2;
            foreach (string columnName in sortedDynamicColumnNames)
            {
                if (columnName == "project")
                    continue;
                DataGridTextColumn newColumn = new DataGridTextColumn()
                {
                    Header = columnName,
                    Binding = new Binding(string.Format("DynamicColumns[{0}].Value", columnName))
                    
                };
                FillDataDataGrid.Columns.Insert(dynamicColumnIndex, newColumn);
                dynamicColumnIndex++;
            }
        }

        private void DataRow_CellEditStart(object sender, DataGridPreparingCellForEditEventArgs e)
        {
            var cell = e.EditingElement as TextBox;
            if(cell != null)
            {
                cellOrginalValue = cell.Text;
            }
        }

        private void DataRow_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
        {
            if (e.EditAction == DataGridEditAction.Commit && e.Column is DataGridTextColumn)
            {
                var editedCell = e.EditingElement as TextBox; 
                
                string newValue = editedCell.Text;
                if (newValue == null)
                    newValue = "";
                if (newValue != cellOrginalValue)
                {
                    var editedRow = e.Row.Item as FieldData.Library.Models.DataRow; 
                    var columnName = e.Column.Header.ToString();

                    if (editedRow.DynamicColumns.Count < 1)
                    {
                        //New cell
                        editedRow.ID = _dataRowRepository.GetMaxRowId(activeProject) + 1;
                        editedRow.project = activeProject;
                        editedRow.Created = DateTime.Now;
                        Field field = _fieldRepository.GetFieldByName(columnName)[0];
                        editedRow.DynamicColumns.Add(columnName, new FieldData.Models.DataRowDynamicColumn()
                        {
                            FieldID = field.Id,
                            Name = columnName,
                            Value = newValue,
                            Type = field.Type
                        });
                        AddNewRow(editedRow, columnName);
                    }
                    else
                    {
                        editedRow.project = activeProject;
                        editedRow.Created = DateTime.Now;

                        if (!editedRow.DynamicColumns.ContainsKey(e.Column.Header.ToString()))
                            AddNewDynamicColumn(editedRow, columnName, newValue);

                        editedRow.DynamicColumns[e.Column.Header.ToString()].Value = newValue;

                        AddNewRow(editedRow, columnName);
                    }
                }
            }
        }

        private void AddNewDynamicColumn(DataRow editedRow, string columnName, string value)
        {
            Field field = _fieldRepository.GetFieldByName(columnName)[0];
            var column = new FieldData.Models.DataRowDynamicColumn()
            {
                FieldID = field.Id,
                Name = columnName,
                Value = value,
                Type = field.Type
            };
            editedRow.DynamicColumns.Add(columnName, column);
            _dataRowRepository.AddDataFieldValue(editedRow, columnName);
        }

        private void AddNewRow(DataRow dataRow, string col)
        {
            _dataRowRepository.AddDataFieldValue(dataRow, col);
        }
    }
}

i have the folowing models

public class DataRow : ICloneable, INotifyPropertyChanged
    {
        private Dictionary<string,DataRowDynamicColumn> _dynamicColumn;
        private DateTime _date;
        public int ID { get; set; }
        public int project { get; set; }
        public DateTime? LastEdited { get; set; }
        public DateTime Created
        {
            get { return _date; }
            set
            {
                if(_date != value)
                {
                    _date = value;
                    OnPropertyChanged(nameof(Created));
                }
            }
        }
        public Dictionary<string, DataRowDynamicColumn> DynamicColumns 
        {
            get { return _dynamicColumn;  }
            set 
            {
                if (_dynamicColumn != value)
                {
                    _dynamicColumn = value;
                    OnPropertyChanged(nameof(DynamicColumns));
                }
            }
        }

        public DataRow()
        {
            DynamicColumns = new Dictionary<string, DataRowDynamicColumn>();
        }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        public object Clone()
        {
            DataRow row = (DataRow)MemberwiseClone();
            return row;
        }

    }

public class DataRowDynamicColumn: INotifyPropertyChanged
    {
        private string _value;
        public int ID { get; set; }
        public int FieldID {get;set; }
        public string Name { get; set; }
        public string Value 
        {
            get { return _value; }
            set
            {
                if(_value != value)
                {
                    _value = value;
                    OnPropertyChanged(nameof(Value));
                }
            }
        }
        public int Type { get; set; }

        public event PropertyChangedEventHandler? PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }

public class Field
    {
        public int Id { get; set; }

        [Required(ErrorMessage = "Name is required")]
        [StringLength(100, ErrorMessage = "Name must not exceed 100 characters")]
        public string Name { get; set; }

        [StringLength(500, ErrorMessage = "Description must not exceed 500 characters")]
        public string Description { get; set; }
        
        public int Type {get; set;}
        public bool AllowNull { get; set; }
        public DateTime? CreatedDate { get; set; }
        public DateTime? LastUpdatedDate { get; set; }

        public Field()
        {
            // Default constructor
        }
    }

i have one table that looks like

ID (PK, int,Not null)
FieldID(int, not null)
Value(nvarchar(500), not null)
ProjectID(int, not null)
RowId(int,not null)
Created(datetime, not null)

i have this query to populate my datagrid

SELECT t1.*, f.FieldName, ft.FieldClass
FROM [dbo].[FieldValue] t1
INNER JOIN (
    SELECT FieldID, RowID, MAX(ID) AS MaxID
    FROM [dbo].[FieldValue]
    WHERE ProjectID = 1
    GROUP BY FieldID, RowID
) t2 ON t1.FieldID = t2.FieldID AND t1.RowID = t2.RowID AND t1.ID = t2.MaxID
INNER JOIN [dbo].[Fields] f ON t1.FieldID = f.ID 
INNER JOIN [dbo].FieldTypes ft ON ft.ID = f.FieldType
WHERE t1.ProjectID = 1 ORDER BY RowID ASC
0

There are 0 best solutions below