UI Frontiers - Pages and Pop-ups in Windows Phone 7
By Charles Petzold | October 2011
The interaction between user and computer application normally proceeds without interruption. We type text into the word processor; enter numbers and formulae into the spreadsheet; and flip the pages in an e-book reader. But occasionally, the application requires additional information from the user, or the user initiates an operation specifically to provide the application with other information.
In a traditional Windows application, we all know what happens next: A dialog box appears. Fill it out and click OK, or Cancel if you’ve changed your mind.
What happens in a Windows Phone 7 application isn’t quite clear, however. Neither in Silverlight for the Web nor Silverlight for Windows Phone does there exist anything called a “dialog box.” I still like the term to describe any mechanism that allows the application to interrogate the user for information, but obviously Silverlight dialog boxes are a little different from the traditional kind.
Over the past several installments of this column, I’ve been progressively building an e-book reader for Windows Phone 7 based on plain-text book files downloaded from Project Gutenberg. It has come time to enhance that program with a whole bunch of dialog boxes.
In implementing dialog boxes in a Silverlight application for Windows Phone 7, you have two options.
Perhaps the most obvious approach is to define the dialog box as a separate page. Derive a new class from PhoneApplicationPage and populate it with a bunch of buttons and text boxes and whatnot. A page then invokes this dialog box by navigating to it using the page’s NavigationService object. The Back button (or something else) terminates the dialog box and goes back to the page that invoked it.
The second option is what I call the “pop-up” approach. This approach could make use of the Popup class, but it doesn’t need to. (I’ll discuss the difference shortly.) This kind of dialog box generally derives from UserControl and appears visually on top of the page that invokes it, and then disappears when the user is finished interacting with it. No navigation is involved.
Dialog boxes implemented as navigable pages are inherently modal. (A modal dialog box inhibits interaction with the window or page that invoked it until the dialog box is dismissed.) With the pop-up approach, a dialog box can be either modal or modeless. Implementing a modal pop-up requires the programmer to make sure that the user can’t interact with the underlying page while the pop-up is active. In this sense, it’s a little easier to implement a modeless pop-up, but typically the process of juggling simultaneous input from a modeless dialog and an underlying page can be tricky.
Beginning with the MiddlemarchReader program that I presented in my July column (msdn.microsoft.com/magazine/hh288085), my e-book readers have included an option to display a list of chapters in a ListBox. You can then select one of the chapter titles to jump to the beginning of that chapter. This qualifies as a dialog box, and I chose to implement it as a pop-up even though the logic was somewhat messy: The ApplicationBar button that invoked the pop-up also served to dismiss the pop-up, and that required a somewhat different image for the button.
In retrospect, I should have implemented this dialog box as a separate page, and that’s how it’s done in the HorrorReader program I’ll be describing in this article. To celebrate October and Halloween, HorrorReader lets you read four horror classics: “Frankenstein,” “Dracula,” “Dr. Jekyll and Mr. Hyde” and “The Turn of the Screw,” as shown in Figure 1. (The e-book reader next month will finally expand the potential library to some 35,000 books, I promise!)
Figure 1 The MainPage Display of HorrorReader
In changing the chapters ListBox from a pop-up to a navigable page, I searched deep into my heart to try to understand why I’ve tended to implement Windows Phone 7 dialog boxes as pop-ups. I’ve discovered the surprising motivation: Fear.
Fear of Navigation
If you’ve done any Windows Phone 7 programming at all, you probably know about tombstoning: In certain circumstances, a running application can be terminated and removed from memory. This happens when the user presses the Start button on the phone to view the start screen; the phone hasn’t received any input for a while and turns off its display to go into a locked condition, or the user turns off the screen manually.
The application is restarted when the user unlocks the screen or presses the Back button to navigate back to the program. The Windows Phone 7 OS will display the last active page in the application, but the restoration of any other information is entirely the programmer’s responsibility. Generally, a page uses the State dictionary of PhoneApplicationPage to save transient information associated with the page, while the App object uses the State dictionary of PhoneApplicationService to store transient application data, and isolated storage for permanent data.
In coding for tombstoning, programmers override the OnNavigatedFrom method in the page to save page information and OnNavigatedTo to restore this information. That’s fine when the program is being tombstoned or being revived after being tombstoned. But if the page is simply in the process of navigating to another page, or returning from that navigation, then saving and restoring page information is unnecessary. Depending on the amount of information involved, this extra activity could slow up page navigation significantly.
One way to avoid this extra activity is simply to restrict the application to one page and implement dialog boxes with pop-ups! That’s what I did in the previous versions of the e-book reader. I avoided page navigation because I feared slowing it down with tombstoning code.
But that’s silly. It’s really only a problem if tombstoning is implemented in what I now think of as “the dumb approach”! A page shouldn’t save a lot of state information unless the application is really being tombstoned, and it shouldn’t attempt to restore that information unless it’s being revived from a tombstoned state. Windows Phone 7.1 will provide the navigation overrides with additional information to more intelligently implement these methods. Meanwhile, you can relegate all heavy lifting to the App class, which is primarily responsible for handling the events implemented by PhoneApplicationService. These events truly indicate if an application is being tombstoned or revived.
It’s usually beneficial to tighten up the OnNavigatedTo override as well. This logic can be rather simple: If a particular field is null, it needs to be regenerated; if it isn’t null, then the object is the same as it was before navigation because the application wasn’t tombstoned.
The Structure of HorrorReader
The App file in HorrorReader has two public properties that reference objects stored in isolated storage. The first, AppSettings, stores application settings that apply to all the books. These include the font family, font size and the style of page transition. The second, the CurrentBook property of App, is an object of type BookInfo, which has all the individual book-related properties, including the filename of the actual book, the current chapter and page, the collection of ChapterInfo objects storing pagination data, and collections of bookmarks and annotations, which are new with this version. Each of the four books in the library has its own BookInfo object stored in isolated storage, but this BookInfo object isn’t created until you first read the book.
HorrorReader has six classes that derive from PhoneApplicationPage. These classes are all part of the HorrorReader project and are easily identified with the word “Page” in their names. Aside from App, all other classes are in the Petzold.Phone.EBookReader dynamic link library.
As you’ve seen in Figure 1, MainPage has four buttons that let you select one of the four available books. (This page will be replaced in next month’s version of the program.) When the user clicks one of these buttons, MainPage navigates to BookViewerPage, which is primarily responsible for hosting the BookViewer control and implementing several pop-ups.
BookViewerPage has four ApplicationBar buttons. The first three cause navigation to other pages: ChaptersPage, BookmarksPage and AnnotationsPage. A Bookmark is simply a reference to a particular page in the book with a label entered by the user. An Annotation is a text selection accompanied by an optional note. Each of these three pages contains a ListBox, and each of them causes the BookViewerPage to jump to a new page in the book.
The ApplicationBar menu in BookViewerPage has three items: “smaller text,” “larger text” and “settings.” The first two cause changes to the font size in increments of 10 percent, and “settings” navigates to SettingsPage, which implements a Pivot control for selecting fonts and the desired page transition, as shown in Figure 2. The text at the bottom of the page is a preview of the selected font.
Figure 2 The Settings Pivot for Font Selection
In working on this page, I ran into an apparent conflict with the Slider and the GestureListener class from the Windows Phone Toolkit. I had to implement my own Slider, and in the process made it jump only in 10 percent increments.
I’m not entirely happy with the use of PhoneApplicationPage derivatives as dialog boxes. I prefer a more structured way to pass information to a dialog box and get information back. This transfer of data often becomes somewhat clumsy within a navigational structure. For example, I would’ve preferred for the BookViewerPage to pass a reference of the current book to ChaptersPage, and for ChaptersPage to return the chapter selected by the user back to BookViewerPage. In other words, I want ChaptersPage to work more like a function without side effects. Perhaps someday I’ll design a wrapper around PhoneApplicationPage that lets me do this. Meanwhile, for HorrorReader, I simply have all the pages share data by referencing the same CurrentBook property in the App class.
Pop up or Pop-Up?
Although I went with PhoneApplicationPage derivatives for the chapters list, bookmarks list, annotations list and settings, I still needed several pop-ups.
There are basically two ways to implement a pop-up in Silverlight for Windows Phone. One approach is simply to put a control (or, more commonly, a UserControl derivative) right in the page’s visual tree. This control sits on top of everything else, but its Visibility property is initialized to Collapsed. When the pop-up needs to pop up, simply set the Visibility property to Visible.
For a modal pop-up, you’ll also want to disable everything else on the page. You can do this by setting the IsEnabled property of the underlying control to false, or alternatively setting the IsHitTestVisible property to false. These two properties are similar, but IsEnabled is restricted to controls while IsHitTestEnabled can also be used with FrameworkElement derivatives such as panels. The IsEnabled property also causes some controls to dim out. Another option is to make the pop-up full screen with a translucent background that blocks touch input. Regardless what technique you use, you’ll probably also need to disable the ApplicationBar. (More on this little problem shortly.)
An alternative is to use the Popup element. The Popup element has a Child property that you’ll probably set to a UserControl derivative. (I wish I could derive from Popup, but it’s sealed.) By default, the IsOpen property of Popup is false, and you set it to true to make the child of the Popup visible. Popup also has convenient HorizontalOffset and VerticalOffset properties that let you position the child.
One interesting aspect of Popup is that it doesn’t require a parent element. In other words, it doesn’t have to be part of the visual tree. You can simply create a Popup object in code, set the Child property and set the IsOpen property to true and it will appear visually on top of everything else.
Watch out, though. If Popup has no parent, the HorizontalOffset and VerticalOffset properties are relative to the upper-left corner of the PhoneApplicationFrame object, which conceptually underlies all the PhoneApplicationPage objects. The frame includes the system tray at the top of the screen, but the pop-up won’t appear on top of the system tray. If the system tray is visible, the Popup child will be clipped at the top. Only the part overlaying the PhoneApplicationPage will be visible. You’ll want to set the VerticalOffset property to a non-zero value to accommodate the system tray.
The advantage of a Popup with no parent is that it’s easy to make it modal. You simply set the IsEnabled property of the PhoneApplicationPage derivative to false and effectively disable everything on the page in one shot.
Gosh, wouldn’t that be nice? In reality, it doesn’t happen, because the ApplicationBar isn’t part of the visual tree of the page. You’ll need to disable the ApplicationBar separately. It has a handy IsMenuEnabled property, but it doesn’t have a single property to disable the buttons. You’ll have to handle those buttons individually.
For two of the pop-ups I needed, I wanted to move them to various places relative to the page. That’s easy when the dialog is a child of Popup. Otherwise, you’ll need a TranslateTransform on the dialog control. To simplify tombstoning, I decided to be consistent and use the Popup element for all my pop-ups. I also decided to define each Popup and child in the visual tree of the page. The HorizontalOffset and VerticalOffset properties are thus relative to the page rather than to the frame.
One of these pop-ups is a ListBox named “textSelectionMenu,” defined in BookViewerPage.xaml. This is the menu that appears when you make a text selection, as I described in my September column (msdn.microsoft.com/magazine/hh394142). One of the options is “note,” which invokes another pop-up defined by a UserControl derivative named AnnotationDialog. This lets the user type in a note, as shown in Figure 3.
Figure 3 The AnnotationDialog Pop-Up
You can define a new bookmark by flicking your finger up or down the page. The BookmarkDialog lets you enter a label.
BookmarkDialog and AnnotationDialog can also appear, respectively, on BookmarksPage and AnnotationsPage for editing an existing item. You can also delete a bookmark or note on these pages, and that invokes another pop-up called OkCancelDialog. This is similar to the standard MessageBox available in Windows Phone 7, except it doesn’t make a noise when it appears.
The final pop-up is FindDialog, invoked by the fourth ApplicationBar button and shown in Figure 4. This lets you search for text in the book with familiar options. As each match is found, the text is highlighted and the FindDialog jumps to the top or bottom of the page to let the highlighted text be visible.
Figure 4 The FindDialog Pop-Up
Should pop-ups accommodate tombstoning? Here’s the scenario I posed to myself: Suppose I’m searching for some text in a book using the pop-up dialog shown in Figure 4. Then I set the phone down on the table. Several minutes later, I pick up the phone and the screen is locked. I push the On button and sweep up the wallpaper. Should the FindDialog still be visible in the state I left it?
My reluctant answer was: of course. I had to acknowledge that this is what the user expects and this is what should happen. That meant that pop-ups needed to be tombstoned.
So, in any page that hosts a Popup, I defined a field of type Popup named activePopup, which is set whenever a pop-up is visible. If this field is set during a call to OnNavigationFrom, it can only mean that the program is being tombstoned, because everything else is disabled while the pop-up is visible. In that case, the page saves the name of the Popup in the page’s State dictionary. It also checks if the child of the Popup implements the ITombstonable interface, which I defined with two methods: SaveState and RestoreState. This is how the AnnotationDialog, BookmarkDialog, OkCancelDialog and FindDialog dialog pop-ups save and restore their current states during tombstoning.
Back Button Overrides
I wrote an entire book on Windows Phone 7 programming and I didn’t include even one example of overriding the OnBackKeyPress method defined by PhoneApplicationPage. Silly me. The Back key referred to in this method is the leftmost of the three hardware buttons on the phone. By default, the Back button navigates from a page back to the page that invoked it. If an application is on its main page, then the Back button terminates the program.
As I was implementing various dialog boxes in the form of pop-ups, it became obvious to me that I needed to override the behavior of the Back button rather frequently. The general rule is this: Whenever it’s conceivable that the user will push the Back button without desiring to navigate away from the page or terminate the application, you should override OnBackKeyPress.
Any page that can host pop-ups—and in my case that was BookViewerPage, AnnotationsPage and BookmarksPage—should override the Back key to dismiss the pop-up, much like clicking the window Close button in a traditional dialog box. The argument to the OnBackKeyPress method is an instance of CancelEventArgs; set the Cancel property to true to inhibit the Back key from performing its normal navigational function.
If a pop-up is active and a TextBox on the pop-up has the input focus, then an on-screen keyboard will be displayed. Pressing the Back key at this time automatically dismisses the keyboard. Pressing Back again should dismiss the pop-up. Press Back again and you’ll navigate back to the previous page. In all cases, a dramatic visual change gives good feedback to the user that pressing the Back key actually did something.
Watch out when overriding OnBackKeyPress. It should never be possible for the user to get stuck in a loop where successive presses of the Back button don’t terminate the application! You’ll never get the program into the Windows Phone 7 marketplace if this is the case.
Ready for the Front End
In programming, there’s often a big difference between supporting one instance of some entity and supporting more than one instance. Besides the various pages and pop-ups in HorrorReader, the program has made a big leap over my previous e-book readers in letting you read four books rather than just one. Each book has its own BookInfo object that’s stored in isolated memory, so each book is independent of all the rest.
Now all that’s necessary is to replace MainPage with a new front end that lets you download books from the Project Gutenberg site. No other changes should be necessary. That should be simple, right?
Charles Petzold is a longtime contributing editor to MSDN Magazine. His recent book, “Programming Windows Phone 7” (Microsoft Press, 2010), is available as a free download at bit.ly/cpebookpdf. This month marks the 25th anniversary of Petzold’s contributions to MSDN Magazine and its predecessor, Microsoft Systems Journal.
Thanks to the following technical expert for reviewing this article: Richard Bailey