I’m writing an HR safety tracking app using WPF and Entity Framework where I want to display and edit a grid of safety incidents, and under each selected incident would be a grid of months during which hours were lost due to the incident. This means a DataGrid for the hours inside the RowDetailsTemplate of a DataGrid for the incidents.
I can currently save changes I make to existing hours rows but I can’t save the additions or deletions to this inner DataGrid. My relevant model code:
[Table("hr.safety_incident")]
public partial class SafetyIncident
{
[Key]
[Column(TypeName = "numeric")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public decimal incident_id { get; set; }
[Column(TypeName = "datetime2")]
public DateTime incident_date { get; set; }
[Required]
[StringLength(30)]
public string incident_type { get; set; }
}
// EDIT: forgot this part of the class
public partial class SafetyIncident // In another file
{
protected ObservableCollection<SafetyHours> _lostHoursDetails;
[NotMapped]
public ObservableCollection<SafetyHours> LostHoursDetails
{
get { return _lostHoursDetails; }
set
{
if (_lostHoursDetails != value)
{
_lostHoursDetails = value;
}
}
}
}
[Table("hr.safety_hours")]
public partial class SafetyHours
{
[Key]
[Column(TypeName = "numeric")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public decimal safety_hours_id { get; set; }
[Column(TypeName = "numeric")]
public decimal incident_id { get; set; }
[Column(TypeName = "datetime2")]
public DateTime safety_hours_date { get; set; }
[Column(TypeName = "numeric")]
public decimal restricted_hours { get; set; }
[Column(TypeName = "numeric")]
public decimal lost_hours { get; set; }
}
Relevant XAML:
<DataGrid x:Name="dtgAllIncidents" AutoGenerateColumns="False"
ItemsSource="{Binding Source={StaticResource safetyIncidentViewSource}}"
RowDetailsVisibilityMode="VisibleWhenSelected">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="incident_dateColumn" Header="Incident Date" Width="110" SortMemberPath="incident_date">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker IsHitTestVisible="False"
SelectedDate="{Binding incident_date, Mode=TwoWay, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=true}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding incident_date, Mode=TwoWay, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=true}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Header="Incident Type" Binding="{Binding incident_type}"/>
</DataGrid.Columns>
<DataGrid.RowDetailsTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Enter data into the blank row to add lost and restricted hours.”/>
<DataGrid x:Name="dtgLostHoursDetails" ItemsSource="{Binding LostHoursDetails}" AutoGenerateColumns="False" CanUserAddRows="True" CanUserDeleteRows="True">
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="incident_dateColumn" Header="Month in Which Hours Lost">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<DatePicker IsHitTestVisible="False"
SelectedDate="{Binding safety_hours_date, Mode=TwoWay, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=true}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<DatePicker SelectedDate="{Binding safety_hours_date, Mode=TwoWay, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged, ValidatesOnExceptions=true}"/>
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="SizeToHeader" Header="Lost Hours" Binding="{Binding lost_hours}"/>
<DataGridTextColumn Width="SizeToHeader" Header="Restricted Hours" Binding="{Binding restricted_hours}"/>
</DataGrid.Columns>
</DataGrid>
</StackPanel>
</DataTemplate>
</DataGrid.RowDetailsTemplate>
</DataGrid>
Relevant code-behind:
private ObservableCollection<SafetyIncident> _incidents;
private SafetyIncidentsViewModel _incidentsVM = new SafetyIncidentsViewModel();
private void mniIncidents_Click(object sender, RoutedEventArgs e)
{
CollectionViewSource safetyIncidentViewSource = ((CollectionViewSource)this.FindResource("safetyIncidentViewSource")));
_incidents = _incidentsVM.GetIncidents();
safetyIncidentViewSource.Source = _incidents;
}
private void btnSaveIncidents_Click(object sender, RoutedEventArgs e)
{
_incidentsVM.SaveChanges();
dtgAllIncidents.Items.Refresh();
}
...
public class SafetyIncidentsViewModel
{
private Corp_DB _context = new Corp_DB();
public ObservableCollection<SafetyIncident> GetIncidents()
{
ObservableCollection<SafetyIncident> results = null;
LoadSafetyIncidents();
results = _context.safety_incident.Local;
return results;
}
private void LoadSafetyIncidents()
{
_context.safety_incident.OrderBy(si => si.incident_date).Load();
_context.safety_hours.Load();
foreach (var i in _context.safety_incident)
{
var lostHours = new ObservableCollection<SafetyHours>();
foreach (var h in _context.safety_hours.Where(sh => sh.incident_id == i.incident_id).OrderBy(sh => sh.safety_hours_date))
{
lostHours.Add(h);
}
i.LostHoursDetails = lostHours;
}
}
public void SaveChanges()
{
// Assign new hours rows their safety incident. Open to more automatic way...
foreach (var si in _context.safety_incident)
{
foreach (var sh in si.LostHoursDetails.Where(h => h.incident_id == 0))
{
sh.incident_id = si.incident_id;
}
}
_context.SaveChanges();
}
...
Another point of complexity: before the _context.SaveChanges call, new incident rows (outer grid) will have an incident_id of 0, so how would any new hours rows (inner grid) know what to save for that? Would something extra have to be done after the initial call, including a second call? Thanks…
PS Yes, I know my MVVM isn't pure, I just need something that works.