Lab 4: Multi-Page Apps

In this lab we will focus on:

  • Creating individual WPF "Pages"
  • Navigation between WPF Pages
  • Collecting form data from the user and passing it to the next WPF Page or sharing it between all pages

This lab will be due Sunday, Apr. 1st, midnight eastern time.

Introduction

This is our second to last Lab assignment, and we are working on our case study projects on the side for the rest of the semester. The pace of the course is picking up as well, and the Labs will reflect that. They will be structured a little differently, though graded the same. Instead of three "parts" that build upon each other from simple to more open-ended questions, the questions in these last two Labs will be primarily geared towards directing you in building certain components for your case study app. Although everyone's app is unique, the techniques that we'll be learning apply to everyone.

For example, in this Lab will we be leveraging C#'s object oriented programming and the WPF class library to create multiple Pages. A Page, as you might have guessed from the capital P, is a class in C#. This means when we say "Page," we are actually refering to an object type, just like int, string, double, and so on. (Pages are, no doubt, more complicated objects than simple numbers, but we have tools to help us manage that complexity.)

What is also important when we talk about multiple Pages is (1) navigation between pages, since that's the whole point of having multiple pages in the first place, right? (2) collecting data from the user through textboxes, since the first page on a lot of apps is a sign-in or similar screen, and (3) passing data between pages, since each time we navigate to a new page in C#, the previous page and it's data cease to exist, meaning that we have to very purposefully not lose track of that user input data.

The techniques that we will talk about today are simple (as simple as I have been able to make them compared to more advanced techniques you might read about on the web). That being said, there are details, and these details are easy to miss if you aren't paying attention, so if seems like absolutely nothing is working on your programs in this Lab, be patient, start from the beginning, and it's probably one line of code that was mistyped, forgotten, or put in the wrong place.

If you have questions (and someone will have questions), post in Ask the Instructor. I am not going to say "or via email" this time because this is one situation where it will be best to share this discussion with the class.

(If you are on a Mac and do not have access to a Windows machine, let me know.)

Pages

To create a single page (which represents a single "view" of your app) in Visual Studio in a WPF project, press Ctrl+Shift+A to bring up the "add file" dialog. Then select Page and give it a meaningful name at the bottom before pressing okay. If you do not give it a good name at this step, it's more of a headache than it should be to rename the file and change the code in all the appropriate places to match, so take your time on this step.

Next, you should see the canvas as usual, but with a small difference. Up until now, we've only seen the canvas when working with a Window, not a Page. A Window is similar, but different in that it has a height, width, and controls around the border (the exit button, etc.). That means that our Page does not really have a height and width, and will take on whatever height and width the Window that contains it has.

Doing this we can create a Page for as many "views" as we want. But when you run the app you will still only see the default (blank) window. This is because we never told the program to load any of those pages. We have to do this directly in C# code (not using the drag-and-drop tools).

To tell C# to load a page, in general we would use a line of code like:

App.Current.MainWindow.Content = new LoginPage();

We will use this line for navigation below as well, but let's break it down first. This line accesses the global App object that exists in all WPF projects. It then accesses through that App object its Current MainWindow's Content. This is the "stuff" that exists within the borders of our program's window. To fill that content (set it equal to), we create a new copy of our login page (on the right hand side of the equals sign). This might seem a little strange, but remember that it is always possible to create a program with multiple windows, each with its own, distinct copy of the login page. Would we ever do this in our class? I doubt it, and I doubt it's a good design idea altogether. But it is still possible, and therefore the programming language should reflect that. So, we create a new copy of our login page, and set the content (the "stuff") of our main window to point to that.

What this will end up doing is immediately destroying whatever content used to be in that window, along with any values stored in memory. More about that later.

So, how do we use this to load our starting page for the first time?

If you view your MainWindow file (exists by default in WPF applications), you can view the C# code for this file and find code similar to this, where we can put our "App.Current..." line:

using System.Windows.Controls;
using Microsoft.Maps.MapControl.WPF;

namespace ExampleApplication
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            App.Current.MainWindow.Content = new LoginPage();
        }
    }
}

This tells our program to, once everything gets loaded (inside the constructor for the MainWindow), to go ahead and navigate to our login page (or whatever page you want to have load from the get go).

Navigation

First, take the time to get the above technique to work: Create a new WPF application, create a new Page called "HelloPage", put the words "Hello" on it, and set the app to load that page after the InitializeComponent() line. Once you have that working, you have my permission to keep reading.

Now to get the hang of navigating between pages, imagine what should happen when the user clicks a "Next" button. When they click the button, we'll create a new copy of the page we want to navigate to, then point our main window's content at it.

Let's try it: Create a "GoodbyePage" and put the words "Goodbye" on it. Then, go back to the HelloPage and add a "Next" button to it. Double click the button, and you should get code like the following, where we can write a similar "App.Current..." line:

private void NextButton_Click(object sender, RoutedEventArgs e)
{
    App.Current.MainWindow.Content = new GoodbyePage();
}

The moment that the user clicks the next button, our HelloPage will cease to exist and will be replaced with a new GoodbyePage.

Finally, add a "back" button to the GoodbyePage and set it to navigate to the HelloPage. Get this to work and play around with the running program for a little while before continuing.

User Data

Getting User Data

So how do we get information from the user in our C# logic?

