My application needs to save its data when it shuts down, so I implemented:
private void oclmEditor_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = false;
OCLMEditorViewModel vm = this.DataContext as OCLMEditorViewModel;
if (vm.SaveModelDataCommand.CanExecute(null))
vm.SaveModelDataCommand.Execute(null);
}
I thought all was well. But then I realised that whilst it was saving at shutdown, it was not actually updating the source controls first. This really surprised me as they are set to LostFocus
and clearly everything is losing focus if it is shutting down .
I decided that I needed to find a way of easily getting all the controls to update the source. As a result of my research I implemented these extensions:
public static class Helper
{
public static IEnumerable<DependencyObject> EnumerateVisualChildren(this DependencyObject dependencyObject)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(dependencyObject); i++)
{
yield return VisualTreeHelper.GetChild(dependencyObject, i);
}
}
public static IEnumerable<DependencyObject> EnumerateVisualDescendents(this DependencyObject dependencyObject)
{
yield return dependencyObject;
foreach (DependencyObject child in dependencyObject.EnumerateVisualChildren())
{
foreach (DependencyObject descendent in child.EnumerateVisualDescendents())
{
yield return descendent;
}
}
}
public static void UpdateBindingSources(this DependencyObject dependencyObject)
{
foreach (DependencyObject element in dependencyObject.EnumerateVisualDescendents())
{
LocalValueEnumerator localValueEnumerator = element.GetLocalValueEnumerator();
while (localValueEnumerator.MoveNext())
{
BindingExpressionBase bindingExpression = BindingOperations.GetBindingExpressionBase(element, localValueEnumerator.Current.Property);
if (bindingExpression != null)
{
bindingExpression.UpdateSource();
}
}
}
}
}
In all honesty, I do not know what the best name would be to give to the class nor where to store it in the application (I used the mainwindow.xaml.cs).
I digressed ... So, I then changed the shutdown code:
private void oclmEditor_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
e.Cancel = false;
oclmEditor.UpdateBindingSources();
OCLMEditorViewModel vm = this.DataContext as OCLMEditorViewModel;
if (vm.SaveModelDataCommand.CanExecute(null))
vm.SaveModelDataCommand.Execute(null);
}
In principle it works. Now the sources are updated before it saves. Great!
But I noticed in the output window:
System.Windows.Data Error: 8 : Cannot save value from target back to source. BindingExpression:Path=SelectedStudentItem;
DataItem='OCLMEditorViewModel' (HashCode=32322646); target element is 'DataGrid' (Name='gridStudents');
target property is 'SelectedItem' (type 'Object') NullReferenceException:'System.NullReferenceException: Object reference not set to an instance of an object. at OCLMEditor.OCLMEditorViewModel.set_SelectedStudentItem(Student value) in D:\My Programs\OCLMEditor\OCLMEditor\ViewModels\OCLMEditorViewModel.cs:line 151'
The DataGrid
is bound to a CollectionViewSource
. The SelectedItem
is bound to a property in the ViewModel
. I did more testing and had to end up doing:
private Student _SelectedStudentItem;
public Student SelectedStudentItem
{
get
{
return _SelectedStudentItem;
}
set
{
// We need to remove this item from the previous student history
if (_SelectedStudentItem != null)
_SelectedStudentItem.History.Remove(Meeting.DateMeeting);
_SelectedStudentItem = value;
if (_SelectedStudentItem == null)
return;
_EditStudentButtonClickCommand.RaiseCanExecuteChanged();
_DeleteStudentButtonClickCommand.RaiseCanExecuteChanged();
OnPropertyChanged("SelectedStudentItem");
if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingMain)
{
_Meeting.TFGW.BibleReadingItem.Main.Name = _SelectedStudentItem.Name;
OnPropertyChanged("BibleReadingMain");
}
else if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingClass1)
{
_Meeting.TFGW.BibleReadingItem.Class1.Name = _SelectedStudentItem.Name;
OnPropertyChanged("BibleReadingClass1");
}
else if (ActiveStudentAssignmentType == StudentAssignmentType.BibleReadingClass2)
{
_Meeting.TFGW.BibleReadingItem.Class2.Name = _SelectedStudentItem.Name;
OnPropertyChanged("BibleReadingClass2");
}
}
}
I had to put that test for null in there and return early. Now the exception does not happen.
Do I assume correctly that there was no other way to handle this? One thing I did try was to change my SelectedStudentItem
to work directly with my collection view CurrentItem
and MoveCurrentTo
methods. But then I lose the ability to keep this variable up to date as the user clicks on grid rows. That is why I did it the former way (binding to SelectedItem
).
Sorry if the question sound wordy or complicated. I was not sure how to convey it.