Preserving bindings and journaled values
WPF provides a data binding system for synchronizing values between views and view models.
WPF also provides a navigation system that handles history and can unload pages that are not currently displayed. When using PageFunctions the KeepAlive property set to false(the default), the values of all dependency properties with the Journal flag are saved into the history entry such that the values can be put back into the controls if the user uses the history entry to return to the page.
Unfortunately, these two things cannot be used together. The problem appears to be the automatic history state provider restores the values to the page using DependencyObject.SetValue.
The sequence appears to work out like this:
- Page is constructed with bindings.
- Navigation service restores the page state, overwriting the bindings.
- Page is inserted into the logical tree.
- Page load event fires.
- Page renders, but values are not synchronized with the view model and validation does not occur.
Chango posts a solution to the binding problem, but it's not perfect. If you subclass the controls and disable the Journal flag, the binding will be preserved, but the values will not be preserved unless the view model is also preserved.
I didn't find any good way of overriding the behavior that uses DependencyObject.SetValue, but I did find a workaround anyway.
Subclass the controls to remove the Journal flag. You'll probably want to extract the code that removes the flag into a helper method in a static class.
At the same time you remove the Journal flag from one property, create another property with the same type, belonging to your subclass, with the Journal flag.
When the original property changes, set the value of the new property. When the new property changes, set the value of the original property. You'll need to prevent reentrancy here, otherwise you'll recurse infinitely setting properties.
That should get you exactly the same behavior as WPF was providing already. Change the code that sets the original property to check for a binding expression on the property. If there is a binding expression, use .SetCurrentValue and then update the binding source.
This should work for some cases, but did not work for mine. My view model is only available after the page has been inserted into the logical tree, meaning the source on all the bindings is null at the time the value is restored, which causes all the values to be reset to the defaults when the view model does load.
I had to additionally check whether the FrameworkElement has been loaded before setting the value. If the element has not been loaded, a one-time event handler needs to be registered to the Load event, and that event handler needs to use Dispatcher.BeginInvoke with the Load priority to enqueue another delegate which does the actual setting. The Dispatcher.BeginInvoke is because setting the value during the Load event seems to cause problems with the initial state of the validation adorner.
This appears to work fine for what I need. This will not work if you have interdependencies between user-settable properties on your view model because the values will be assigned in an undefined order. It is possible to make them set in a specific order if you really want to, but it seems like you'd be better off trying to remove the interdependencies instead.