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