This got nothing to do with threads, sorry for intruding, but I don’t know where to ask this question (managed newsgroups provide no answer)
When you navigate back in a browser-like WPF application, the pages are re-created (at least the constructor is called).
And somehow, magically, the content for Textbox(es) in the page will be filled with whatever had been in there when the page had been left.
Since the page is created anew on navigating ‘Back’, where does this content come from? Is it bound to some kind of session storage as default?
And how do I get my own values back (as content for Listboxes for example are lost on navigating)?
First of all, Sam, I am sorry that no one answered your newsgroup question. I just returned from a long vacation and I hope that this delayed response is still helpful.
Understanding how control state is retained requires a bit of knowledge of how “journaling” works. Journaling is the mechanism for keeping track of the user’s navigation history – the “Journal” is an internal data structure which consists, among other things, of a ForwardStack and BackStack (these stacks are exposed on Frame and NavigationWindow). As a user navigates through a set of pages (say by clicking on Hyperlinks that point to other pages), JournalEntries are added to the BackStack. Specifically, a BackStack entry for a page is added upon navigating away from the page. If the user clicks on the back button, a few things happen: a JournalEntry is popped off the BackStack, the page it represents is rehydrated, the framework navigates to the rehydrated page, and a JournalEntry for the current page is created and added to the ForwardStack. If a user initiates a new navigation, the forward stack is cleared.
This system is modeled after how navigation works in all popular web browsers today, and you will notice that WPF navigation works the same way that navigation works in Internet Explorer, for example.
There are two main ways that JournalEntries are created, depending on what is being journaled (and also on the value of the KeepAlive property, which I will discuss in a moment):
- By URI: If you navigate to a XAML page via its source URI (e.g. page1.xaml), and then navigate away from it, the page is stored simply by creating a new JournalEntry that stores the URI for that page (the JournalEntry.Source property is used for this purpose). Navigating back to the page simply requires the framework to get the Source and navigate to it.
- By object: If you navigate to an object rather than a XAML page (e.g. by creating an object tree programmatically and calling Navigate(object content)), and then navigate away from it, we cannot journal by URI because there is no URI (!). In this case, the object tree is kept alive and a reference to the tree is stored in its JournalEntry. Navigating back to the page involves re-attaching the saved tree as the Content of the Frame or NavigationWindow.
The default behavior for journaling pages that have URIs is by URI, but this can be overridden by setting JournalEntry.KeepAlive (which is an attached DP) if desired. Keep in mind that there are working set implications when you use KeepAlive, because the whole page really is kept alive in memory. URI JournalEntries are, of course, smaller.
At this point, it is probably obvious to you that when we journal “by object,” storing the user-modified state of controls (like TextBoxes and RadioButtons) is trivial: because the whole tree is kept alive, there is no additional work to do. When we journal by URI, we traverse the tree and look for controls that have properties which should be journaled. We store these property values in the JournalEntry along with the source URI for the page. We know which properties should be journaled because they have set the FrameworkPropertyMetadataOptions.Journal flag. Also note that we will ignore controls which do not have the PersistId property set. When a page is rehydrated from a JournalEntry, we navigate to the JournalEntry.Source URI and we apply all of the deltas to control state that we have stored in the Journal.
In general, controls whose state may be changed via user interaction maintain their state across navigations (there are notable exceptions, like PasswordBox). When authoring custom controls, you should consider whether or not your controls have state that may be modified via user interaction, and if they can, then you should usually mark those properties with FrameworkPropertyMetadataOptions.Journal and set the PersistId. If your control stores sensitive data like a password or credit card number, you will likely not want that state to be journaled.
Sam, I did not understand the last part of your question, about ListBox. Could you elaborate or send me an example of some source code that is not behaving as expected?