When you drag and drop a text box onto your canvas, it adds code to the XAML for the page (usually below the canvas on your screen). To access the text that the user has typed in that box, first you will need to give that text box a name by adding a x:Name attribute. For example, the XAML for a username input box might look like:

<TextBox x:Name="UsernameInput" ... />

Notice the capitalization in this example. It is very important that your uppers and lowers are always correct! This is a common typo problem that leads to many errors and is difficult to track down.

To get the value from this text box in C# of our Page then, we just use the same "name" in a line like:

MessageBox.Show("You typed " + UsernameInput.Text);

For text boxes the property name is Text to get the contents. For other kinds of user controls (checkboxes, buttons, etc.) the property names may differ, but the Visual Studio auto-complete function is very useful to discover these.

Storing User Data

Now, imagine the following scenario. (Getting this program to work is the first question given further below.)

Pretend we have a LoginPage coded that works. On this LoginPage, the user is asked to input their username and password. The values for these inputs only exist within the data for our LoginPage. So, when we navigate to our AccountPage, that data will cease to exist, meaning that the AccountPage will not have access to the username and password, which might prove problematic.

So what do we do?

The simplest solution is to have the LoginPage "write it down" before it is destroyed.

To do this, we can press Ctrl+Shift+A, select to create a new Class file, and give it a meaningful name, such as "Data". We can then declare inside this class the pieces of data that want to "write down" in our program:

public static string Username = "default";
public static string Password = "password123";
public static int Age = 0;
public static boolean WantsSpamEmails = true;

This will create a public static space (accessible from anywhere) in memory for our values (and give each a default value for now). So, before (before is very important here) LoginPage navigates to the AccountPage, we can jot those important values down.

Back in the click event handler for the login button on the LoginPage we'd have:

private void LoginButton_Click(object sender, RoutedEventArgs e)
{
    Data.Username = UsernameInput.Text;
    Data.Password = PasswordInput.Text; 
    App.Current.MainWindow.Content = new AccountPage();
}

This is very similar to how our "next" button worked in the Hello/Goodbye example above.

Try it: Create a new WPF project, add a LoginPage, an AccountPage, and a Data class. Set the program to load the LoginPage from the get go. Add two text boxes and a login button the LoginPage, give all three appropriate x:Name attributes, and set the logic for the login button so when it is clicked we "jot down" the text from the input boxes before navigating to the login page.

Using User Data

When you get this to work you'll notice...nothing happens on the AccountPage. This is correct, since we never told that page to do anything, but also this is boring.

To make use of that data that we jotted down, let's make the AccountPage say a message like "Hello, username here".

To do this, add a text block to the AccountPage and give it an appropriate name, like "MessageBlock". Then, in the C# for the AccountPage, find the constructor. Remember from our readings about classes that the constructor is the method that is run whenever a new copy of an object is created. What we want to happen, when our AccountPage is created, is for the MessageBlock to be filled with the user's input. We can do this like:

public AccountPage(){
   MessageBlock.Text = "Hello, " + Data.Username + "!"; 
}

That's it! By combining these techniques, we should begin to be able to make increasingly complex programs made up of multiple views that load one another and draw from the same, shared Data storage.

Grading and Submission Instructions

To submit, write your responses to the following questions in a Word document, then upload it to Blackboard under this week's folder in Current Assignments.

Labs are due two weeks from when they are assigned, on Sunday at 11:59pm eastern time.

Labs are each worth 5% of your final grade. Scoring on your submission will be based on the following rubric:

0% - Student does not submit on time or submits plagiarized or unacceptable work. Double check that you have attached the right file, as usually students get zeros because they upload a previous week's Lab by accident.

1% - Student answers less than half of the questions with sufficient or accurate responses. Make sure that you are leaving yourself enough time to complete the Lab each week, as usually students submit incomplete work because they were rushed at the last minute.

3% - Student answers almost all questions with sufficient and accurate responses. If you encounter a problem or have a question about one of the questions, be sure to post in Ask the Instructor well before 24 hours before the due date, then continue to attempt to resolve the issue on your own while you wait for a reply.

5% - Great job, maximum points! The student answers all questions accurately and sufficiently, demonstrating the best of their ability.

Note, because Labs span two weeks' worth of reading, it is recommended to go through the Lab twice, once after the first reading where you answer everything that you can, then again after the second reading where you answer everything else.

Questions

  1. Get the Login example above to work. (Create a LoginPage, have the user enter their username and password, and when they click login, a message like "Hello, username here" is displayed on the next screen.) Demonstrate your answer with screenshots of each of your file's C# and screenshots of each stage of your program running.
  2. What are the different "screens" or Pages you expect will be in your app? List them all. How will they connect (what buttons will be clicked where) to one another? Illustrate these screens and connections with a hand-drawn diagram, and embed a scan or photo of your drawing as part of your answer here.
  3. For each of the screens you listed above, what information will you need to collect from the user?
  4. For each of the screens you listed above, what information will you need to use that you have collected from the user elsewhere?
  5. Is there any data that you will need in your program that is not collected from the user? For example, in an app that recommends places to eat to the user, you might want to store a list (array) of restaurants in your area. It might be a better idea to have this data be "built-in" into the app instead of asking the user for it every time.
  6. In C#, what is the difference between a class and an instance? What is the purpose of a constructor? What does the static keyword mean?