Last week I began work on prototyping a new application as part of the Full Stack series. In this application I would like to open a dialog box, retrieve some information from the user, and return that information to the calling page.
There is a dialog box in Windows Phone (System.Windows.MessageBox) but unfortunately it cannot retrieve data entered by the user. No problem, I’ll create a new page, put in a label and a textBox and two buttons (OK and Cancel). The only issue is how do I get the data entered on page 2 back to page 1 (the calling page)?
The answer is to use the NavigationService.Navigate method to add the data in page 2, and the NavigationContext.QueryString property to retrieve the data in page 1. Let’s create a small sample to see this at work.
To begin, open a new Windows Phone application and in MainPage.xaml add two rows. Add a Button to the first row and a TextBlock in the second row. The content for the Button is Retrieve Data From User and the TextBlock should have nothing (i.e., an empty string) in the Text property for now. Set the PageTitle to Page 1,
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28"> <TextBlock x:Name="ApplicationTitle" Text="Sending Messages" Style="{StaticResource PhoneTextNormalStyle}" /> <TextBlock x:Name="PageTitle" Text="Page 1" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}" /> </StackPanel> <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.RowDefinitions> <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> <RowDefinition Height="4*" /> </Grid.RowDefinitions> <Button Name="RetrieveData" Content="Retrieve Data From User" Width="400" Grid.Row="0" /> <TextBlock Text="" Name="DisplayText" Grid.Row ="1" /> </Grid>
Create a second Windows Page. Set its PageTitle to Page 2. Add two rows and two columns. In the first row add a TextBlock whose text is Enter Data, and in the second column, a TextBox named Entered Data. In the second row add a StackPanel and within the panel two Buttons whose content are OK and Cancel respectively.
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <Grid.ColumnDefinitions> <ColumnDefinition Width="124*" /> <ColumnDefinition Width="332*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="1*" /> <RowDefinition Height="1*" /> <RowDefinition Height="4*" /> </Grid.RowDefinitions> <TextBlock Height="30" HorizontalAlignment="Left" Text="Enter Data" VerticalAlignment="Bottom" /> <TextBox Name="DataEntered" HorizontalAlignment="Left" VerticalAlignment="Bottom" Width="300" Grid.Column="1" /> <StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row="1" Grid.Column="1" Orientation="Horizontal"> <Button Name="OK" Content="OK" Width="150" Margin="5" /> <Button Name="Cancel" Content="Cancel" Width="150" Margin="5" /> </StackPanel> </Grid>
In MainPage.xaml.cs create the event handler for the RetrieveData button. The job of this event handler is to navigate to Page 2. (No data is passed from MainPage to Page 2).
public MainPage() { InitializeComponent(); RetrieveData.Click += ( RetrieveData_Click ); } void RetrieveData_Click( object sender, RoutedEventArgs e ) { NavigationService.Navigate( new Uri( "/Page2.xaml", UriKind.Relative ) ); }
Open Page2.xaml.cs and let’s add the logic for passing the user’s entry to MainPage.xaml.cs. To do this we want to respond to either the OK or the Cancel Button, but only if the user clicks OK will we pick up the data in the TextBox. Begin by creating a unified event handler for the two buttons,
public Page2() { InitializeComponent(); OK.Click += ButtonClick; Cancel.Click += ButtonClick; }
The implementation of the click event handler needs to differentiate which button called it, and thus must cast the sender object to an object of type Button (which is what it will really be). Once we have a button we can ask for the Name or Content property to see which button called the handler. If it is OK we can then retrieve the Text property from the TextBox and pass it to Page 1. In either case, we’ll navigate to Page 1.
void ButtonClick( object sender, RoutedEventArgs e ) { Button btn = sender as Button; string msg = string.Empty; if (btn.Name == "OK") { msg = DataEntered.Text; } NavigationService.Navigate( new Uri( "/MainPage.xaml?DataEntered=" + msg, UriKind.Relative ) ); }
The string following the question mark will be passed back to MainPage.xaml in the NavigationContext’s QueryString property.
Return to MainPage.xaml and add an override of the OnNavigatedTo method. In that method you’ll retrieve the QueryString if it exists and add the string retrieved to the TextBlock.
protected override void OnNavigatedTo( System.Windows.Navigation.NavigationEventArgs e ) { string newText = string.Empty; if (NavigationContext.QueryString.TryGetValue( "DataEntered", out newText )) { DisplayText.Text = newText; } base.OnNavigatedTo( e ); }
Note, by the way, that you could use a Border on Page 1 and animate it so that it grows into place and then shrinks away, giving you more of the Dialog Box experience. I’ll cover that approach in an upcoming posting.
I feel several bad smells with this sample but I think most of it coming from keeping the sample simple. One I consider a bad practice. The cancel button is a relict from the desktop, better to use the back button for that on the phone.
I’m astonished that a query string is the recommended way to communicate information between anything other than two network endpoints. Is there really nothing less kludgey?
Ben what is kludgy about a querystring ? I think it is excellent and seeing as the entire URL is on the back stack it makes bringing views back to life after a tombstoning much easier IMHO
I don’t know what kludgy mean, but I am also a bit astonished.
It’s very useful to pass a plain object to a new Page. We can do this in BlackBerry and in Android if the object is Serializable or Parcelable. One does not always want to go through a singleton container to retrieve an object by its Id.
Doesn’t that go against the idea of “Places”(not sure of the correct name) and the idea of keep “Pages” like dialogs off of the back stack.
Doesn’t your example above, that uses Forward navigation to update values onto the querystring, cause the user to be dragged back through the dialog box when they press the Hardware back button?
Yes. The right answer to that problem is to delete the page from the backstack. I’ll work up a sample showing how to do that.