0

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.

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • *"save its data when it shuts down"* - this is one of the reasons why I don't like `LostFocus` (I use either `PropertyChanged` or a command). So you have window, where user is typing something and he hits `Alt-F4` and you want to save what he is already entered into `TextBox`? Shouldn't be as simple as changing [focus programmatically](http://stackoverflow.com/q/2914495/1997232) (so that binding will update source) and delaying exit (if it's needed, not sure)? – Sinatr Jul 04 '16 at 09:51
  • @Sinatr In this case I am just hitting the X in the top right of the window. I also have a File menu with Exit on it. But I have to click on another control for things to update as opposed to a menu item or anything. – Andrew Truckle Jul 04 '16 at 09:54

0 Answers0