Vous êtes sur la page 1sur 235

Introduction to Object-Oriented Programming Using Visual C# Express Edition Course Assignment Pack

by T. Grandon Gill

Copyright 2008 by T. Grandon Gill Permission to copy for educational use in non-profit institutions is granted, provided that notification of such use is made to the author at ggill@coba.usf.edu.

Contents

Assignment 1

Assignment 1: Part 1
Overview
By the time you've completed the entire Assignment 1, you'll have an application that allows personal information and photos of 4 students to be displayed on a form. In Part 1 of Assignment 1, you will be creating a simple Person class--holding an individual's name, age and photo--and adding it to your project. Once you have done so, you'll be modifying the class on your own to change the public data members to properties (as discussed in the 01.05 section on classes). If you follow the instructions, the entire process shouldn't take more than about 10 minutes. A complete video walkthrough of the project (excepting the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create an Empty Windows Project


Following the same procedure used in Assignment 0, create an empty Windows project. Call it Assignment1. In addition, you may decide that you want to use the same Code Snippets that are used in the walkthrough. These XML files contain code that can be pasted into the application to save typing. To do this, you should:

Download the module sample application from Blackboard, if you have not already done so and unzip it. Place the Assignment1 folder (containing the .snippet files) in a place where you can find it. Load the snippet folder into your application. This is done with Tools | Code Snippet Manager, as shown below.

Once this has been done, you can access snippets through Edit | Intellisense | Insert Snippet. You can also add an Insert Snippet button to one of your toolbars, which I recommend. Since you can always type in code (as opposed to using snippets), these will not be mentioned further in the assignments. Snippet folders will, however, be available for Assignments 1 through 4.

Step 2: Create a Person Class


Using Project|Add Class, add a Class (circled) to your project, using the dialog shown below.

You will be creating a class that corresponds to the following UML diagram (notice that there are no function members):

When complete, your class definition should appear as follows:

It is probably evident to you that you know nothing about how the composed PictureBox object works. Fortunately, as you'll find out, you can often use objects without understanding their implementation. By the end of the project, we'll be using this member to display a photo, all the while having no idea about how it works. You will also find that the PictureBox object is not in the same color on your screen as it is above. That is because C# doesn't know what namespace it is in. To resolve that problem, place the code: using System.Windows.Forms; with the other using namespace commands, at the top of the Person.cs file. Alternatively, you could have declared the member as follows: public System.Windows.Forms.PictureBox Photo; Using the fully qualified names gets old in a hurry, however.

Step 3: Modify Code to Create Properties


Its good to get into the habit of exposing your data as properties, not as public members. Therefore, as shown in Section 01-05 (Classes), you should implement get and set members for the three data members (the Sales member in the lecture and reading is a great prototype). Your class should correspond to the following UML diagram:

10

Step 4: Build Your Project


To complete the exercise, build your project--mainly to see if you made any typing errors (missing semicolons and unclosed braces are common errors).

Step 5: Fill in Report


Fill in that portion of the Assignment 1 report sheet (on Blackboard) relating to Part 1.

11

Assignment 1: Part 2
Overview
In Part 2 of Assignment 1, you will be creating a simple Student class--inheriting from your Person class (created in Part 1) and holding the student's year (e.g., junior, senior) and major (e.g., MIS, accounting)--and adding it to your project. Once you have done so, you'll be modifying the class on your own to change the public data members to properties (as discussed in the 01.05 section on classes). If you follow the instructions, the entire process shouldn't take more than about 10 minutes. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a Student Class


Using Project|Add Class, add a Class (circled) to your project, called Student. Use the : Person after public class Student to make that class inherit from Person. In that class, you should implement:

A Year member, to hold the student's year (e.g., junior, senior) A Major member, to hold the student's major (e.g., MIS) A 5 argument Student constructor function, allowing us to create a student with all its members initialized (i.e., name, age, photo, year and major)

You will see in the walkthrough the creation of a class that corresponds to the following UML diagram (your diagram would be slightly different, as noted below, since you should have modified your Person class to implement properties in Part 1):

12

When complete, your class definition should appear as follows:

Once again, you should build your code to make sure you didn't make any typing errors, but you will not be able to test it. You'll get to that in Part 3.

13

Step 2: Modify Code to Create Properties


As you did in Part 1, change your class so that the two public data members above--Year and Major--are implemented as properties. Your class should correspond to the following UML diagram:

Step 3: Build Your Project


To complete the exercise, build your project--mainly to see if you made any typing errors (missing semicolons and unclosed braces are common errors).

Step 4: Fill in Report


Fill in that portion of the Assignment 1 report sheet (on Blackboard) relating to Part 2.

14

Assignment 1: Part 3
Overview
In Part 3 of Assignment 1, you will be creating a form to display your student data and testing it to ensure it will display information on a single student. You will be adding all the elements to the form (including buttons) but the handlers for the buttons will not be implemented until Part 4, when the project is complete. If you follow the instructions, the entire process shouldn't take more than about 30 minutes. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Add Graphic Elements to Your Form


Use the graphic toolbox to create a form that looks like the following:

15

This form is designed to display data for four different students. If you are working alone, or in a group of less than 4 people, you will have to add non-members to the form (your spouse, your kids, your dog, your favorite chair--it doesn't matter as long as there are 4 to work with). In creating the objects, use the following guidelines:

Rename the form MainWindow (as shown) and modify its title. You will add 4 picture boxes, each laid on top of each other (so only the top one is visible). These should be named according to who they represent. If they are USF students, the name should be pictureBlackboard-ID (e.g., pictureGrandon). If not USF students, choose some other reasonable abbreviations. The buttons should have each person's name displayed (the Text property), and should be named buttonBlackboard-ID (same rules as for pictures). You can use the default names for the labels. The text boxes should be named textName, textAge, textYear, and textMajor. By setting the ReadOnly property to true on each box, you can turn them from white to grey (as shown) and prevent user input.

After you have done this, you should build the project and test to ensure the form displays properly. As it stands, it still won't do anything.

Step 2: Modifying Your Form Class


Right click an empty area of your form to show the code for your MainWindow class. Make the following modifications to the code: A. Add five private Student members As shown below...

The first four should be named using the same abbreviations (i.e., Blackboard IDs) that you used above. The last, sCurrent, will be used to display the currently selected student, and can use the same name.

16

B. Create member functions to display the current student First, we take advantage of the fact that the Hide() member function, applied to any Window object, makes it invisible. Before we can display a specific photo, we therefore hide all the photos. Thus the function is written as follows:

Second, we create a function that updates the displayed data so it displays whatever student is referred to by sCurrent. This is done by:

Hiding all the pictures, using the function we just wrote Using the .Show() member on the picture associated with the current student Assigning the current student's data to the various edit boxes. In the case of the Age edit box, we need to use the ToString() member to turn the integer age into a string, i.e. textName.Text = sCurrent.Age.ToString();

The function is written as follows:

C. Modify the MainWindow constructor Finally, we modify the constructor function to:

Initialize our Student objects Assign sCurrent to the first student Call UpdateForm() so that the student displays

All this needs to be done after the call to InitializeComponent() because we need the PictureBox objects to be initialized before we create the Student objects (otherwise, we'd be passing null values in to our Student constructor). The revised constructor is written as follows:

17

Naturally, your own version will be slightly different--since your member names and data will different.

Step 3: Build Your Project


To complete the exercise, build your project. If you've written the code correctly, you will see the photo and data for the first student on the list displayed.

Step 5: Fill in Report


Fill in that portion of the Assignment 1 report sheet (on Blackboard) relating to Part 3.

18

Assignment 1: Part 4
Overview
In Part 4 of Assignment 1, you implement event handlers on each of your buttons to display the respective students. Upon finishing this activity, the assignment is complete. If you follow the instructions, the entire process shouldn't take more than about 5 minutes. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Add Event handlers


To add an event handler for the "Click" event on a button is simple. Just double-click the button in the form designer. A function will be created and you will be placed in the editor for that function. To complete the project, you will double click each button to add the event handler (the default name is acceptable). Then write code that:

Sets sCurrent to the Student object corresponding to that button Calls UpdateForm() to display that student's data

After doing this for each button, the code should appear as follows, with only the names being different:

19

Step 3: Build Your Project


To complete the exercise, build your project and test it. If you've written the code correctly, you will see the photo and data for each student when you click his or her button.

Step 5: Fill in Report


Fill in that portion of the Assignment 1 report sheet (on Blackboard) relating to Part 4.

20

Assignment 2

21

22

23

Assignment 2: Part 1
Overview
By the time you've completed the entire Assignment 2, you'll have an application that allows you to play a simulation-style game in which you try to complete the MIS major at USF. In Part 1 of Assignment 2, you will be creating an entry screen for the project, where the student playing the game can be selected. In order to do this, we'll need to implement a number of underlying classes to hold our data and to act as stubs to be modified later. Part 1 of the assignment should take about 30 minutes to complete, since it is very similar to the completed version of Assignment 1. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create an Empty Windows Project


Following the same procedure used in Assignment 1, create an empty Windows project. Call it Assignment2. Rename the blank form MainWindow and change its title to "Select Player". Review Assignment 0 if there are any questions on how to do this. You should also rename the Form1.cs file to MainWindow.cs in the Solution Explorer. This will rename all the appropriate files automatically, and is considerably more informative than Form1.

Step 2: Create a "stub" class called Transcript


Using Project|Add Class, add a public class to your project called Transcript. Add a read-only property GPA that returns 4.0. When we return to the class (in a later part of the assignment) we'll replace this code with code that actually computes a GPA. For now, however, we need a stub class so we can create our Student class. The Transcript stub code appears below:

24

Step 3: Create a Student class


We could, as we did in Assignment 1, create a Student class inheriting from Person. For the purposes of this project, however, our students are sufficiently different so it is just as easy to start from scratch. Our student object should be created according to the following UML class diagram:

25

The version of the code used in the demo is presented below:

In looking at this code you should notice a number of things:


It does not conform to the UML diagram because the members (excepting GPA) are not implemented as properties. You must do this in your own version of the class. The GPA property just goes into the Transcript object (record) and returns the value. This is just a convenience, since we could have accessed the same data using myStudent.Record.GPA. We have used C#'s ability to initialize member values upon construction.

Also note the public specifier for the class itself. As has been mentioned earlier, this relates to the availability of the class outside of the project (a.k.a., the assembly) we are creating. If not specified, the class could not be accessed by another program. While this may not seem like a major issue at the present time--since we're not planning on creating a linkable .dll file with our classes for other programmers to use--it does potentially impact other code in the project. This is illustrated in the walkthrough, where the following situation occurred:

The Student class empty definition, as created by the Add New Class wizard, did not include the public specifier (its default). That meant it was private to the project. The MainWindow form, as created by the designer, was declared public--also the default.

26

Within the MainWindow form, a Student member variable (sCurrent) was declared with protected access.

This produced a compiler error because a programmer accessing the MainWindow class from outside the assembly we created could then use the member variable to access the Student class though an inherited object. Had we declared sCurrent public, it would have been even worse, since we could then have created a form object (e.g., myForm) and used it to access a Student object as follows: myForm.sCurrent Doing so, the programmer would have completely circumvented our declaring the Student class private, which is why the compiler doesn't allow it. A similar situation occurred later in the assignment, when private classes were inserted into public collection objects (e.g., Answer, Question, Quiz). None of the problems are very serious, so long as you are able to recognize them and address them by making your classes public (or by making all of them private). Such recognition normally comes in one of two ways: either the autocompletion feature does not seem to be giving you all the expected options (which can also indicate a namespace problem) or through compiler errors, as mentioned above. The problems do, however, underscore the need to inspect your code very carefully should you find that you are getting errors that were not described in the assignment, or encountered in the walkthroughs.

Step 4: Create an Enumeration for your group


Create a public enumeration for the 4 players in your group using the same rules as for Assignment 1 (i.e., Blackboard ID preferred). This should be done at the top of the MainWindow form class. As was the case with Assignment 1, you may invent members if you are working in a group of less than 4. In the example, this enumeration appears as follows:

Step 5: Populate your MainWindow form with controls


Following the same general principles used in Assignment 1, add controls to your form so that it looks as follows (excluding the numbers in boxes, of course):

27

The elements on your form should be as follows: 1. A list box called listPlayers containing the names of your 4 players. These must be in the same order as they were specified in the enumeration. 2. 4 pictures overlaid on top of each other, once again appropriately named (e.g., pictureGrandon, pictureClare, pictureTommy, pictureJonathan in the example). 3. Labels and text boxes. The text boxes should be named textName, textGPA, textWealth and textPride, respectively. 4. A button named buttonBegin. This button will reset the transcript for the currently selected player.

Step 6: Create data members


Create private data members for each student as you did in Assignment 1 and an sCurrent member. Initialize these members (using the one argument student constructor) at the beginning of your MainWindow constructor, as shown: 28

Step 7: Create SelectStudent() function


We are now going to create a function that displays the data for a student based upon the enumeration value passed in as an argument. The function is simple in structure, performing the following 3 activities: 1. It hides all the pictures. 2. Using the switch...case construct (discussed in the lectures and readings) it uses the enumeration to display the appropriate picture and sets sCurrent to the appropriate student. 3. It populates the text boxes based on the values in sCurrent (i.e., the current student). The code for this function--using the example students--is presented below:

29

Step 8: Call your function from the MainWindow constructor and test it
You can now place a call to SelectStudent() below InitializeComponent() in your form constructor--MainWindow()-- as shown in the highlighted area below:

30

Using this, you can test your code by trying different players defined in the enumeration. Once you've ensured this is working, you can proceed to the next step--attaching it to the list box.

Step 9: Enable selection from menu and test it


We are now in a position to tie the display to our menu. We need to link changes to the menu to the data being displayed. As you might suspect, any time the selected item on a menu is changed, an event is generated (a SelectedIndexChanged event, to be precise). Since this is the "default" event for a menu (just as the "Click" event was the default for a button), you only need to double click the menu and the event function is created for you. Alternatively, you can double click the event on the list of events in the menu's property grid. Once you've created the handler function, you can call SelectStudent from within it, as shown:

As indicated above, you need to typecast the index (an int value) to the Players enumeration, which is risk-free since the default enumeration produces integers. At this point, you can build and test your code to verify that clicking a player on the menu changes the display.

Step 10: Select the appropriate index on loading


Although the code would be perfectly functional as written, one aspect is a bit annoying. When the form pops up, it doesn't show the player being displayed as a selection on the menu. This can be easily remedied, however, by adding the code to select the appropriate menu item to the bottom of the SelectStudent function, as highlighted below:

Notice that we need to typecast our selection in the reverse direction--from Player to integer-within this function. Although we could have dispensed with the enumeration altogether in this form and just kept track of the player order in our heads, as our programs grow more complex,

31

the more you can make your code self-documenting (through use of enumerations and well chosen function/variable names), the better you'll be able to manage it.

Step 11: Implement Begin MIS Major button


The purpose of this button is to reset a student's transcript. This is easily done by taking the current student (m_current) and assigning a new Transcript to its Record property. The code for this is as follows:

It is interesting to note that we can do this even though we don't know anything about the Transcript object yet. That is a benefit of encapsulating your code.

Step 12: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 1.

32

Assignment 2: Part 2
Overview
In Part 2 of Assignment 2, you will be creating a multiple choice question dialog screen. In the process, you'll see examples of how classes can be composed together to create more complex classes, as well as numerous examples of if-statements. It will also give you the opportunity to experiment with dialog boxes, message boxes and various controls. Part 2 of the assignment should take about 30 minutes to complete. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create new Answer and Question classes


To hold the data for a multiple choice question, we need to create two classes:

An Answer class, which holds the prompt for one of our responses (e.g., A through E) as well as a Boolean value indicating if the answer is correct A Question class, which can hold up to 5 answers, as well as a question prompt (e.g., What color is the sky?)

UML class diagrams for the two classes are shown below. The diamond relationship shows that Answer objects are composed elements of the Question class.

33

As we will continue to do throughout this course, we are specifying that the data members be implemented as public properties, even though the example code implements them as public data members. You will, of course, need to make this small change in your own implementation. The Answer class can be added using Project | New class. The code in the example-implemented without property members--is as follows:

The Question class code, equally straightforward and, once again, requiring properties be implemented on your part, is as follows:

34

Step 2: Create a multiple choice question dialog box


Once you have created your Question and Answer classes, you should add a new Windows Form to the project, using Project | Add Windows Form. Name that form MultipleChoice. You should place controls on the form so that it appears as follows:

35

You should customize the form as follows (corresponding to the numbers): 1. Change its title to "Make your selection then choose submit" 2. Add a read-only text box, setting its BorderStyle property to None and its TabStop property to false. That will make it appear to be written directly on the form. Name the text box textPrompt. 3. Add a GroupBox control (under Containers in the toolbox) to cover the area where you'll be placing your questions. By doing this, you'll make sure that the radio buttons work together, and that only one can be selected. Name it groupAnswers. 4. Add 5 radio buttons within the group box. Name them radioA, radioB,..., radioE. The Text property on these doesn't matter, since we'll be replacing it with the answer text in the code. 5. Add a button control, name it buttonSubmit, set its Text to Submit (as shown) and set its DialogResult property to OK. 6. Add a button control, name it buttonCancel, set its Text to Cancel (as shown) and set its DialogResult property to Cancel. Build and test the form, although it won't do anything yet.

36

Step 3: Link your Question class to the form


We now want to link the MultipleChoice form to a specific question. To begin, make a private Question member called Q, and modify the constructor so it takes a Question object as an argument. This is important, because we don't want to create the dialog if we don't have a question ready. That modified code (highlighted) will appear as follows:

After we have created all our graphic objects (by calling InitializeComponent() within the constructor, as shown above), we need to replace the text in the various screen controls with the text from the Question object that we passed. One little twist here is that we leave open the possibility that one or more of the answers in our question might be null. For example, in a true/false question, we would pass in null values for answers c through e when we constructed the question. Thus, we need to check for that before taking answer's text and setting it to a control. If the answer is there, fine. If not, we'll hide the radio button it corresponds to (see green highlight below). The resulting code, including setting the textPrompt text (yellow highlight) is shown below:

37

The final step to link our class to the form is to implement a Correct read-only property to the form, allowing us to determine if the user had the right answer selected when Submit was pressed. As you may recall, each Answer in our Question has a Boolean value indicating whether or not it is the correct answer. Therefore, we need to do as follows:

Determine what radio button is selected. See if that button matches a corresponding correct answer in the Question object.

The code for the Correct property can be constructed as follows:

38

Within the get construct for each response, it will:


Determine if an answer is not null. Determine if the answer is correct. Determine if the corresponding radio button is checked.

If all three of these are true for any answer, it returns true. Otherwise, it returns false.

Step 4: Testing your code


Unfortunately, the dialog box we just created is not tied to our MainWindow form in any way, nor is it supposed to be. An obvious question therefore becomes: how can we test it? One easy way to establish our test is to recall that the Main() function for our project is located within the Program.cs file. It follows that we might try launching our dialog right before the main window starts running. To do this, we will need to:

Construct 5 Answer objects. Construct a Question object, then embed the 5 Answer objects in it. Construct a MultipleChoice form, then embed the Question object. Run the form using ShowDialog, as presented in the readings and lectures on Dialog Boxes. Test the Correct member to verify it is working. We can use message boxes, for example, to pop up the test results.

An example of such a modified Main() function, with test code embedded (highlighted in yellow) is presented below:

39

In this code, you'll note that I chose to construct the sub-elements of each class within the constructor. In normal program code, it would have probably made sense to: 1. create 5 Answer variables, constructing each individually. 2. create a Question variable, constructing it by passing in the Answer variable. 3. create a MultipleChoice variable (as was done) and pass it the Question variable. Given that we're dealing with test code, however, this bit of shorthand is probably allowable. The resulting question appears as follows:

40

As part of your modifications, you will need to come up with your own question for test purposes.

Step 5: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 2.

41

Assignment 2: Part 3
Overview
In Part 3 of Assignment 2, you will be creating a Quiz object, inheriting from the List<Question> collection, that will administer a set of questions as a Quiz and return the score. Part 3 should take under 15 minutes to write, excluding the time required to compose the questions for your test code. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Quiz class


Conceptually, we may think of a quiz as being a collection of multiple choice questions that has:

A point count assigned for each question, and A score assigned when a student takes it

This is an instance where what we think a quiz should be translates very nicely into a UML diagram, as shown below:

42

This diagram shows our Quiz object inheriting from a generic List<Questions> class (remember, a quiz is a collection of questions!) with additional members to assign points per question, compute the score and, of course, a member function to administer the quiz. The code to implement the Quiz class is quite simple. In the example shown below, our only departure from the UML diagram is making Score and PointsPerQuestion public data members. As always, this is left to you as a required code modification:

The only code here that is not trivial is the AdministerQuiz() function--which is still pretty straightforward. It does the following:

It initializes Score to 0. It iterates through the collection of questions (and remember, since Quiz inherits from List<Question>, Quiz is a collection--hence the use of the this pointer) with the foreach construct. For each question, we create a MultipleChoice dialog (created in the previous step) to test the user. If the user picked correctly, we add the PointsPerQuestion value to the total score. If not, we leave the total score unchanged. If the user cancels the dialog at any time by hitting the Cancel button, the function returns false. Otherwise, the function returns true, signifying that the quiz was completed.

The only other issue here is that changing the Score property to read-only impacts some of your other code. Specifically, anywhere that the Score value is being set (and, as a helpful hint, those lines are comments), you need to replace the property with the private member name--since the property can't be changed directly. Indeed, this is one of the reasons for defining properties--it allows the compiler to catch errors (such as setting a value that should not be set) in your code, instead of an irate user!

43

Step 2: Testing your code


As in the previous part of the assignment, we'll want to modify the Program class in our project to insert some test code. In this case, the test code is rather unwieldy, so our best approach is to create a function to do the test. An example of such a function, which is called in the Main() function right before our main window is run (as highlighted) is presented below:

In this example, we've created a 5 question quiz, with 20 points per question being awarded. The Add() member of the List<Question> base class was used to place the questions into the Quiz collection. In addition, you may notice that some Quiz elements have null values for some responses (i.e., the 4th and 5th questions). You should be testing this type of thing in the questions that you compose for your own quiz.

Step 3: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 3.

44

Assignment 2: Part 4
Overview
In Part 4 of Assignment 2, you will be creating a Course object--holding data relative to a student taking a course (e.g., term, course number, grade)--and then a Transcript class, inheriting from the List<Course> collection. In your Transcript class, you will be implementing a number of useful functions, such as those for computing GPA and determining if a student is done with the MIS major. Finally, you will implement a form using the DataGridView class that displays a student's entire transcript. Part 4 should take about an hour to write, not including time spent thinking about the code. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Course class


To create a transcript, you need a record of the courses a student has taken. Information that could be useful includes:

The term the course was taken in The course prefix (normally ISM) The course number (e.g., 3232) The student's grade Whether or not the student is currently enrolled

The course object, along with its relationship to the transcript object are presented in the UML diagram shown below:

45

You should begin by adding a new class named Course to the project. The code for the Course object is presented below, excluding property implementations (which you should be able to do in your sleep by now). The one property implementation shown is the one for Grade. For this, logic has been added to set the Enrolled property to false as soon as a grade is assigned. (I'm forced to admit, here, that I didn't think about doing this until after I started working with the class. The nice thing is that since I already had the properties set up, it was a trivial matter!)

46

Step 2: Implement Transcript class


The Transcript class, much like the Quiz class of the previous assignment part, inherits from a List<> generic collection. In this case, a Transcript is a collection of Course objects, so we inherit from List<Course>. What makes a Transcript object more complex is the logic associated with various activities associated with a transcript, including:

Computing a GPA Determining if a student has taken enough courses to graduate, and Determining if a student has the prerequisites to take a specific course

We'll go through these tasks one-by-one. First, however, we'll implement the inheritance from the generic collection and create a public enumeration of courses. While we could get away without this enumeration (just using the course number), if--at some point in the future--we were to decide that we wanted to extend the class to non-ISM courses, we could use this enumeration to modify our numbering system on a global basis. Not to worry, though... I won't be asking you to do this. You should therefore be modifying your already created Transcript class (recall, it was created as a stub class in Part 1, when you implemented the Student class) as follows (modifications are highlighted):

47

Notice that the enumeration just uses the number for the values (e.g., ism3232 is 3232). As mentioned, this could be changed in some later implementation. A. Computing GPA The initial challenge we face in computing the GPA for a transcript is translating the letter grade (A, B, C, etc.) to its numeric equivalent (e.g., 4.0, 3.0, 2.0, etc.). To make matters worse, there are + and - values associated with each grade, but for some they don't matter (e.g., A+ is treated like A) and some grades don't exist (e.g., F- and F+). It turns out, however, that the process of translating grades to numbers is much easier than we might have at first thought. Because we can use literal string objects for our cases, we can simply turn the entire translation into one big case statement. Since this translation doesn't depend upon a specific Transcript object, we can make it static. The resulting function, defined within the class, is shown below:

48

Using this, we could simply iterate through the transcript, add all the individual grade values together, then divide by the number of courses and, voila, we get a GPA. Except... Within the Fall 2006 MIS Programs of Study Guide, there is a statement: Grade forgiveness can be used for only one upper-level MIS course That means that to compute the correct average, we'll need to check each course and see if it is repeated and, if so, use only the second grade for our average. As a starting point for this, it is useful to write a function that determines if a particular course is present, starting at a particular position within our transcript. This function (and another overload) are presented below:

49

These functions both return integers that specify where the course was found in the Transcript collection.

The first version begins searching at nStart in a for loop that uses i as a counter. It looks at each course object (accessed using the this[i] notation) and checks to see if a match is found. If it is, the value of i, the position in the List<Course> array, is returned. If no match is found, -1 is returned. The second version simply calls the first, starting at position 0. This is a convenience, since sometimes we'll want to find out if the student has taken the course. By calling the first function, we make sure that if we've made an error in our code, we only have to fix it in one place.

With our FindCourse() function, we can implement the GPA read-only property. So as not to make the get {} construct too messy, we create a private member function, ComputeGPA(), to do the actual computations. This is shown below:

50

The highlights of the ComputeGPA() function are as follows:


We initialize the bForgiven Boolean variable to false to track of whether or not we've used our one available forgiveness. We initialize gpa to 0.0 and use it to keep a running total of our grade points. We initialize nCourseCount to 0 to hold the number of courses included in our GPA. Because we need to keep track of our position, we iterate using a for loop with i as a counter, instead of a foreach loop. We access each course, using this[i] to assign it to a variable c. Then, if the c.Grade is null or the Grade is "W" (withdraw), we skip it (using a continue keyword).

51

Then, if we haven't already used the forgiveness (bForgiven==false), we check to see if the course appears later in the transcript by calling FindCourse(c.Number, i+1). If it does (a value > than 0 is returned), then we set bForgiven to true (we've used our forgiveness) and skip the course, using continue (as above). Otherwise, we add the points for the grade to gpa with the statement gpa=gpa+GradePoints(c.Grade); At the same time, we increment the nCourseCount variable (using nCourseCount++) in order to keep track of how many grades we've added. Notice that the continue statements mentioned above will, by design, cause these two statements to be bypassed. When we have iterated through all the courses, we need to divide our total (gpa) by the number of courses to turn it into an average. One possible danger is the situation where a student--perhaps in his or her first semester--is enrolled in some courses, but does not have grades for them (in which case nCourseCount will be 0). We use a conditional statement to avoid this in our code, as follows: gpa = (nCourseCount>0) ? gpa / nCourseCount : 0.00;

We have now completed the implementation of the GPA property. As a customization, you should change the logic of the ComputeGPA() function so that it only institutes forgiveness when the later grade is higher than the original grade. As a hint, you can access the repeated course grade using the expression this[nRep].Grade. B. Determining if a student has taken enough courses to graduate According to the Fall 2006 MIS Program of Study guide, an MIS major must take:

Five required courses (Ism-3232, Ism-3113, Ism-4212, Ism-4220 and Ism-4300), and At least three electives (which include Ism-4234, among others)

All of these need to be passed with a C (2.0) or better grade. We'll implement the Done property to check this, using the same approach we used to compute the student's GPA. First, we'll create a modified version of the FindCourse(int) function called FindPassing(int), that looks to see if a student has passed a particular course in his or her transcript. That function is presented below:

52

This function uses the computed grade points (a "C" is 2.0) to determine if the course has been passed, and stops as soon as it has found it, returning the position. Notice that in the test, we check for c.Grade==null before we compute grade points. If we passed a null string into GradePoints(), it would generate a program exception, and null grades may exist (for the courses a student is currently taking). With this function in place we can now implement the property and private function, as follows:

The function is surprisingly simple. First, it goes through the required courses and returns false if a passing grade is missing on any one of them. Then it checks each elective, incrementing a counter (nElective) if a passing grade on the elective is found. In glancing at this code, you might notice that it is impossible for it to return true as written, since nElective can never be more than one. One of the customizations you'll be making at the end of the assignment is to add a few more electives. Obviously, this function will need to be modified at that time.

53

C. Determining if a student has met the prerequisites to take a specific course At the time of the Fall 2006 MIS Program of Study guide, there were two basic prerequisite structures for the major. These are diagrammed as follows:

In addition to the diagram, one additional prerequisite is noted. In order to take Ism3113, students must have either passed Ism3232 or be taking it concurrently. The "taking it concurrently" option means that we need to create another helper function--FindEnrolled(int)-that determines if a student is enrolled in a particular course. (This function will also prove very helpful in later parts of the assignment, when we try to find out if "Players" have met the prerequisites for courses they are taking). It very much resembles our other "Find" functions, except we choose to return the Course object instead of its position (once again, this was chosen based on later uses, and doesn't make much difference here). The function appears as follows:

54

For an explanation of how the function works, you can look at FindPassing(). The key difference here is that the function will return null if the course is not found, instead of -1.

55

The code uses a case statement to separate out the different courses. For the case literals, we typecast the enum values to integers (we also do this for the FindPassing() and FindEnrolled() functions). As previously mentioned, we could have just used numbers (3232, 3113, etc.), but such code would be very hard to read and the devil to modify in the future. Amazingly, we can translate the code almost precisely to the diagram. For example, if you're testing Ism4220 or Ism4212, you check to see if you've passed Ism3113--just as the diagram shows. In the case of Ism4300, we need to check three values--Ism4220, Ism4212 and Ism3232). Although the Ism3232 relationship isn't shown on the diagram, it is implied by the fact that Ism3232 is required for Ism3113. Ism3113 presents another interesting case, since we do a check for enrollment in Ism3232 if a passing grade has not been achieved in that course. Thus we call FindEnrolled() and see if a nonnull Course object is returned. Upon completing this code, you have completed the Transcript class. What remains to be done is attaching the class to a DataGridView object, so we can examine our transcripts and test the code.

Step 3: Implement a DataGridView on a form


The DataGridView class is a remarkable class, as you should have discerned from the lecture and reading. Although it is designed to be used principally with an external data source (i.e., a database), it can also be used to display the contents of a collection. To do so, however, the objects being collected (i.e., Course objects in this case) must have the members to display implemented as properties. Of course, that is what we've been making you do throughout the course--so you don't have to make any changes to your class. To create the form, just:

Create a new Windows form class called TranscriptForm (you should know how to do this by now!) and add a DataGridView object to it. I chose to set its docking property to fully docked, so it filled the entire client area. Using the common task command (or the Solution Explorer), when choosing the data source, select "Add a Project Data Source" to open the Data Source Configuration Wizard, where you will choose "Object" as circled below.

56

Click the "Next" button and open up the Assignment2 project to select Transcript, as highlighted below.

57

Click "Finish" and the data source will be attached to the grid using a transcriptBindingSource object that the wizard automatically creates. When you look at the grid, you'll probably notice that the properties aren't listed in the order you'd like. By right clicking the column headers, you can select "Edit Columns..." The circled buttons on the column editor dialog, shown below, can be used to reorder the columns. Other column properties can be edited as well in the properties box.

58

When completed, you should name the control dataTranscript (as shown) and your form should look something like the following:

The final step to implementing the form involves tying a specific object to it. As created in the designer, we've built a "shell" capable of displaying or editing any Transcript object. To get it to

59

display a particular object, we need to set that object to the DataGridView's DataSource property (FYI, when attaching a DataSet object to the control, you need to set DataSource to the DataSet object and DataMember to the specific table or query to be displayed). This code can be done in the forms constructor, after InitializeComponent() has been called, as shown:

In addition to attaching the Transcript object to the control, we have forced the caller to specify a name to appear on top of the form (the Title argument).

Step 4: Write some test code and display the form


Now that we have convenient I/O, we can display the form and see if it is working properly. To do this, we'll need to add some course elements to a transcript. Using a static function in Main(), the following test code was created:

The resulting display, when the code was run, was as follows:

60

The GPA was computed as 3.5 (which is correct, since the original B in Ism 3113 was forgiven). For your customization, you should create your own test code that:

Tests that a second retake is not forgiven in the GPA calculation. Tests the MeetsPrerequisites() member, testing courses that both do and don't meet the prerequisites. (Note: you'll have to remove some courses from the test transcript to do the latter, obviously).

Step 5: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 4.

61

Assignment 2: Part 5
Overview
In Part 5 of Assignment 2, you will be creating a form that will never be displayed in your assignment, but will serve as the base class for all your "course" forms. The advantages of writing the code in this way are numerous:

We can create a consistent look and feel for all the Course forms We can write the code for navigating between forms once We can save a lot of time creating the forms

Part 5 should take about 45 minutes to complete, since we aren't adding a lot of elements. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new IsmClassForm class


We begin by creating a new Windows form class, called IsmClassForm. You should lay out some controls and graphic objects on that form as shown in the illustration below.

62

The specific elements are as follows: 1. A PictureBox, docked at the top and sides, named pictureHeader. You may choose your own color and size as a customization, or you might choose an image (such as one clipped from PowerPoint). 2. A button object called buttonDrop. 3. A static text label, called labelWelcome, with size, font and font color (ForegroundColor property) suitably adjusted. 4. Another static text label called labelCourse adjusted to the same size, etc. as labelWelcome. (It's probably easiest to copy the labelWelcome control, then adjust its name). 5. A TextBox named textDescription docked at the bottom to hold the course description. Your layout should have the same basic elements as above--plus any others you think you might want--but should look somewhat different from the above.

Step 2: Create a new EnrollmentForm class


Create a new Windows form class, called EnrollmentForm. This will serve as a stub class until the very end of the assignment, but we'll be referencing it by name.

Step 3: Implement form data members and properties


Because we are going to be inheriting from this form, any controls that we might want to adjust are going to be unavailable to us in the child class--since they are declared private and private members are not accessible from child classes. We can address this in two ways. First, we can create public properties for those items in the interface that we think we might want to change in our course-specific class. These include the title bar (Text property of the screen), the Welcome text (Text property of labelWelcome), and the course description (Text property of textDescription). These can be implemented as follows:

63

Alternatively, if we know we are going to need access to the controls, we can change the control's Modifier property, as shown below for the Drop button (circled and highlighted):

64

For example, as noted in the lecture and readings, protected members are accessible in child classes. Thus, if we change a control's properties to protected, the child class can access it. As a general rule, if your child classes need specific access to all the properties of a base class control, it may make sense to place the control in the child class, instead--particularly if you are trapping events. If, on the other hand, you just need to hide the control, or do something non-event driven, then making the control protected is probably a good choice. Finally, we need to declare some data members. Rather than make these private, however, we make them protected--for the same reasons as above. It is much safer than making them public, and they don't need to be implemented as properties. The members we'll need are:

A Student member (m_student), to hold the student object of the player going though our MIS Major maze. A Course member (m_course), detailing the course being taken (so we can assign it a grade). An IsmClassForm member (m_next), which will be the next form we'll open when the current form closes. An EnrollmentForm member (m_home), which will hold our class selection screen. This will be opened if our This will be opened if our player is not enrolled in any more courses (i.e., m_next is null).

The code to implement these members is as follows:

65

To initialize the values, before we display the Window, we need to be sure of two things:

The course number is on the student's transcript with the status of enrolled, and The student has the prerequisites for the course.

We test for this in a series of functions that combine the initialization with validation. Specifically:

In the first, Initialize() we check for non-null arguments, verify that the student is enrolled in the course, then call TestPrerequisites() to ensure the prerequisites are met. If the prerequisites are not met, we:

Pass the user a message. Subtract 5 "pride" points from the student's score, for getting dissed by the registrar. Remove the course from the student's transcript, using a call to the List<Course>.Remove() member that our Transcript class inherits. Return false.

The return value of Initialize() should generally be checked after it is called and before displaying the Window.

66

Step 4: Implement a DropMode within the IsmClassForm


To add to the reality of our "game", we want players to be able to "drop" the class at various points in the proceedings. The first thing we do in this regard is define an enumeration of drop states called DropMode, a member variable (mState) to hold the drop state, a Drop property to ensure we have access to it in inherited forms, and an UpdateDrop() function to ensure the text in the Drop button (on the form) is set consistent with the current drop state (we include a call to it in the set construct of the Drop property). This code is shown as follows:

Should the "Drop" button be pressed, we need a handler. You can create it by double clicking the drop button on the form. The code to implement the handler should be along the following lines:

67

Should we drop early enough, we lose a bit of pride and nothing else. Later, the drop costs us the price of the course (arbitrarily set to $500). In both cases, the m_course is set to null, so no additional point deductions will take place when the FormClose handler is called (see Step 5). When we reach the Withdraw stage, an even greater deduction of pride points takes place. Finally, NoDrop is set when grades are established. For this reason, we deduct the money but leave pride and grades to the code location where the grades are set. We then close the form. The way this is implemented, it is the responsibility of the inheriting form to keep the Drop property updated to a reasonable value. This makes sense, because the interactions in these forms are quite varied.

Step 5: Implementing an OnClosed event


One of the greatest challenges of working with a Window form as opposed to a DialogBox is that you can't necessarily stop the user from closing it before things are done. What we can do, however, is implement a Closed event to ensure the player is punished for doing so in the middle of an interaction. To add this event to the form, we double click the FormClosed property of the form, then add the code that follows:

68

Before doing anything else, be sure to change the private, written by the designer, to protected (see highlight). Because we want this member to work in our child classes, they will need to have access to it. As an added bonus, you may find that having private events in the base class keeps crashing Visual C# 2005 Express Edition once you inherit your form--at least it did mine! As for the code, it can be interpreted as follows:

If this is not a user-initiated closing, such as a system shutdown, then we don't do anything. The event passed in has that as one of its properties, so we can check it. CloseReason is an enumeration. If the m_course variable is null or a grade has already assigned (meaning things are as they should be), then there's no need to exact any further penalty. Otherwise, we set the user's grade to "F" (he or she could have withdrawn by hitting the "Drop" button), and deduct some hefty penalties from the players wealth and pride point tallies.

Once we're done adjusting scores and grades, if m_next is not null, we display it using the Show() member. If it is null, we test if the EnrollmentSelector window that we were sent from is available and, if so, we show it. If not, we just let the current window close.

Step 6: Testing your code


As in the previous part of the assignment, we'll want to modify the Program class in our project to insert some test code. In this case, the test code is rather unwieldy, so our best approach is to create a function to do the test. An example of such a function, which is called in the Main() function right before our main window is run (as highlighted), is presented below:

69

In this test, we:


Create a test student (gill) Add some courses to his transcript Create three new forms Initialize them in the order we want to display them. Notice how f4212 has f3232 as its next argument value, and f3232 has f4220 as its next value. f4220 has a next value set to null, which means it will end the list (and try to open the EnrollmentSelector form that's passed in--except we passed in null for the last argument--since we don't have the EnrollmentSelector form completed yet-- so it will just close)

As a customization, you should create your own test code (which will probably, by necessity, not be too different from the above).

Step 7: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 5.

70

Assignment 2: Part 6
Overview
In Part 6 of Assignment 2, you will be creating a simulated multiple choice test that inherits from our IsmClassForm. Based on the results of that form, various instructor reaction clip art will be displayed. Part 6 should take about 20 minutes to complete, not including any time spent gathering clips or photographs for the reaction screen. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Ism3232 class


Our objective in creating this form is to build it based upon the IsmClassForm. This process is straightforward and involves the following steps:

Add a new Windows form to the project called Ism3232 (the usual drill). Make it inherit from IsmClassForm by changing the : Form to : IsmClassForm in the class definition. Create a three argument version of the Initialize() function (defined in the IsmClassForm) that calls the base class with 3232 as the Number argument (since we shouldn't make the programmer specify 3232 as the course number when creating an Ism3232 object). Initialize the values of the IsmClassForm public text properties (Welcome, Title, Description) that we created in Part 5 to values relevant to the course. You may find that the Description property, which should hold the course description from the USF course catalog (you can go to the USF web site, copy and paste it into your program), extends off the side of the screen--as it does below. This is not a problem.

These changes are shown below, highlighted.

71

You should now be able to view your class in the designer, where it will appear as follows (customized to however you created your IsmClassForm):

The little arrows on the base-class objects signify that they are inherited. When you examine them in the properties Window, they are grayed out--meaning that you can't edit them (since they are private members). But we'll assume that we really don't want to edit our base form, so the situation is fine as is.

72

Step 2: Adding some controls to your form


Next, we want the user to be able to interact with the Ism3232 course form. For this course, we'll implement a multiple choice test, where feedback is provided using clip art (or photos of yourself--which would be way cooler!). To begin, we need to add some controls, shown as follows:

The controls are as follows: 1. A button named buttonQuiz that we'll use to launch the quiz. 2. A series of 5 picture objects named pictureA, pictureB, pictureC, pictureD, and pictureF (note--no E, since that's not a grade) to provide feedback on the quiz score. I used PowerPoint clip art for these, with the pictures as follows:

73

pictureA

pictureB

pictureC

pictureD

pictureF

You should use your own artwork, which could be other clips from PowerPoint (e.g., pasted into Paint and saved as GIF or JPG files) or photographs.

Step 3: Add the quiz logic


You can now add a quiz using the Quiz class created in Assignment 2, Part 3. This should be done as follows:

74

Double-click the "Take the Quiz" button to add a Click handler for it. Create and initialize a Quiz object in your function. I used the same quiz I created in Part 3. You should create a different quiz, however, that must be 5 questions long. Administer the quiz (highlighted in yellow). As you may recall, if the user cancels out of the quiz, Quiz.AdministerQuiz returns false. We'll treat that as an indication that the player wishes to Withdraw from the course, so we'll call the base class. Implement some logic for grading the quiz. I used the Ism-3232 course curve (80+ is A, 60+ is B, etc.). Assign grades to the m_course object and display the appropriate picture. Since we've already verified that m_course is non-null in base class code, we can just go ahead and assign it. This portion of the function is highlighted in green. Hide the quiz button so the user doesn't take it again: buttonQuiz.Hide(). Set the Drop property to NoDrop, signifying the user can't withdraw any more.

The code is presented below:

75

76

Your code should be similar, but you should create your own quiz questions and should give the user some "pride points" for getting an A (and, perhaps, even a B).

Step 4: Testing your code


Since you want to make sure the navigation is working, you don't want the 3232 window to display by itself. In the test code below, the Ism3232 window is placed between two IsmClassForm objects. This was easily done by copying the test function from Part 5 and renaming it, then modifying the lines highlighted in yellow.

Notice that the Initialize() function for the Ism3232 object requires only 3 arguments (since our call to the base class within our function supplies the 3232 course number, as implemented in Step 1).

Step 5: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 6.

77

Assignment 2: Part 7
Overview
In Part 7 of Assignment 2, you will be creating a form that holds a fill-in-the-blanks test that inherits from our IsmClassForm. Based on the results of that form, a grade will be displayed. Part 7 should take about 20 minutes to complete, not including any time spent composing questions. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Ism3113 class


Our objective in creating this form is to build it based upon the IsmClassForm. This process is straightforward, identical to what you did in Part 6, and involves the following steps:

Add a new Windows form to the project called Ism3113 (the usual drill). Make it inherit from IsmClassForm by changing the : Form to : IsmClassForm in the class definition. Create a three argument version of the Initialize() function (defined in the IsmClassForm) that calls the base class with 3113 as the Number argument (since we shouldn't make the programmer specify 3113 as the course number when creating an Ism3113 object). Initialize the values of the IsmClassForm public text properties (Welcome, Title, Description) that we created in Part 5 to values relevant to the course. You may find that the Description property, which should hold the course description from the USF course catalog (you can go to the USF web site, copy and paste it into your program), extends off the side of the screen--as it does below. This is not a problem.

These changes are shown below, highlighted.

78

You should now be able to view your class in the designer.

Step 2: Adding some controls to your form


Next, we want the user to be able to interact with the Ism3113 course form. For this course, we'll implement a fill in the blanks test, where feedback is provided using check marks and color. To begin, we need to add some controls as follows:

The controls are as follows:

79

1. A collection of 5 check boxes (one for each question) with no text. These should be named check1 through check5. You can increase their size by increasing the font size property. You should also set their Visible property to false, so they won't appear until we've graded the answers and make them read-only, so the user can't change them. 2. A series of label text objects with font and size adjusted appropriately to create a pleasing appearance. One the left of the text box (see 3) they should be named labelQ#_1 (e.g., labelQ3_1), where # is the question number. On the right, they should be named labelQ#_2 (e.g., labelQ3_2). You must come up with your own questions, as a customization. 3. A series of 5 text boxes named textQ1 through textQ5 with text size appropriately adjusted. 4. A button named buttonGrade with text size appropriately adjusted.

Step 3: Add the grading logic


The grading of the quiz should be implemented as follows:

Double-click the "Grade Answers" button to add a Click handler for it. Set the Drop property to NoDrop, since we've reached the end of the course. Set a cumulative score variable to 0. Using a series of if statements, grade the answers (highlighted in yellow). Check for reasonable alternatives and use the ToUpper() member so you don't have to worry about case sensitivity. If the answer is correct, add 20 to your score variable, then set the Checked property on the appropriate check box to true. If not, change the ForeColor property on your label to signal the answer was wrong. Use a color other than red as a customization. You can access colors using the Color enumeration, where autocompletion comes in very handy! Implement some logic for grading the quiz. I used a switch...case construct to do this (100 is A, 80 is B, etc.). Assign grades to the m_course object and deduct some pride points for low grades. This portion of the function is highlighted in green. Reveal the check boxes with a series of check#.Show() statements, where # is button number. Hide the AnswerQuestion button so the user doesn't take it again: buttonAnswer.Hide(). Pop up the user's grade in a message box.

The code is presented below:

80

81

Your code should be similar, but you should create your own quiz questions and should give the user some "pride points" for getting an A (and, perhaps, even a B).

Step 4: Implementing more sophisticated drop sequencing


Suppose that we wanted to have the student progress through drop-add and withdraw states. To implement this we keep track of how much editing the student has done, then update our Drop property (which will automatically change the text of the Drop button, as implemented in our base class). We then want to attach an event to our text boxes such that as the user edits them, the drop state is moved to the next level. This can be done by attaching a TextChanged handler to each text box where the user types in answers. Double-click the TextChanged event in the Properties window for your first text box. You can then edit the code, as follows:

82

After you've implemented this for the first text box, you can simply select it (instead of creating a new one) for the other text boxes. This sharing of handlers is a good idea, since it reduces code and the number of places to modify when doing maintenance. Sometimes when you do this, you'll look at the sender argument to handle different text boxes differently. In this case, however, we're just interested in a count and don't care which box generated the event. In a real application, we'd probably want to do a bit more testing--since pressing any key (or just entering a box) produces a text changed event. Nonetheless, you can see how the text in the "Drop" button changes when you start entering text.

Step 5: Testing your code


Since you want to make sure the navigation is working, you don't want the 3113 window to display by itself. In the test code below, the Ism3113 window has been added to our sequence of screens. This was easily done by copying the test function from Part 6 and renaming it, as shown below.

Notice that the Initialize() function for the Ism3113 object requires only 3 arguments (since our call to the base class within our function supplies the 3113 course number, as implemented in Step 1).

Step 6: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 7.

83

Assignment 2: Part 8
Overview
In Part 8 of Assignment 2, you will be creating a form that contains a word-search exercise that inherits from our IsmClassForm. Based on the results of that form, a grade will be displayed. Part 8 should take about 20 minutes to complete, excluding the time it takes to get the word search grid. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Ism4212 class


Our objective in creating this form is to build it based upon the IsmClassForm. This process is straightforward, identical to what you did in Part 6, and involves the following steps:

Add a new Windows form to the project called Ism4212 (the usual drill). Make it inherit from IsmClassForm by changing the : Form to : IsmClassForm in the class definition. Create a three argument version of the Initialize() function (defined in the IsmClassForm) that calls the base class with 4212 as the Number argument (since we shouldn't make the programmer specify 4212 as the course number when creating an Ism4212 object). Initialize the values of the IsmClassForm public text properties (Welcome, Title, Description) that we created in Part 5 to values relevant to the course. You may find that the Description property, which should hold the course description from the USF course catalog (you can go to the USF web site, copy and paste it into your program), extends off the side of the screen--as it does below. This is not a problem.

These changes are shown below, highlighted.

84

You should now be able to view your class in the designer.

Step 2: Adding some controls to your form


The key control you are going to be adding to your form is a GIF or JPEG image of a word search puzzle. Naturally, you can create one on your own. I would recommend, however, going to a web site that creates one for you (e.g., http://www.edhelper.com/wordfind_free.htm). Once you've got your search on the screen, you can capture it (Alt-PrtScrn) then paste it into Paint or some other editing program where you can trim it up. I do quite a bit of this type of activity and have found the program SnagIt (available from http://www.techsmith.com) to be very useful. The word search I created (using the Edhelper site) was as follows:

85

I used the words ENTITY, DATA, SQL, TABLE and ORACLE. To implement the word search, we first set the picture box as the form background image, using the Properties table. Some general comments about this:

Since we have decorative bars on the top and bottom, you may need to resize the form to make the entire picture visible. Don't tile the image, unless you want to create all sorts of boxes for the next step. Eliminate resizing the window by setting the form's FormBorderStyle to FixedSingle, or some other non-sizable value. This is important, since you don't want your form resizing in a way that misaligns the windows you will be creating.

Once you have done this, create 5 empty PictureBox objects, named after the words they will be locating (e.g., pictureSQL), and place them directly over the words they relate to in your word search grid. In the example, these appeared as follows:

Next, create one more PictureBox object, called pictureWords, that completely covers the word search area (hiding everything). Then, in your Ism4212 constructor function, add the highlighted statements shown below:

86

The first 6 statements set the background color of the picture objects--which are entirely background color, since no image has been loaded--to transparent. (Now, what C# defines as transparent isn't necessarily our definition. Rather, it means the control's parent is used to paint the background--and the parent of all these controls is our form. If we put one of the pictures over a button, for example, the button would be hidden by whatever happened to be behind it in the form's background). In the last statement, we move the picture covering the entire puzzle behind our word picture boxes. Since the top object responds to a mouse click, it means that if we click a word, the word picture will respond. If we miss a word, the pictureWords object will pick up the click.

Step 3: Implementing the mouse handlers


Next we implement click handlers on the background picture (pictureWords) and the pictures covering up our database terms (e.g., pictureSQL, pictureEntity-- these will be named differently in your case, since you should use different words in your puzzle). You can double-click the pictureWords Click property to create that handler. For the terms, type pictureFound_Click in the Click handler for one of the pictures. Then attach the same handler to the other 4 terms as we did for Part 7's TextChanged event. Before editing the handler code, add a couple of data members to the class, as highlighted below:

These will be used to establish a grade, according to the following scheme:


You can click the word puzzle area up to 10 times. nClickCount keeps track of all clicks. Each time you click on a term, the nFoundClick count member is incremented.

87

When you reach 10 clicks or have found all 5 terms, your score is computed using the formula: 100-10*(nClickCount-nFoundCount). Under this scheme, you get 100 for 5 correct clicks, and 0 if all 10 clicks were wrong.

In addition, your progress in the course (vis--vis dropping or withdrawing) changes with clicks. The first click moves you into PayDrop status. More than 3 clicks and you're in Withdraw status. After more than 7 clicks, you're in NoDrop status. You'll need to add a function to your Ism4212 class to accomplish this called UpdateDropStatus(). It can be written as follows:

The two handlers are incremented as follows:

Some general comments about these:


The Grade() function is used to end the grading process. The pictureFound_Click member has some unusual code in it. Specifically, the following lines: ((PictureBox)sender).BackColor = Color.Green; Update(); System.Threading.Thread.Sleep(500); ((PictureBox)sender).Hide(); 88

The first three lines are designed to turn the picture box green for half a second to provide the user feedback that he or she has hit a term. Originally, this was not in the code written for the assignment. In playing with the puzzle, however, I found the lack of feedback so irritating that I felt I needed to do something. It works as follows:

The first thing odd about the first line is the typecast ((PictureBox)sender). The reasoning here is that we know the sender will be PictureBox objects--since they are the only controls we've assigned to this handler. By setting the sender's background color to green, we cover the word that was clicked with a green box. Update() forces the form to refresh. Without it, we would never see the green rectangle. System.Threading.Thread.Sleep(500) tells the system to take a break for 500 milliseconds, during which time Windows is free to do something else. In an objectoriented environment, you always need to be aware that multiple processes are running. The static Sleep() function is a convenient way of pausing your application.

The final line hides the picture that was just clicked. That way, the user can't get credit for the same word twice. As a customization, you should modify the pictureFound_Click handler so that instead of hiding the picture window, it puts a border around the word (BorderStyle property). Make sure, however, that you don't allow the user to get credit for clicking the same word twice. (Hint: you can test a control's properties as well as setting them).

Step 4: Add the grading logic


The grading logic is implemented in the Grade() function, shown below:

89

Your code should be similar, but you should create your own scheme and should give the user some "pride points" for getting an A (and, perhaps, even a B).

Step 5: Testing your code


Since you want to make sure the navigation is working, you don't want the 4212 window to display by itself. In the test code below, the Ism4212 window has been added to our sequence of screens. This was easily done by modifying the test function from Part 7 (as highlighted). In this case, renaming it is optional, as shown below.

90

Step 6: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 8.

91

Assignment 2: Part 9
Overview
In Part 9 of Assignment 2, you will be creating a form that contains a web-search exercise that inherits from our IsmClassForm. Based on the results of that form, a grade will be displayed. Part 9 should take about 20 minutes to complete. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Ism4220 class


Our objective in creating this form is to build it based upon the IsmClassForm. This process is straightforward, identical to what you did in Parts 6-9, and involves the following steps:

Add a new Windows form to the project called Ism4220 (the usual drill). Make it inherit from IsmClassForm by changing the : Form to : IsmClassForm in the class definition. Create a three argument version of the Initialize() function (defined in the IsmClassForm) that called the base class with 4220 as the Number argument (since we shouldn't make the programmer specify 4220 as the course number when creating an Ism4220 object). Initialize the values of the IsmClassForm public text properties (Welcome, Title, Description) that we created in Part 5 to values relevant to the course. You may find that the Description property, which should hold the course description from the USF course catalog (you can go to the USF web site, copy and paste it into your program), extends off the side of the screen--as it does below. This is not a problem.

These changes are shown below, highlighted.

92

You should now be able to view your class in the designer.

Step 2: Adding some controls to your form


You should add the following controls to your form:

Four button objects named buttonBack, buttonForward, buttonUSF and buttonInstructions. These will control the browser (except for the last, which just pops up the instructions). A WebBrowser object, called webBrowser. (The control is the white rectangle in the diagram).

The layout of the controls should be along the following lines:

The WebBrowser control's Url property should be set to the USF web site (http://www.usf.edu). This will cause it to load that site when the window opens.

Step 3: Implementing the handlers


Implement Click handlers for the 4 buttons on the side. The first three will just call the WebBrowser controls equivalent message. The fourth will pop up a message box with some instructions. The code to implement these handlers can be as follows:

93

In addition to these, implement a Navigated handler for your WebBrowser control called webBrowser_Navigated. The event is called each time the website displayed in the browser changes (e.g., after a user click). We'll use that to keep track of the user's search for a specific site--both to see if it has been found and for grading purposes. Thus, it also makes sense to declare an nClickCount variable (just as we did in Part 8). The handler code appears below, along with the member declaration. In addition, an UpdateDropState() function--nearly identical to that developed in Part 8 to keep track of enrollment status as the user keeps clicking, is shown:

For your assignment, choose some other USF site to look for and change the Welcome property to reflect it. Some care should be taken when writing the destination website. The easiest way to verify that you're using the exact right form (and I spent over an hour trying to figure out what was wrong when my sites didn't match--one was missing the lead www) is to put a break point in the webBrowser_Navigated handler just before you get to your destination, then inspect the variables as shown below). Also, be aware that the Url property of the browser is a Uri object,

94

not a string. I recommend comparing the AbsoluteUri property--which always gives a full path-as I did above (webBrowser.Url.AbsoluteUri).

Step 4: Add the grading logic


The grading logic is implemented in the Grade() function shown below. It is similar to that of Part 8, except if constructs are used (instead of a case construct) since we could go to as many as 20 clicks, and that would be a lot of cases:

95

Your code should be similar, but you should create your own scheme and should give the user some "pride points" for getting an A (and, perhaps, even a B).

Step 5: Testing your code


Since you want to make sure the navigation is working, you don't want the 4220 window to display by itself. In the test code below, the Ism4220 window has been added to our sequence of screens. This was easily done by modifying the test function from Part 8 (as highlighted). In this case, renaming it is optional, as shown below.

96

Step 6: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 9.

97

Assignment 2: Part 10
Overview
In Part 10 of Assignment 2, you will be creating a form that contains a picture jumbling exercise that inherits from our IsmClassForm. Based on the results of that form, a grade will be displayed. Part 10 should take about 30 minutes to complete, not including the time required to dice a photograph into 9 equal pieces. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Ism4300 class


Our objective in creating this form is to build it based upon the IsmClassForm. This process is straightforward, identical to what you did in Parts 6-9, and involves the following steps:

Add a new Windows form to the project called Ism4300 (the usual drill). Make it inherit from IsmClassForm by changing the : Form to : IsmClassForm in the class definition. Create a three argument version of the Initialize() function (defined in the IsmClassForm) that calls the base class with 4300 as the Number argument (since we shouldn't make the programmer specify 4300 as the course number when creating an Ism4300 object). Initialize the values of the IsmClassForm public text properties (Welcome, Title, Description) that we created in Part 5 to values relevant to the course. You may find that the Description property, which should hold the course description from the USF course catalog (you can go to the USF web site, copy and paste it into your program), extends off the side of the screen--as it does below. This is not a problem.

These changes are shown below, highlighted.

98

You should now be able to view your class in the designer.

Step 2: Adding some controls to your form


You should add the following controls to your form:

A button object named buttonBegin. 9 PictureBox objects that each represent a piece of a photograph. You can use the default names, but be sure you bring them in using an order that you understand.

One easy way to create 9 objects is to take your artwork (a photo or something else) and put it in PowerPoint. You can then create an unfilled rectangle about the right size (1/3 of each dimension) and duplicate it to form a grid, as shown below:

99

You can then screen capture this image, and bring it into a package such as Paint, where the individual squares can be saved as JPG (best for photographs) or GIF (best for text and line art) files. The layout of the controls should be along the following lines:

100

Step 3: Implementing the swap logic


The way the picture program will work, when the user presses the "Begin Playing" button, we'll scramble up the picture. The user can then click two photos at a time to swap them. The program ends when the user has reassembled the picture, or after 20 mouse clicks. The first thing we need to do, then, is implement a Swap() function that switches the position of two pictures. An example of such a function follows:

A more challenging problem is how to determine if a picture has been reassembled properly. One way to do this is to get the elements positioned properly, as above, and record all their positions in an array. Later on, we can then check to see if their positions still match the original positions. The array variable and two member function implementations are presented below. The repetition of code suggests a collection might be a better way to store all the pictures than as individual objects. But at least the code is simple. 101

We now have pretty much what we need to implement the handlers.

Step 4: Implementing the handlers


The button handler is pretty simple to implement. All it needs to do is record the correct order (through a call to SetCorrect()), then scramble up the picture (through a series of swaps). Finally, it can upgrade the Drop state to PayDrop and hide itself. The code is as follows.

102

Since all 9 pictures are going to use the same handler, you can group select the pictures in the designer, then type the Click handler name. I chose pictureGrandon_Click. You should choose a name that reflects the picture you are assembling. The handler begins with the familiar incrementing of nClickCount and moving between Drop states, as necessary. More critically, we have a member variable m_First that keeps track of the first click in a pair. If that variable is empty, then we place the sender (which we typecast to PictureBox, since that's the only type of object that sends this message) in m_First. If m_First already refers to a PictureBox, then we make sure it isn't the same one we just clicked, and then swap them. After the swap, we call CheckValid() and check if the click count has reached 30 (our limit) to see if we need to assign a Grade. If so, we call Grade(), which is implemented in the next step. The code for this is presented below.

Step 5: Add the grading logic


The grading logic is implemented in the Grade() function, shown below. It is virtually identical to that of Part 9, varying only in the number of clicks required for each grade level:

103

Your code should be similar, but you should create your own scheme and should give the user some "pride points" for getting an A (and, perhaps, even a B).

Step 6: Testing your code


Since you want to make sure the navigation is working, you don't want the 4300 window to display by itself. In the test code below, the Ism4300 window has been added to our sequence of screens. Since 4300 has a lot of prerequisites, a new test function--to be used for it and Ism4234, was started.

104

Step 7: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 10.

105

Assignment 2: Part 11
Overview
In Part 11 of Assignment 2, you will be creating a form that contains a multiple choice quiz inspired by a sketch from "Monty Python and the Holy Grail", that incorporates sound files and inherits from our IsmClassForm. Based on the results of that form, a grade will be displayed. Part 11 should take about 30 minutes to complete, not including the time required to create your knight images and sound effects. A complete video walkthrough of the project (except the modifications described below) can be found on the next page.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a new Ism4234 class


Our objective in creating this form is to build it based upon the IsmClassForm. This process is straightforward, identical to what you did in Parts 6-10, and involves the following steps:

Add a new Windows form to the project called Ism4234 (the usual drill). Make it inherit from IsmClassForm by changing the : Form to : IsmClassForm in the class definition. Create a three argument version of the Initialize() function (defined in the IsmClassForm) that calls the base class with 4234 as the Number argument (since we shouldn't make the programmer specify 4234 as the course number when creating an Ism4234 object). Initialize the values of the IsmClassForm public text properties (Welcome, Title, Description) that we created in Part 5 to values relevant to the course. You may find that the Description property, which should hold the course description from the USF course catalog (you can go to the USF web site, copy and paste it into your program), extends off the side of the screen--as it does below. This is not a problem.

These changes are shown below, highlighted.

106

You should now be able to view your class in the designer.

Step 2: Adding some controls to your form


In "Monty Python and the Holy Grail", King Arthur is confronted by a knight blocking a bridge who proclaims the words "None shall pass..." Arthur proceeds to duel the knight, ultimately lopping off all of the knights arms and legs, after which the knight proposes "Let's call it a draw". In this form, each time you click the knight, a multiple choice question pops up. If you get it right, one of the knights limbs gets lopped off. You should add the following .NET controls to your form:

6 PictureBox objects that each represent a knight in various stages of dismemberment. You can use the default names, but be sure you bring them in using an order that you understand. 1 Text object named textComments used to acknowledge concept of the window and to display the knight's words if the audio doesn't work.

One easy way to create the 6 objects is to put your artwork in PowerPoint. I searched for knight under ClipArt. You can then paste it into a program like Paint and cover up the limbs (one at a time) with white, saving as a new file after you sever each limb. All but the first PictureBox should have its Visible property set to false. The six images used in the example are shown below:

pictureBox1

107

pictureBox2

pictureBox3

pictureBox4

pictureBox5

pictureBox6

The layout of the controls should be along the following lines:

108

Step 3: Adding the Windows Media Player control


One of the neatest things about .NET is that it allows you to work with COM controls (also called ActiveX objects), many of which were created with earlier versions of Visual Studio, before .NET. Many Windows programs, including the Windows Media Player that is included with Windows, provide these controls so that you can embed them in your programs. In the case of Windows Media Player which we will be using to play our sound files, the control is not automatically available in our VCEE Toolbox, as shown below:

109

To add it, we right click the "General" area and choose "Insert Items". We then select the "COM Components" tab in the dialog that is displayed (shown below) and check the box next to Windows Media Player (highlighted).

110

A Windows Media Player icon will then appear in our Toolbox. Drag that on to the screen, just as you would any other item on the toolbox. You can use the default name, axWindowsMediaPlayer1. You will then need to adjust the properties as highlighted below:

111

The explanation for these, which can be found with a Google search, is as follows:

112

uiMode: Determines what play controls (e.g., Play, Stop, Pause, Rewind) are shown to the user. none means that there are no user controls. Visible: Hides the control, as we've already seen for countless other controls. windowlessVideo: Means that video can play without its own window, rendering directly to the client area. This one is probably not necessary, but it seems to work with it.

You will be creating WAV files to play in the control. Naturally, you'll need a microphone for these. An easy way to make the recording is using the Windows Sound Recorder, found in Start | All Programs | Accessories | Entertainment. The program shown below allows you to record sound bites and save them as .wav files.

It also has a few effects (accessed through the Effects menu item). These include an echo, which can be used to make your voice sound more sinister. To play a .wav file, you need to set the URL property of the control. Unfortunately, there are a great many things that could go wrong--most notably, the .wav file not being in the right place. Since any error of this sort will generate an exception that could crash our program, we'll place the property in an exception handling try...catch block, as introduced in the lectures and readings. If we get an exception, we'll just put the corresponding text in the comment box. (If we were writing this program for individuals who might have hearing disabilities--or who might be running the program in a lab without speakers--then it would make sense to display the text anyway. But in this case, the example works better with an either/or approach). To implement this, we'll create a function called PlayKnight(), as shown below:

113

Step 4: Implementing the handlers


To get the full effect of this course scenario, we'll need to implement a Shown handler on the form and a handler that can be shared by the knight pictures. We also need to place the sound files where the program can find them. A. The Ism4234's Shown handler The first handler we'll implement is called as soon as the window is displayed, the Shown event. Create this event handler in the properties box, then include a call to the PlayKnight() function so that it says "None shall pass...". Naturally, you'll need to record and name your own .wav file. The function appears as follows:

B. Locating our sound files Since we did not provide a path to the .wav file, there's a very good chance this won't work (i.e., it will display text instead of playing sound) unless you've been very careful. Specifically, the .wav file must be in the same folder as the program. In VSEE .NET, the program is located well under the solution folder, as shown below:

114

Specifically, you would find it as follows: 1. 2. 3. 4. Start at the solution folder. Move down into the project folder. Move into the bin folder, which is where your executable (binary) files are stored. Move into the debug folder, which is used when debugging.

If you want to give your program to someone to run, you'll need all the files in your Debug folder except the Assignment2.vshost.exe and the Assignment2.pdb files, which relate to the Visual Studio project. (FYI, the two Interop files were created to make your .NET project compatible with the Windows Media Player, which is why there's a WM in their name). You could also copy the corresponding files from the Release folder, which are likely to be smaller. C. The picture handler Each time the picture of the knight is clicked, we will ask a question. We will place all the question logic in a function called AskQuestion(), so when we create the handler (which can be done by group selecting the 6 knight pictures, then typing pictureKnight_Click in the Click event in the property grid), it will be very simple, as shown:

To implement AskQuestion(), we first declare some member variables to hold key pieces of information, namely:

nQuestionCount: The number of questions that have been posed. nCorrectCount: The number of correct answers. m_current: The current knight picture being displayed. testQuiz: Used to hold the quiz that the knight is administering.

To give testQuiz a quiz to reference, we also need to create a Quiz object. This is done in the static member function CreateQuiz(), shown below, which was modeled after similar code created for the Ism3232 class:

115

The code for AskQuestion() is shown below:

116

The logic behind the function is as follows: 1. We check to see if the question count has reached 5. If so, we return, which effectively disables the handler when we're done. 2. If we're calling the function for the first time, we create the quiz by calling CreateQuestion() and set our picture to the first (intact) knight drawing. 3. We create a multiple choice dialog box from the current question. Although the Quiz.AdminsterQuiz() member was used in the Ism3232 class to administer a 5 question multiple choice test, here we need to display the questions individually so we can insert the knight's comments. Thus, we are mainly using the Quiz class as a convenient collection. We then increment the question count (since our Quiz collection is 0-based, we need to access elements before we increment the counter). 4. Some logic to move us along the FreeDrop->PayDrop->Withdraw->NoDrop sequence as the user progresses through the quiz. 117

5. We administer the question using the ShowDialog() member of our MultipleChoice class (which inherits from the form, as you may recall). The main branch of the test is where the user didn't cancel the dialog. In this, we check to see if the answer is correct. If so, we: (a) hide the current picture (m_current), (b) increment our correct count, (c) set m_current to the next picture in the sequence along with an appropriate sound bite, and (d) display the new current picture. 6. If the user gets the question wrong, we play "None shall pass..." 7. If the user escapes out of the quiz or closes the question without submitting, we call Close() to close our form. The base class logic then takes over. 8. If we've reached a question count of 5, the quiz is done and we call the function Grade() to grade it.

Step 5: Add the grading logic


The grading logic is implemented in the Grade() function, shown below. It is virtually identical to that in Part 7 (Ism3113), varying only in the curve used:

Also, instead of popping up a message box, the grade is placed in the textComments area. Your code should be similar, but you should create your own scheme and give the user some "pride points" for getting an A (and, perhaps, even a B).

118

Step 6: Testing your code


To test your code, you can reuse the Ism4300 test function, making the highlighted changes:

Step 7: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 11.

119

Assignment 2: Part 12
Overview
Upon completion of this part, you will have completed the entire Assignment 2. If you've followed the instructions carefully, you'll have an application that allows you to play a simulation-style game in which you try to complete the MIS major at USF. In Part 12 of Assignment 2, you will be inheriting from the entry screen that you created in Part 1 of the project, where the student playing the game can be selected. Part 12 of the assignment should take about 30 minutes to complete. In addition, on the second page of the assignment, guidelines for modifying the program logic to reflect current (2008) requirements and for creating your own elective course form are presented. A complete video walkthrough of the project (except the modifications described below) can be found on the final page of the section.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Make your EnrollmentForm inherit from MainWindow


When I originally designed this project I envisioned a separate EnrollmentForm window. When I reached Step 12, however, it occurred to me that I might want to use a student selector (MainWindow) for something else. So why not inherit from it, just to be safe? To implement the inheritance, just change your already created EnrollmentForm class as follows (highlighted).

Inheriting also means that we need to rethink some of our base class members. Specifically, the current student object (sCurrent) cannot be private in the base class, or we cant use it in our EnrollmentForm. In addition, if the selector menu control in the base class remains private, we cant refresh a student's data by calling MainWindow.SelectStudent(). To deal with this, the following changes must be made to the base class (highlighted):

120

By making sCurrent protected, it becomes available to our EnrollmentForm child class. By adding a protected member nSelected and setting it in the SelectStudent() function, we can refresh the student data (e.g., GPA, point scores) whenever we need to in the base class (since SelectStudent() is a public function, and can be called from anywhere). Make these changes to your base class and it should work properly.

Step 2: Add controls to your Enrollment Form


In our EnrollmentForm, we want a player to be able to check off the courses to be taken in a specific semester, then "Register" for them. It would also be nice if the student's transcript could be viewed. To make these accessible to the user, controls should be added as shown below:

121

As you can see, the inherited controls are locked, as indicated by the little arrows. The controls you will add are as follows: 1. A checked list box control called checkedCourses that will hold the list of possible selections. Although any courses can be selected as desired, our transcript logic should prevent us from taking course that we're not eligible to take. The common tasks arrow gives you access to the list of items. Obviously, this should correspond to your courses. 2. A combo box called comboSemester used to select the term (1,2,3, etc.). The user will be required to make a selection (mine goes up to 8), not just make up a number. 3. A Register button called buttonRegister that will launch the player/student into the checked courses. 4. A View Transcript button called buttonTranscript that will display the player/student's transcript.

Step 3: Implement the Transcript button


The Transcript button is relatively trivial to implement, since it simply opens a TranscriptForm dialog attached to the student's transcript (Student.Record property). Double click the button to create the handler and then write the code, which should be similar to the following :

As you may recall, the first element of the TranscriptForm constructor is the title, and the second is the Transcript object.

Step 4: Implement the Registration button


The registration button needs to accomplish the following basic tasks:

Collect the checked items from the checkedCourses box. For each item, create a course enrollment (no grade, currently enrolled). For each item, create a new form (e.g., Ism3113 class, Ism3232 class). Stitch these forms together using their Initialize() functions (as we did in the test code). Launch the forms and hide the EnrollmentForm window until they are done (you may recall that the IsmFormClass base class for the course forms applies Show() to the EnrollmentForm passed into it once the last course has been processed).

The code that accomplishes this is as follows:

122

123

The explanation of this code (by number) is as follows: 1. The value of the index (i.e., the position) of the term combo box is tested. If it is zero or less, we prompt the user to make a selection and return. This was put in because I kept forgetting to set a semester when I was testing. 2. The last member is used to keep track of the previous form created as we go through our list of checked courses. It then sets the next value to our forms. This is similar to what we did in our test code, except it will be accomplished with a loop. 3. The CheckedItems property of our checked list box gives us a collection we can iterate through. How easy is that? 124

4. For each course checked, we (a) create a new form of the appropriate type, (b) add an enrollment record to the transcript for an enrolled course (i.e., no grade, last argument is true), (c) initialize it and, if successful, set the value of last to our form. In the next pass of the loop, that will become the "next" argument form when we initialize. The result of this, of course, is that we'll go through the courses in the opposite order that appeared on the checked list box. But that really doesn't matter. 5. When we've finished creating our forms, we make sure last is not null (in case no courses were checked or the prerequisites of the checked courses weren't met). If it's not, we hide the current form and show the last form, which is the head of the list that we have constructed. If the last value was null, we refresh the selected student since some pride points will have been deducted. This almost completes the application, except for one thing that I discovered when testing it. When you call last.Show(), you are--in effect--launching a separate program thread that proceeds independently of the rest of the function. We need to update the student data when control is returned to the EnrollmentForm. To do this, we can create a handler for the Activate event, which occurs when a window gains focus. In that handler, we can call SelectStudent() to refresh our data. The function looks like the following:

Without this code, each time control returns to the form returned, we will have to switch back and forth between players to refresh.

Step 5: Test your Code


We are now beyond test code; we can run the real thing. To do this, we rename the Main() function so it runs EnrollmentForm() instead of MainWindow(). The old call is commented out and the new call shown below:

125

Step 6: Fill in Report


Fill in that portion of the Assignment 2 report sheet (on Blackboard) relating to Part 12.

126

Revise Prerequisites
In Fall 2007, the structure of the MIS undergraduate major changed. As a result, certain prerequisites were dropped. The assignment will require modifying your code to handle the new structure, shown as a diagram below:

127

Elective Form
The final part of Assignment 2 involves creating an elective course and adding it to your project. Because the intent here is to encourage you to create your own code, no detailed guidelines are presented. We will, however, present some things to think about and offer some ideas.

Things to Think About


When you have added forms in the past, the infrastructure (e.g., transcript handling, checkbox items) has all been prepared for you and every detail has been created in the walkthrough. Having been through the process 6 times, the process of creating a new form (e.g., inheriting from IsmClassForm, writing the Initialize() function, adding objects to the form, implementing handlers and helper functions, implementing grading and, finally, testing the function) should now be familiar. Creating the new elective, however, you should be sure to:

Examine the Transcript class carefully. Most of your changes will be made here-checking prerequisites, accumulating points towards graduation, etc. Examine the EnrollmentForm class, which needs more modest modifications.

Also remember that you should try to adjust pride points and move between DropMode levels as the users interaction with the form progresses. This was one aspect of each form that differed slightly.

General Ideas
We hope that you will design your own interactions with the user for your form. You may create a form that is very similar to what you've already done in Steps 6-11 of the assignment, but it should not be identical. Examples of things that you might do, which should not be too different from what we've already seen, include:

A "Who Wants to be a Millionaire" type multiple choice quiz with buttons for "Ask the Audience" (simulated, of course) and "Remove Two Wrong Answers". This could easily be done by inheriting from the MultipleChoice form and adding appropriate logic. A "concentration" game like the picture game where pairs of identical pictures are hidden in a 3 x 3 (with an empty middle) or 4 x 4 grid, and when a user clicks two paired elements in a raw, they remain exposed. This would be very similar in structure to the Ism4300 exercise. A "wheel of fortune" type game, where the user selects letters from a menu (since we haven't covered how to accept key presses yet, although you can probably guess that it would involve catching the "KeyPress" event for the form) and the letters in a phrase become exposed.

128

A "match game" style interaction, where the form prompts the user with synonyms for a word and the user must type it .

129

130

Assignment 3: Bingo

131

132

Assignment 3: Part 1
Overview
In Assignment 3 we'll be creating a Bingo application that serves a dual function: 1. It serves as a tool for players, who can select called pieces on a DataViewGrid-based card. When "Bingo" has been achieved (all the pieces on a row, column or diagonal have been called), it notifies the user. 2. It serves as a tool for callers, saving a record of calls into a local database. In the first part of the assignment, we will be building the classes required to generate a single bingo card. In doing so, we will demonstrate a variety of useful capabilities--introduced in the lectures and readingsincluding:

Declaration of indexes, allowing a class to act as if it is an array (even if it is not). Overriding virtual functions, the heart of polymorphism. Use of control containers, including the tab control and the window splitter.

Upon completing this part of the assignment, you should have a working Bingo card. In Part 2, the ability to load and save it will be implemented, along with a menu system. In Part 3, the caller side--including the connection to a local Access database--will be added. The procedure that should be used in completing Part 1, excluding customizations and modifications described in the assignment, now follows.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a Cell class


First we will need to create a series of classes to hold the information that will, ultimately, be displayed in a Bingo card. In doing this, we want to be sure that our result is consistent with the DataGridView--since that will greatly simplify our interface implementation. And I mean greatly! The UML diagram for the data classes we'll be creating for the assignment is as follows:

133

Essentially, then, these can be characterized as follows:


A Card is a collection of Row objects (5, to be precise, although we inherit from a List<Row> generic). A Row is a collection of Cell. Each cell in turn can be accessed by index (e.g., myRow[3]) or by column name (e.g., B, I, N, G, O). A Cell is a very simple class that keeps track of the number (e.g., 64) and whether or not it has been selected.

To begin the project, create a new Windows project called Assignment 3, then add a class called Cell to the project. The class definition should begin as follows, and you should then implement public Value and Selected properties:

134

When creating the Value property, you should set Selected to true any time a value of 0 is passed in. This will help in our implementation of the free cell that appears in the middle of all Bingo cards. There is only one thing unusual about the Cell class. One of its members, the ToString() function, is actually implemented in the Object C# base class. We need to override it, however, since its return value will--ultimately--determine what appears in each cell in the DataGridView object we use to display the board. For this reason, we use the override keyword. The member is implemented as follows:

If the cell is not selected, we display the cell's Value. If the cell is selected, we display [Value]. This will make identifying selected cells on our DataGridView easier. If its value is 0, we display "Free". We need this because the middle cell of the 5 X 5 Bingo board is always a free space.

Consistent with this description, the function looks as follows:

Step 2: Create a Row class


The Row class is also very simple, although it introduces some concepts new to our assignments, namely creating an array-style index. This way, we can access individual cells in the row by index, as well as by letter. This will simplify a lot of loops as the project progresses. 135

You should add a Row class to your project, which should begin as follows:

In the constructor, we create a 5 element Cell[] array object, then populate it with new Cell() objects. (Remember, in C#, you not only have to populate the array, you have to create each element.) By now, you should be able to recognize that B is a public property. By defining B, I, N, G, and O properties, we greatly simplify the use of the DataViewGrid later on. When such a grid is attached to an object (as we saw for the Transcript class in Assignment 2), the column names automatically take on the names of the properties associated with each row object. By making these letters, our Bingo display will get the letters as column headers for free. In implementing B, we enabled access to the first Cell in the array (position 0). It follows, therefore, that I should access the second Cell (position 1), etc. The implementation of the I, N, G, and O properties is left to you (copy and paste can be really helpful here). The one new technique introduced here is the implementation of an array [] interface. Even though the Row object is not, properly, an array (although it has one as a member, of course), it certainly makes sense to access it like one in other classes (i.e., the card class). You should be familiar with the concept of defining an indexer property from the lectures and readings. Here, it is implemented as follows:

136

Indexers look very much like properties, with the following subtle differences (highlighted):

Yellow: Since indexers are accessed using object-name[...index...] notation, they don't have their own name. Therefore, we use this as the name. Green: Indexers have arguments (unlike properties), which are placed in brackets (not parentheses, like functions). This makes indexer declarations easy to spot. Light Blue: Indexer arguments can be accessed by name within the get and set constructs. As was the case with properties, a value keyword--which is assumed to match the return type--can be used within the get construct.

In our case, the indexer is trivial. Besides access the appropriate array value the only thing it does is throw an exception if the coefficient is out of range. .NET would have thrown an exception had we exceeded the bounds of the m_cells array, but this way we know exactly what caused the problem in our code. Another version of the indexer can be defined for letters, as follows:

137

This indexer allows access by string instead of by integer, e.g., myRow["G"]. As a customization in your writing of the B, I, N, G, O properties, you should use this indexer.

Step 3: The Card class


The Card class is slightly less trivial than the first two classes--mainly because it contains a lot of "Bingo" logic--but it is still pretty straightforward. It also provides an interesting introduction to randomization.

138

You should begin by adding a Card class to your project and make it inherit from List<Row>. Because you are now familiar with this concept, you can then write a two-element indexer to provide convenient access to cell values within the card. This can be defined as follows:

This is similar to the indexer we wrote for the Row class, with a couple of interesting twists (highlighted):

Yellow: The indexer has two arguments. That means when we use it, we'll do so as this[c,r]--where c and r are appropriate column and row values between 1 and 5. Green: Within the indexer, we refer to individual elements as this[row][col]. The this[row] acquires a specific Row object from the collection (which inherits from List<Row>). Once we have that row, we can then use its indexer to choose the column. This means that within our class, we can access elements either by this[row][col] or by this[col,row]. It is purely a matter of preference.

As a customization, you should create a new indexer of the form: Cell this[string letter,int row] This should access cells by letter and row value (as the arguments suggest!). In other words, myCard["I",3] should access the 4th (0-based!) row of the "I" column. You might also notice that this indexer is not declared public, meaning it can't be used outside of the class members. Having a public indexer available confuses the DataGridView's default display--and not for the better. We'll leave it to you to guess how this aspect of DataGridView behavior was discovered. The next step is to write three similar constructor variations, as follows: 139

In each case, the constructor ultimately calls GenerateCard() with a seed argument that we'll use to start off our random number generator. How we get that seed, however, varies according to the variation:

In the no argument constructor, we create a Random object using the Random() constructor, which--as covered in the lectures and readings--is seeded using the system time, causing it to produce a different sequence every time you call it. We then take the first integer from that sequence--different every time--and use that to seed GenerateCard(). The result will be that every time (well, almost every time, since there are only about 2 billion possible seeds) you call the no-argument constructor, a different board will be generated. In the integer argument constructor, we pass the seed into the GenerateCard() function unchanged. That means that every time we construct with Card(345), for example, we'll always get the same card. As it turns out, we don't even use this constructor in the project, but it seemed like a useful one to have for explanation purposes. In the string argument constructor, we call GetHashCode(), a virtual function defined in the Object base class, then pass that result into GenerateCard() as a seed. This version requires some further explanation.

Without going into too much detail, a hashing is a term for an algorithm frequently used in lookup functions. In the simplest version, an array is used to store the objects in the collection. Their placement is determined by a hash code, which scatters them throughout the entire table (hash tables usually have a lot of empty elements). The characteristics of a hash code are as follows:

For the pool of objects (e.g., string objects) we are hashing, the scatter should be fairly uniform across the range of possible codes (in the case of the C# HashCode() function, the range is all positive 4 byte integers--roughly 0 to ~ 2 billion). If the hash code range is much larger than the table size--and it usually is--this does not normally present a 140

problem, since you can take the remainder of hash-code divided by table-size (i.e., hashcode % table-size) to bring it within bounds. Whenever the hash-code of the same object is taken, it is always the same. Although the coding algorithm's distribution is scattered, it is not random. Similar objects (e.g., "Hello" vs. "hello") typically produce quite different hash codes, meaning that hashing is generally best for exact lookups. Sorting is better if we are looking for near misses.

Since the same string always produces the hash code and since possible hash code values have a range suitable for seed values for Random(), the hash code provides a great way to ensure that we always get the same board when the same string (usually UserName-GameName or UserName-GameName-BoardName) is passed in. That way, we don't have to store the data for every possible board and, should a player lose his or her board, we can always recreate it. The next job on our list is to generate a board from the seed. In a Bingo board, the cells are not completely random. The B column always selects from 1-15, the I column from 16-30, the N column from 31-45, etc. Thus, we can simplify the process of creating a card by generating a column with values from 1 to 15, then adding an appropriate amount to it to transform it into a specific column (i.e., 0 for B, 15 for I, 30 for N, 45 for G and 60 for O). Thus, the first function we'll construct is called GenerateCol(), which has a Random argument passed in to ensure we generate the values according to the appropriately seeded sequence. It returns a 5-element array of Cell objects, each of which has a unique value between 1 and 15. The function appears as follows:

The big problem with creating 5 random numbers between 1 and 15 using a random number generator is there's a good chance well get a duplicate. We avoid this as follows:

141

We create a 15 element List<int> collection where each element is assigned the value 1 through 15. In other words, our available collection will contain {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}. Notice that when we loop (yellow highlight), we specifically don't start at 0. Rather, we start at 1 because Bingo cards don't have zero values. When we choose a random number within the second loop, we do so from a range of 0 to Max--the current size of our available collection (green highlight)--since the rand.Next(Max) function limits the range of the resulting number from 0 to Max-1. That means it is appropriate as a coefficient to the available array and, in the next statement: col[i]=new Cell(available[i]); we assign whatever value is at that coefficient.

We then remove the element we just selected from the array. As a result, in the next pass the available collection size (Max) will be one less and it will be impossible to select the value that we just selected.

With this function available, the GenerateCard() function becomes much simpler to write, as shown below:

The key elements of the function (by number) are as follows: 1. We create a new Random object using our seed. This ensures that all cards generated with a specific seed argument are identical. 2. We create a Row[] array and initialize it with 5 new Row elements.

142

3. We demonstrate the List<Row>.AddRange() inherited base class function, which is just like List<Row>.Add() except that it appends the entire collection at once. 4. We loop through the columns, populating a Cell[] array with column values each time. 5. These are then adjusted by the appropriate amount for the specific column (green highlight)--e.g., B is column 0, so 0 is added, I is column 1 so 1*15==15 is added, N is column 2 so 2*15==30 is added, etc. The this[nC,nR] index notation (yellow highlight) is used to access individual elements. Also, the special case of cell 2,2--the center cell--is assigned a value of 0--which will cause it to display as "Free". By this point, we have enough logic for the card to display. There are, however, some additional functions we can add to the class to make our job easier in later sections. First is the Call function, which allows us to pass in a number and, if it is present on the card, it selects it for us. It can be implemented as follows:

Some aspects of the function to be aware of are as follows (highlighted):

Yellow: We use the truncating property of integer division to determine the column. First, we subtract 1 from the value--since Bingo boards start at 1, not 0. Then we divide by 15. This means 0-14 will become 0, 15-29 will become 1, etc. These correspond precisely to the column. Green: Within the loop that checks the 5 column values, we use our index to access the cells. Light Blue: Should we find a matching value, we set the Selected property of the cell to true.

As a customization, the assignment includes writing your own version of the Call function that takes string arguments representing the pieces (e.g., "B12", "O63"). This is a fairly trivial modification, particularly if you look up the Substring() member of the string class. A similar function is the Select() function. Whereas Call() took a piece value as an argument, Select() is passed in a column, row position--making it even simpler, as shown:

143

There are only two aspects of the function that are remotely interesting (highlighted):

Yellow: It skips the center cell--since it can't be deselected. Green: Unlike Call(), Select() toggles its value between selected and not selected. This is appropriate because we'll be using the function to respond to user mouse-clicks on the grid. Having the selection toggle with each click is an intuitive way to simply the interface.

The final helper function Bingo() determines if the card has reached the "Bingo" state by having a row, column, or diagonal completely selected. The function can be defined as follows:

144

If you are at all comfortable with loops, the logic here should be straightforward. As highlighted:

Yellow: We loop through each row. Within each row, if we loop through all 5 column elements without breaking out (on a deselected element), we've found a fully selected row and return true. Green: We loop through each column. Within each column, if we loop through all 5 row elements without breaking out (on a deselected element), we've found a fully selected column and return true. Light Blue: If all the elements on either diagonal (the \ diagonal and the / diagonal) are selected, we return true. If none of these conditions are met, we return false.

As a customization in your assignment you should rewrite the two diagonal tests as separate loops--instead of large Boolean expressions.

145

Step 4: Adding form controls


At this point, we should be prepared to create our user interface. Start by changing the name of Form1 to Bingo. Then, to make things interesting, we'll begin by adding a few container controls. First, we'll add a Tab Control. Begin by dragging the control to the form, then resizing it and, finally, by adjusting the TabPages collection (through the interface) so that the first tab is named tabPlayer with the text "Player" and the second tab is named tabCaller with the text "Caller". Upon completion, your form should now appear roughly as follows:

Next, you should select the Caller tab and insert a horizontal Split Container tab. Upon completion, this should look something like the following (note the highlighted Orientation property):

146

Finally, return to the Player tab and add a DataGridView. You must now follow the procedure that we've already discussed for this type of form, namely:

Add the DataGridView card to the form calling it dataCardView. Add the Card class to Project Data Sources. Associate the dataCardView control with the Card data source. Edit the columns so they appear in B,I,N,G,O order.

When complete, you will have something that looks like this in the designer:

147

As a customization, you should now go into the various grid properties and make adjustments to produce a more Bingo-like display, with larger text, more rectangular cells, etc. Keep track of what you did, since you'll need to detail this in the assignment. This part of the assignment is intended to be an opportunity for you to explore various properties.

Step 5: Modifying the Bingo form class code


At this point in the assignment, there is relatively little we need to do to the Bingo form class-which will be modified extensively as the assignment progresses through later parts. Specifically, we need to:

Create a Card member called current that holds the card being displayed. Since we'll need to access this from the Caller section, we will also define a public Current property. We need to attach the current object to the dataCardView object.

These are shown below:

148

Some specific comments relating to the highlighted code:

Yellow: When we assign a new current object, we need to change the dataCardView control's DataSource property and call Update() to make the new card display. We build this into the set construct of the property definition so we don't have to worry about it. Green: Sets the data source for the dataGridView to a random card (i.e., the constructor with no arguments). In Part 2, we'll allow specific cards to be created.

Step 6: Adding dataCardView CellContentClick event


To allow the user to select and deselect cells, we'll create a handler for the dataCardView control's CellContentClick event. Create that event handler from the properties box and write the code as follows:

149

The code works as follows:

It gets the row and column of the click from the event argument e. FYI, if you don't know what an argument does, just type it inside the function and press a . (period) and autocompletion will give you a list of members. It calls the Card.Select() function (Step 3) to toggle the selection state of the cell clicked. It calls Update() to refresh the grid with the current data (particularly, the cell you just toggled). If a Bingo state is encountered, which is determined by calling the Bingo() function (Step 3), it pops up a message box to alert the user.

At this point, you can play Bingo with your application. Unfortunately, you can't load or save boards, print them, or come up with a board based on a particular game. These limitations will be addressed in Part 2.

Step 7: Fill in Report


Fill in that portion of the Assignment 3 report sheet (on Blackboard) relating to Part 1.

150

Assignment 3: Part 2
Overview
In Part 2 of the assignment, we will build a menu interface to support our Bingo card application and implement the common activities on the file menu: New, Open, Save, Save As, Print and Print Preview. The procedure used in completing Part 2, excluding customizations and modifications described in the assignment, now follows.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create a Menu strip


First we need to create a menu. In addition, we'll add some text boxes to the menu strip to display our current status. Upon completion the form should appear as follows:

The details are as follows:

151

1. Drag a MenuStrip control between your Tab Control and the title bar. You can populate it by choosing the "Insert Standard Items" common task. 2. Add three text boxes to the strip. Call them textName, textGame and textCard, respectively. If you inspect your menu, you'll discover that you've got a fully populated set of menu items. We'll concentrate on supporting the standard File menu items, shown below, for this assignment.

Step 2: Implement File | New option


In a typical Windows application, File | New creates a blank document. Sometimes, as in the case of Word or PowerPoint, it opens up a menu of forms to allow customizations before the document is created. We will follow that second path (to some extent) by allowing the user to specify a Game, User Name, and Board combination of strings that can be used to create the Bingo card. Prior to this, we need to do a bit of preparation work. First, we create a very simple Game class to hold the strings that define our board. Its UML diagram is shown below:

152

The class mainly consists of matching private and public data properties, with one additional public property, CardCode. After you add the class to the project, you should create the private members as follows (you must define the public property counterparts to these on your own):

Highlighted, we see our first appearance of the [Serializable] attribute. As mentioned in the lectures and readings, this generates code behind the scenes that will be used in our File|Save and File|Open implementations. We don't need it for New, but we might as well put it in. Later, we'll be adding it to our other data classes. The CardCode() member function returns a string object that can be used as an argument for the Card() constructor (see Part 1, Step 3). The Card() constructor then uses a string to determine what board is generated. Since the hashCode() function we use is case sensitive, we use the ToUpper() string member in constructing the code string, as shown below:

153

As the function indicates, a valid code string must include both a name and a game string. The CardName string is optional and can be used to assign extra boards to a player during a game. Our next preparatory task is to add a Game property and m_game member to our Bingo form class. The set member of the property should be attached to the three text boxes we added to the menu, as follows:

Next on the Bingo form you will need to create a new Game object in the Bingo constructor and assign it to m_game. This will exactly parallel what you did for m_card earlier. If you fail to do it, you'll run into exceptions as a result of the null Game property of the form (as demonstrated in the walkthrough). Now we need to create a dialog to allow the user to enter the data for a Game. Add a new form to the project called NameGame and populate it with three text boxes (named textName, textGame, and textCard) along with the appropriate buttons and labels. It should appear as shown below:

154

The code behind this dialog is very simple. For ease of access, we'll give it a Game data member and property (highlighted in green). To make sure the form always has its own copy, we will create it in the constructor (highlighted in yellow). When setting the value, we modify the members of that value and those of the text boxes, rather than just assigning the value game object. This modification insulates what we do in the dialog from whatever was passed in. The code is as follows:

155

Now we've completed all the preparations to respond to the File | New command. Just doubleclick the item to create a handler, then add the code that follows:

156

The code is very simple. We pop up a NameGame dialog and use its results to create a new Card. If the user-name and game-name are specified (and, possibly, the card name) it constructs a card specified on these (based on the value in sCode). Otherwise, it pops up a random card. What is particularly interesting here is all the work going on behind the scenes. For example, every time we set a Game property (highlighted in yellow), three text boxes are updated--in the dialog (top) and in the main window (bottom). Similarly, we set the Current property based on the code we implemented in Part 1.

Step 3: Implementing File | Save and File | Save As


It makes sense to implement the Save and Save As options at the same time because, properly designed, they can share a lot of code. To do this, first drag a FileSaveDialog control on to the form. You can then adjust its properties so it is named saveFile with the default bgo extension for the files. Also, adjust the filter so it shows only those files. These changes are highlighted below:

Next, you need to go to our other data classes (Cell, Row and Card) and add the [Serializable] attribute to the top of the class definition. This should be done right above the public class classname line, as was done for the Game class earlier, in Step 2. The final preparation is to create a typical m_filename string member and a FileName property in the Bingo form class, as shown:

157

Now you can double-click the Save and Save As options on the menu to create the two handlers (the default names are fine). We'll make the saveToolStripMenuItem_Click handler do all the heavy lifting. If FileName is set, it automatically serializes the data. If not, it will pop up the file dialog box. To implement the saveAsToolStripMenuItem_Click handler, we'll just null out FileName, then call the Save handler. Both handlers are shown below:

158

In the Save (saveToolStripMenuItem_Click) handler, the process begins with popping up our SaveFileDialog (saveFile) if we don't have a FileName value. We use the dialog's FileName property to set the Bingo.FileName property (assuming the user didn't cancel out). We then enter the actual process of saving--the hardest thing about which was finding the namespaces where the classes and functions were hiding (these are in the comments and code, for your benefit): 1. We create a BinaryFormatter object, fmt, with binary formatting being the normal way we serialize (as noted in the lectures and readings). 2. We open our file for writing, initializing a FileStream variable strm. 3. Using our formatter's members, we first serialize our m_game (Game) then current (Card) members. These are the only data elements we need to recreate a card. The other data we'll be working with (in Part 3) will be stored in a database, so we don't need to serialize it here. 4. We close the stream so it doesn't lock up other programs or users. As a matter of good programming practice, it would be crazy to have all this file access code running unhandled--since file activities are ripe for exceptions. Therefore, as a customization you should place this code in a try...catch block that pops up a message to the user if something goes wrong. You can and should now save a file. That way, you'll have something to test when it comes to implement your Open function.

Step 4: Implementing File | Open


It makes sense to implement the Save function before the Open function for two reasons:

A lot of the code can be copied and modified (by design). You need to have a saved file to test your Open function.

As a preparation to implementing File | Open, drag an OpenFileDialog control into your Bingo form and call it openFile. Next you can double-click the Open item on the file menu to create the openToolStripMenuItem_Click handler. The handler should be defined as follows:

159

The key thing to remember in writing the Open handler is that a lot of the Save handler code can still be used. Furthermore, the file handling code that needs to be modified (highlighted) is generally in the right place (objects must be saved and opened in the same order!).

Step 5: Implementing File | Print and File | Print Preview


As noted in the lecture and readings, printing can get ugly in a hurry--particularly in a formbased application. The problem here is that even if .NET forms knew what they looked like (they don't), the entire printing process is based on the notion of pagination. The functions that handle printing work by generating one page at a time, then rendering each to the printer. Even for a text document, this can take some work. Having said all this, there are some things about printing that .NET makes reasonably painless. You should begin, therefore, by dragging a PrintPreviewDialog, PrintDialog and PrintDocument control into your Bingo form. Next, you can create handlers for the Print Preview (printPreviewToolStripMenuItem_Click) and Print (printToolStripMenuItem_Click) items on the File menu, by double clicking them. Finally, select the Print Document object (at the bottom of your form) and in the events menu create a PrintPage handler called printDocument1_PrintPage. To repeat the readings, printing occurs through repeated calls to the document's PrintPage handler. Thus, as long as we have a reasonable handler attached to our print document object, we should be able to print. Moreover, since printing and print preview work the same way--they simply direct their output to different devices--if we can print, we should also be able to print preview. But, here comes the messy stuff. In .NET 2.0, forms don't really know how to render themselves to a printer. So we handle this by doing a screen capture of the application window, saving it to a

160

bitmap, then writing that bitmap to whatever device is passed into the printDocument1_PrintPage handler. The code for creating the bitmap is presented below:

Since this involves drawing, we will not focus on explaining it in detail here. In essence, it creates an empty bitmap of the right size (memoryImage), then fills it with the CopyFromScreen() function. More detail is provided in the Resources and Bitmaps and Printing lectures and readings. The PrintPage handler also draws, and is written as follows:

The two File menu handler functions (File | Print and File | Print Preview) are as follows:

The three lines with print in them are completely standard--attaching the document to the dialog, then showing the dialog and, if the user indicates its OK to print, calling the Print() member on the document. The two unusual functions are:

A call to the form's Update() member, getting rid of the File menu, which was otherwise included in the screen capture. The CaptureScreen() function (shown in the text code) call, which takes the form area and copies it into a bitmap.

161

The Print Preview handler is even simpler; the printDocument1.Print() step is omitted (since the print preview dialog takes care of that):

Step 6: Implementing File| Exit


To end a form-based application, you need to close its main window. Thus, you can implement the following handler for File | Exit:

Step 7: Fill in Report


Fill in that portion of the Assignment 3 report sheet (on Blackboard) relating to Part 2.

162

Assignment 3: Part 3
Overview
In Part 3 of the assignment, we will be connecting to an MS-Access database and implementing further functionality that supports the use of the application by an individual who is acting as a caller. The procedure that should be used in completing Part 3, excluding customizations and modifications described in the assignment, now follows.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Create connections to a database


Part 3 of the assignment revolves around saving the pieces called in numerous Bingo games into a database. First we will need to bring in an MS-Access database containing the appropriate table logic Caller.mdb into the project. To begin, download it from Blackboard and unzip it. Then, as described in the readings, bring it into the project by choosing Data | Add new data source from the main menu. This will open the following wizard (which we've already seen when adding a class as a data source in Assignment 2 and earlier in 3):

163

Choose database, then click next. On the next screen, click the "New Connection" button. In the resulting dialog (below), click the "Change" button.

This will open a dialog that allows you to select MS-Access as your data source, as shown below:

164

You can then OK the dialog. Upon returning to the "New Connection" dialog, you can browse for the Caller.mdb database file by clicking the "Browse" button next to the Database file name (shown previously). You can just ignore the User Name and Password options and click OK. Then click the Next button. It will pop up a message about copying the file into your project. Say yes, then click next on the screen asking you about saving the connection string. Now you are on the final screen. Check the Tables box, circled below, to indicate you want to be able to access the Games and Calls tables in the application.

165

Then click Finish. If you then choose Data | Show Data Sources, then Edit DataSet with designer (a button is circled), you can see the table structure of the database.

166

In this case, we see two tables:


Games, containing the Game name, a KeyName and Started and Ended dates Calls, a related table that shows the Sequence (a counter), Piece and Called time for the particular game.

In this case, there is a many-to-one relationship between calls and games--a given game can have many calls (up to 75, in fact).

Step 2: Create a parent-child form in a split window


Our next step is to place two DataGridView controls in a split window control of the Caller tab. Name the top one dataGridGames and attach it to the Games table, using the common tasks button as shown:

167

Name the second control dataGridCalls, and bind it to GamesCalls under the gamesBindingSource, as shown:

This particular binding means that the calls in the second grid are controlled by the game selected in the top grid. You should also uncheck the add property on the Calls control, since we are going to want to control the way information is added. There is a bit of test data in the file, so you should now be able to run your program and test it out. It should look something like the following:

168

As a final bit of cleanup, delete the Edit menu (which won't be used) and all the items on the Tools menu, which we will fill with our own items. Also eliminate everything from the Help menu except About.

Step 3: Create a Caller class


Since we want to be able to use the database for calling Bingo games, we need a class that can make the calls. That class is the Caller class. Its UML diagram is as follows:

169

Internally, it has a Random object (which is seeded in the constructor) and an array of 75 integers (pieces[]) that holds the values for that particular call seed. The class code is as follows:

The Call() function just returns the value of the array at a particular position. The constructor, on the other hand, virtually clones the logic we used in creating a Bingo card, namely: 1. It initializes a Random object using a seed derived from the Game and Key strings we pass in, made upper case. (The idea behind the Key string is that it prevents players who know the algorithm from predicting the call sequence based on the Game name alone. The caller would keep the Key secret). 2. We populate a List<int> collection called vals with the values from 1 to 75. 3. We successively randomly select coefficients from the remaining vals elements, then remove each element once it has been selected. Each time we select a value from the vals collection, we place it in the pieces[] array at the next available spot. Once the constructor has completed its work, we don't need to compute further calls. As long as the Caller object is available, we can just look up what call would be made at any point in the sequence of calls.

170

Step 4: Create GetInteger dialog


Another supporting class we need is a dialog that prompts the user for an integer between 1 and 75. This allows manual calls to be entered. We can also use it in some of our other features (such as to generate a certain number of calls or keep calling until you reach a certain number). This is a fairly trivial task, but it needs to get done. The dialog should be called GetInteger and should be laid out as follows:

The dialog should have a Piece read-only property that takes the value in the text box and converts it to an integer. In addition, you should handle the Leave event of the text box and--if the value is not between 1 and 75--alert the user with a message box. The code to implement this could be written as follows:

171

As an extension to this code, you should also allow the user to put in a piece (e.g., "I17", "G55") and return the associated value (17, 55). The Substring() member of the string class can be very useful in this regard. You should also implement exception handling, so the dialog doesn't crash the program if the user enters a value that doesn't translate to a number.

Step 5: Implement single call logic


Add a "One Call" option to the Tools menu (which you should have emptied). Double-click it to add the event handler. This will handle the situation where the user wants to call a single number. There are two ways that this could "go down":

The user could be a caller who knows the key for the game. In this case, the Caller class needs to be used to generate the call. The user could be a player who is recording each call. One reason to do this might be to run the calls against multiple cards in the same game (which we implement later). This is very common in the social set that gets into Bingo in a big way. In this case, the number must be entered manually.

We use the game row to determine which to use. If the KeyName field is empty, we make the call manual. Otherwise we go automatic. The code for making a single call is as follows:

172

As an explanation: 1. If the Caller tab is not selected, we just select it and bring it to the front. Presumably, the user doesn't want to make a call without knowing what is selected. 2. We use the CurrentCell.Y property to find out what row we're on in the Games grid. If no row is selected, or if its greater than or equal to the number of rows in the grid, we alert the user and return. We need to be sure a row is selected, so the calls row displays the right calls. 3. We get key and game strings from the current row of the Games grid. These will determine how we make the call. We get our sequence number (for the next call) by counting the number of rows in the child calls grid. 4. If the key is null, we call GetInteger() to make a manual call. 5. If the key exists, we create a Caller object based on the game and key values. We then get the piece that should be called at that point in the sequence. 6. We iterate through the calls grid to find out if there are any matching calls. This should only be an issue where the calling is being recorded manually. You will find this useful, however, if you keep calling pieces on the test data long enough. 173

7. We add the row to the Calls table in the DataSet object that underlies the grid (check the readings and lectures for an explanation of the role played by the DataSet class in bridging between databases and DataGridView controls). With this code in place, the application appears to work fine. At the end of running it, however, you'll discover that the underlying Caller.mdb Access database has not changed (you'll find the relevant copy in the bin\debug folder under your project). Thats because modifying a DataSet object doesn't automatically update its associated database. You could choose to update after each action--which might be the safest way. On the other hand, you might update when the program ends. To do this, you can create a handler for the Bingo form's Closing event with the following code:

The Update() method of a table adapter--which acts as a bridge between the database and the associated dataset--causes changes in the DataSet to be translated into SQL commands, making changes to the actual database. Since the documentation specifically warns us that such updates can generate exceptions, we perform the code in a try...catch block so that the user is alerted should a failure to update the database take place.

Step 6: Implement multiple call logic


There are two forms of multiple call logic to be implemented. The first option is to issue all the calls up to a specific sequence number (e.g., the first 25 calls). The second option is to keep calling until a particular piece is called (e.g., call until N33 is called). These routines were developed so that a caller (e.g., a professor making calls on Blackboard) could make calls from any computer with the software, provided he or she knew either: a) how many calls had already been made, or b) the identity of the last piece called. (Naturally, the game name and key name also need to be known). Add two items to the Tool menu, Multiple Calls and Call Until and create handlers for both. The code for the MultipleCalls handler is shown below. As a customization, you will be required to implement the Call Until handler--which is nearly identical--on your own.

174

The multiple call routine is very similar to that of the single call routine, with just a few exceptions. The overall logic is as follows:

175

1. As previously done, If the Caller tab is not selected, we just select it and bring it to the front. Presumably, the user doesn't want to make a call without knowing what is selected. 2. As previously done, we use the CurrentCell.Y property to find out what row we're on in the Games grid. In testing this routine, I discovered that I almost always generated multiple calls after adding a new row to the DataGridView object. It turns out that if I did not leave the row, the edits didn't reach the underlying DataSet before I made my calls. To get around this, I forced a row switch by changing the CurrentCell property to the top and back. That seemed to do the trick. 3. As previously done, if no row is selected, or if its greater than or equal to the number of rows in the grid, we alert the user and return. We need to be sure a row is selected, so the Calls row is displaying the right calls. 4. As previously done, we get key and game strings from the current row of the Games grid. These will determine how we make the call. We get our sequence number (for the next call) by counting the number of rows in the child calls grid. 5. If the key is null, we pop up a message box and return. Only automatic calling is allowed for multiple calls. 6. If rows already exist, we need to erase them since we don't know where they came from (and duplicate calls could result). We alert the user to this and provide the option to stop, using a Yes/No MessageBox. If the user responds "Yes", they are deleted from the underlying DataSet. This is the most interesting block of code in the routine because it requires us to access the underlying data set from the data grid view--since we can't just delete all the rows in the Calls table of the data set, since many of them don't involve the current game. To do this, we use the DataBoundItem property for the DataGridView row (highlighted), This is the normal way to get a source data from within a DataGridView object. We then call the Delete() member of the Row object. This is different from simply removing a row, since the row is marked for deletion and will be removed from the underlying database on the next table adapter Update() call (which we do when the form is closing). 7. We prompt the user for the number of calls to make, using the existing GetInteger dialog. We change its title by altering the Text property to makes its purpose a bit clearer. 8. If the user doesn't cancel when prompted for a number, we create a Caller object based on the game and key values. We then iterate through the calls as many times as specified, adding each one to the DataSet. Remember, you are to implement the "Call Until:" logic. (Hint: Everything is the same until (7) above.)

Step 7: Implement card checking logic


The final option to be implemented is checking an arbitrary board with a specified set of calls. To do this, we must:

Identify the set of calls to be used (using the row selected in the top grid). Generate a board by prompting the user for Name, Game and CardName. Make the calls from our calls child table and update the board. Display the board.

176

Prior to writing the handler code, there is one user-interface nicety to accomplish. In order to prompt the user for a particular card, we want to reuse the NameGame dialog. We do not, however, want the user to be able to change the game string (which will be set based upon our row in that grid). So, we go into the NameGame dialog code and add a GameLocked write-only property that sets the NameGame.textGame box to read-only, as follows:

After adding a Check Board option to the Tool menu, then double clicking it to add the handler, the code to accomplish checking the board can be written as follows:

177

The code is explained as follows: 1. As previously done, If the Caller tab is not selected, we select it and bring it to the front. Presumably, the user doesn't want to make a call without knowing what is selected. 2. As previously done, we use the CurrentCell.Y property to find out what row we're on in the Games grid. If no row is selected, we alert the user and return. We need to be sure a row is selected, so the calls row is displaying the right calls. 3. As previously done, we get game strings from the current row of the Games grid. We don't need a key here because we won't be generating calls. Instead we'll just use whatever calls have already been registered in the grid. 4. After creating a NameGame dialog with the game value locked--based on our current row, we query the user for name (and card name, if available). If the user okays the dialog, we get the CardCode from the dialog. 5. We generate a new card using the card code. We then iterate though the rows of our calls grid, applying each call to the card using the Call() member function. 6. If a "Bingo" is detected after making the calls, we alert the user with a message box. We then prompt the user with a Yes/No message box as to whether or not to display the card. 7. If the user wants a card display, we set the card we just created to the Current property (updating the bingo card display), then switch to the Player tab. Upon completing this, you have one final customization. That is to create your own "About" box (just create a form with an OK button and fancy it up). It should have the names of your group members in it and a version number, which you can make up.

Step 8: Fill in Report


Fill in that portion of the Assignment 3 report sheet (on Blackboard) relating to Part 3.

178

179

Assignment 4: The Great Aquarium Escape

180

181

Assignment 4: Part 1
Overview
In Assignment 4 we'll be creating the "Great Aquarium Escape" application. This application implements a graphic-intensive (too graphic-intensive for Windows, really) video game in which fish swim, crabs jump and the user tries vainly to keep them in the Aquarium by clicking on them. At least, in my case, it was in vain The completed project illustrates a number of key concepts: 1. 2. 3. 4. 5. 6. Use of bitmaps in a program. Use of polymorphic objects. Implementation of a menu tool bar. Use of some advanced capabilities of the DataGridView control. Basic steps for serialization. Basic steps for printing.

Upon completing this part of the assignment, you should have a working aquarium display, and be able to play the game. In Part 2, we will focus on developing a menu system and implementing dialogs to customize the playing experience. In Part 3, we will implement the "File" menu items for loading, saving and printing. The procedure that should be used in completing Part 1, excluding customizations and modifications described in the assignment, now follows.

Instructions
After creating a Windows form project called Assignment4 and renaming the Form1.cs file to Aquarium, you will perform the following steps.

Step 1: Create a GameSpecs class


First we need to create a class to hold a collection of parameters that will, ultimately, define our game experience. These parameters are as follows:

TickDuration: This is how long, in milliseconds between moves for the fish. For example, 500 would be half a second. In theory, the smaller this is, the smoother the display will be. In practice, Windows can't refresh that fast (that's why "real" games bypass Windows drawing and use another tool, such as DirectX). PointsPerTick: You get points for keeping fish in the aquarium. This is how much you get for each fish every time the clock ticks (see above). LostPointsPerHide: This is how many points you lose when your fish swim off either side of the aquarium (or your crabs hit the top or bottom).

182

SpeedIncreasePercent: The speed of the fish increases every SpeedIncreaseInterval (see next setting). This is the percentage increase (e.g., 0.2 is 20% increase). SpeedIncreaseInterval: The number of ticks between speed increases. TicksHidden: The number of ticks a fish remains hidden after hitting the side of the screen. GameLength:The number of ticks in a game. StartingPoints: How many points a player starts with each game. TopSpeed: The "top speed" of a fish. When fish are created, this is the maximum speed used in creating a random speed value. TotalPoints: The current player's total points. This is not a specification, but the class was a convenient place to hold the value.

The UML diagram for the GameSpecs class we'll be creating for the assignment is as follows:

Every property in this class is a "standard" property (nothing special in the get and set), so after you add the class to your project, you can add these based on the specification. 183

Step 2: Finding some fish pictures


To make this project more visually interesting, we need to get some pictures of fish. Moreover, these fish should be on a plain background so that we can make it transparent. To find such images, Google is a good place to start. After searching for Free Fish Clipart, I found a good site (see highlighted site below).

This site, Classroom Clipart (shown below), has dozens of royalty-free images highly suitable for our task. You should probably download 5-10 of them (try not to duplicate the ones I used). Also, be sure to get a crab or two!

184

Once you've got a supply of fish bitmaps, you need to bring 6 of them, including a crab picture, into your program. A nice self-contained way to do this is to bring them in as resources (meaning they will be compiled into our executable, and we won't need separate files). To do this, we need to add a resource area to our project (right click the project and Add | New Item), then select Resources File, as shown below:

185

Once you've got the resource editor open, you can choose Add existing file to resources and select your image.

186

For consistency with the code, you should name your images Fish01 through Fish06 as shown below, making the crab image Fish06--although any name would work. Try to use different images from the ones shown here.

Step 3: Create a Fish class


The Fish class presents two challenges: 1. To incorporate the logic to make our Fish objects "swim". 2. To ensure that the class is built in a manner that ensures that it is serializable when we get to Part 3. The second step presents the largest challenge. Since we are going to make our Fish objects appear to swim by moving a PictureBox containing a Fish image, a good thought might be to make Fish inherit from the PictureBox class. Unfortunately, the PictureBox doesn't serialize--as you would find out in Part 3 (I'll leave it to your imagination how I determined this). So, the alternative is to give our Fish class an interface that wraps around a PictureBox. Specifically, we'd like to be able to access the following properties and functions:

int Height:Gets or sets the height of the box int Width: Gets or sets the width of the box Point Location: Gets or sets the position of the box Size Size: Gets or sets the width and height of the box

187

bool Visible: Gets whether or not the box is visible string Name: Gets or sets the name of the box void Hide(): Makes the box invisible void Show(): Makes the box visible

In addition, we'd like to create our own PictureBox class that includes a reference to the associated Fish object. This means that when a fish picture is clicked, we look at the sender object in the handler to find the associated Fish object. We do this by creating a FishPicture class. The UML diagram for is as follows (with the wrapper functions highlighted):

188

A good way to proceed with writing the code for the Fish class is to begin by creating an empty Fish class (using Project | Add Class), then go right to adding a FishPicture class the same way. That class should then be created first, since it is relatively trivial.

189

The FishPicture class is trivial in nature As shown below, it inherits from PictureBox (highlighted) and holds one thing: a property reference to the Fish object that holds it:

Returning to the Fish class, we can proceed to fill it out. One easy place to begin is the highlighted wrapper functions in the UML diagram, shown in code below. They assume the FishPicture object is accessed through a Box property and they fall into two categories:

They simply call the underlying picture box function or property (highlighted in green). They have a data member that mirrors the value of the Box member (highlighted in yellow), in which case the Box value is set at the same time as the data member.

190

191

Some of the properties, such as Height and Width, also access a third property--e.g., the Size object. Thus, these properties may be considered purely a matter of convenience. We can then proceed to enter the code for constructing a Fish object and establishing the linkage to the FishPicture object. You may find when you compile that some of the classes we use (e.g., Bitmap, Point, PictureBox) aren't covered by the standard using statements .NET puts on top of a generic class. For this reason, you'll want to add the following to the top of your Fish.cs class file (and, later, to the Crab.cs file):

192

using System.Windows.Forms; using System.Drawing; As already noted, the Box property provides access to the underlying FishPicture (PictureBox child) object. The property function is not entirely trivial, because Fish objects can be created in two ways (highlighted in yellow, below):

Through the new operator, in which case it is done using the constructor (top yellow highlight). Through serialization (i.e., being loaded from a file, when the user chooses File | Open). In this latter case, the constructor is bypassed and--as a result--no FishPicture is created. To get around this, we check to see if the m_box member is null when we access the get member. If it is, we go through the same construction process we used for the constructor. Since the only way to get at the FishPicture is through the Box member (even within the class, we limit ourselves to that access, except in the constructor), we ensure a FishPicture is available whenever we need it. This is a typical example of a situation where defining properties saves us a lot of headaches.

193

We also need to do a lot of initialization for our FishPicture, in addition to creating it. Some involves settings that can be adjusted whether or not we have a Bitmap loaded. These, highlighted in light blue, ensure we can see through transparent areas of the Bitmap and that it

194

won't become a tab stop when loaded on the form. Zoom mode is also set, meaning we'll be able to see the whole picture. If you forget how to set these properties in code, the easiest way to find out how to set them is to plop a PictureBox on an empty form, set the properties you want, then look at the code that was generated in the Form Designer. When we actually attach the Bitmap (i.e., the fish picture we've chosen), we also need to set some properties (shown in green). First, on the bitmap itself, we need to identify what color is treated as transparent. To do this, choose a pixel you know is that color, then make that the transparent color. In the code, the program takes the pixel at position 1,1, and assumes it is part of the background. This is important, since it means you've got to choose an image on a plain background (such as the clipart I used, where the background is white). If you were to do this on a photo, it would have little effect--since only places where that exact color appears would be transparent. Once we've set up the bitmap colors, we call ComputeSize() to set the size of the image. That function, shown below, simply takes the height and width of the bitmap and multiplies it by our PercentSize factor to come up with the FishPicture dimensions. This means we can take bitmaps of any size, instead of having to resize them in a graphics program before we bring them into our program. (Recall, we set the picture box to Zoom mode, so the bitmap will always be displayed at a size that fits the picture box).

195

We also need to be able to load bitmaps into our Fish. We want to be able to do this in two ways: from a file or by copying another bitmap (e.g., using the resources we loaded in Step 2). The code to do this is entirely straightforward, as discussed in the lecture and readings. It is shown below:

The only difference between the two is that in the first we create a new Bitmap by using the string constructor (which creates it from a file), while in the second, we clone the Bitmap passed

196

in as an argument. We need our own copy because we'll be reversing the fishs direction when they reach the edge and don't want to mess with the resource. It is also possible if we create a number of fish from the same resource, we don't want them all turning at once. Two remaining properties need to be defined before we can turn to the two remaining functions-Swim() and Reverse()--that actually determine how a fish behaves. These properties are Speed and Tank, defined below.

These properties perform the following functions:

The Speed property determines how many pixels a fish moves per clock tick. The larger the number, the faster the fish moves. It can be positive (left to right motion) or negative (right to left motion). The Tank property is a rectangle specifying the boundaries of our aquarium tank (its members include Left, Right, Top and Bottom). With this property, we could limit our fish to a portion of the screen.

Up to this point, every function we've defined and property we've created has been provided for "housekeeping" purposes--to get our bitmap properly configured, to establish our window's size and position, etc. The last two functions are different, however. They determine the "behavior" of a fish. For this reason, they are defined to be virtual--meaning we expect to override them if we inherit from Fish (as Crab will do). Change the function and you change the behavior of the object.

197

The Reverse() function is relatively simple, so we'll explain it first:

Basically, since our fish swim horizontally, to reverse them requires two actions:

Flipping the bitmap left-to-right, so our fish isn't swimming backwards. That is done with the RotateFlip() function, which--based on the argument we passed--rotates it 180 degrees (so it is upside down, facing the opposite direction), then flips it vertically so it's right side up again. Reversing the speed, accomplished by multiplying the previous speed by -1.

Naturally, reversal would be somewhat different if a different behavior (e.g., swimming up and down) was intended. The Swim() function is the final function in the Fish class, and pretty much captures the behavior of our fish. It is defined as follows:

198

The code begins with a declaration of a protected member (nTickCount) that can be used to count the number of clock ticks that a fish has been hidden. (As you may recall, the GameSpecs class defines a parameter which determines how many ticks a fish waits after it has hit the side of the tank). Passed into the function is a tick counter (which keeps track of the timer that we're using) and a GameSpecs object, used to set the value of a variety of parameters. The logic inside the function (by number) is as follows:

199

1. If the fish is hidden, we check the number of ticks since it was first hidden. If we've reached the magic number (g.TicksHidden), the fish is made visible again with a call to the Show() function (which calls the associated Show() function for the picture box). We then reset the tick count and return. 2. If the fish's X value is less than or equal to the left side of the tank, we reverse it and hide the fish. We also subtract the appropriate points per collision, as specified in g.LostPointsPerHide. This only applies if Speed is negative--since a positive speed would move us in the proper direction. Thus, the call to Reverse() ensures we won't hit this condition on the next tick. 3. If the fish's X value is such that if we move it Speed units over to the left (Speed < 0) it would be past the boundary, we move it so that its left side is right on the left boundary (pt.X=Tank.Left). This will cause condition 2 to kick in next tick. 4. We repeat (2) for the right boundary. In this case, we are interested in a collision with the right edge of the fish, which is at pt.X+Width (since pt.X and pt.Y are the upper left hand corner of the image). The logic is precisely the same, however, and the Reverse() function has the same effect since it reverses the Speed, no matter what direction the Fish is going. 5. Corresponding to (3), if the fish's right boundary (pt.X+Width)+Speed would put it over the tank's right boundary, we move it so the right boundary of the fish sits on the right boundary of the tank (pt.X=Tank.Right-Width). That will cause (4) to invoke next tick. 6. If the fish hasn't encountered any boundary, we add Speed to its pt.X value, moving it to the left or right, depending on whether Speed is negative (left) or positive (right). At this point, we have a fully functional fish. We have yet to see, however, how to attach it to a form and make it go. Before we do that, we'll create a different type of fish: the Crab.

Step 4: Create a Crab class


The Crab inherits from Fish and is identical in many respects. A Crab object swims differently from a fish: it goes up and down instead side to side. Thus, the only functions we need to override are the two virtual functions defined in our Fish object, as illustrated by the UML diagram below.

200

After you've added the Crab class to your project, make it inherit from Fish. The Reverse override is really trivial, as shown below. Since crabs don't swim upside down (at least I don't think they do...), we don't need to flip the bitmap. We just need to reverse the Speed (which, for a Crab, refers to vertical velocity).

201

The Swim() override is slightly more complex. It is identical to the logic for Fish.Swim(), except we replace pt.X with pt.Y, Width with Height and Left/Right with Top/Bottom. The changes are shown below, highlighted:

202

As a customization to the project, you will need to create a third type of marine animal--one that swims in a different way (e.g., diagonally, in a random pattern, etc.) As with Crab, only Swim() and Reverse() should be modified. We now have two marine animals ready to go. Next thing to do is to get them swimming.

Step 5: Create a FishSet class


Since we're going to have a bunch of fish on the screen, it makes sense to create a collection to hold them. We'll do this by creating a new class that inherits from our List<> generic and call it FishSet, as shown below:

Step 6: Get the Fish to display on the form


Our next step is to get some fish to display on the screen. Before you do this, you should attach a background image to your Aquarium form. An aquarium setting would be best, obviously. The demonstration version uses one of the Windows stock photos, but you can certainly do better. Since we will need to be able to click on the fish (to make them turn around before they hit the edges of the tank) there are three things we have to do after each fish is initialized:

Add it our FishSet collection. Add the FishPicture (which inherits from PictureBox) to the Aquarium.Controls collection--which is how the form knows it needs to be drawn. Add a handler to the control.

The easiest way to see how to do this is to put a PictureBox control on the screen, double-click it to get the handler created, then (after inspecting the code generated by the Form Designer to initialize the PictureBox object--used in the Fish class, as previously mentioned) delete the PictureBox. Since the empty handler function isn't deleted by this, you can use it. All of these functions are accomplished in a function AddFish(), shown below:

203

As you may remember, f.Box refers to the FishPicture member of our Fish object, which is the control the form needs. Similarly, pictureBox1_Click is the default name given when the test picture we put on the form was double-clicked to generate a handler. Since experience tells us that whenever you write an add function, you'll probably need a remove function later, it is written as follows:

Next we need to create a convenient set of fish to load when we start the program. We'll generate these fish in a function called InitializeFish() but first we need to get a convenient way to access the fish images we added to the resource area in Step 2. To do this, we create an array of bitmaps initialized with the resource names. (For convenience, a null was inserted between the fish and our crab image). This is shown below, along with the Aquarium constructor which also creates a GameSpecs member object we'll need:

Now we can turn to the InitializeFish() function, which will take our resource-based fish and throw them--pretty much at random--around the tank. The function looks as follows:

204

Because this function creates a lot of form objects, to avoid pointless screen refreshes the graphics-related code is between SuspendLayout() and ResumeLayout() commands, which should reduce pointless redrawing. The function itself works as follows (by number): 1. We create a new FishSet object m_fish and initialize our nTicks counter--which will be incremented by our timer in Step 7. 2. We initialize a Random object rand. This will be used to distribute fish around the tank. We also set bCrab to false. This will be used to determine whether we create Fish objects or Crab objects from our resource images. 3. We iterate through the builtin array of resource bitmaps. 4. If we encounter a null in the array (as we intended), we switch bCrab to true, meaning we'll generate crabs from subsequent pictures. (Hint: when you create your own

205

5. 6.

7.

8. 9.

swimming object, you could add another null in the array to trigger yet another transition). The two new statements after the test is where the objects, assigned to the variable myFish, are created. We load the bitmap and a sequential name (we generated using the nCtr variable) into the myFish object. We generate a random speed within our limits by choosing a random number between 0 and 2*Speed, then subtracting Speed from it. That way it can be either positive or negative. It is then assigned to the myFish object. We generate a position between the top (0) and bottom (ClientSize.Height) of the client. The -50 was added so fish weren't at the absolute bottom. We do the same thing for the X position (with the speed subtracted so we dont start out at the absolute right of the tank). We assign the resulting position to the myFish object. We call AddFish(myFish) to attach our fish to the form, as previously described. We initialize the game TotalPoints to the StartingPoints, so we are ready to begin playing.

At this point when you test your form, fish should appear. However they won't be moving since we haven't told them to yet.

Step 7: Animating our Fish


The final step in Part 1 of Assignment 4 is to get the fish moving. To do this, we'll need to add a Timer control to the form. A Timer generates events at specified intervals, which you can handle just like any other event. To add a timer, just take the component (circled below) from your toolbox and drag it on to your form. It will then appear in the grey area beneath your form.

206

Once you have the timer on your form, you can set its Interval property (try 400 to start), then double-click it to get an event handler. You can then add the following code:

207

The code works as follows (by number): 1. If our tick counter has reached the end of the game, we stop the timer (timer1.Stop() member) and display the player's score. 2. We increment our nTick count. 3. If our number of ticks is evenly divisible by our increase interval (i.e., the remainder of nTicks/m_game.SpeedIncreaseInterval is 0), we set bIncrease to alert us to bump up the speed by m_game.SpeedIncreasePercent. 4. We loop though each fish, resetting the Tank dimensions to our ClientRectangle (the client area is the non-border part of the screen). We do this in case the user resized the screen. We then tell the fish to swim (highlighted). Because this function is virtual (polymorphic), the left-and-right version will be called for Fish objects, while the upand-down version will be called for Crab objects. Finally, if we've reached a speed interval increase point, we increase the speed of the fish. There are only two more functions we need to begin animating our fish. First, we need a way to start the fish moving--which involves turning on the timer. To do this (temporarily) place a new button object on the upper right corner of the screen and label it "Start" (Text property). Then double click it to create a handler. The code you should write appears as follows:

208

We first declare a member variable m_running to let us know if we're running. Clicking the button toggles this variable (first line). Then:

If we're running (yellow highlight), we first check to see if the timer's Interval setting matches the m_game.TickDuration value (another GameSpecs property). If not, we change the timer's interval setting. We then start the timer (timer1.Start() call) and reset the button's text to "Pause". If the button was pressed to pause the game (green highlight), we stop the timer, change the button's text to "Play" and pop up a message with the player's current score.

Finally we need to implement a handler for when a fish is clicked. We already created the handler function (pictureBox1_Click), so all we need to fill in the code to reverse the fish's direction, as shown:

This code uses the fact that the FishPicture class is not only a PictureBox, but also has a reference to the associated Fish object. Thus, we can reverse its direction by calling Reverse(). Moreover, since Reverse() is virtual (polymorphic), if our fish happens to be a Crab, the appropriate version of the function will be called. A final comment with respect to the appearance of the display involves the concept of double buffering. Normally, when Windows draws a form, each control is drawn individually. When controls are complex and are moving around a lot--like bitmap images of fish!--this process can produce a lot of flickering. An alternative to this style of drawing involves drawing the entire

209

display on to a memory bitmap, then taking the entire bitmap and copying it to the screen. This is called double buffering because the bitmap acts as a holding area (a.k.a. a buffer) while we are getting things ready for the graphics area of memory (which is also a buffer). For the Aquarium project, double buffering dramatically improves the applications appearance. It can be enabled by setting the DoubleBuffered property of the form to true.

Step 8: Fill in Report


Fill in that portion of the Assignment 4 report sheet (on Blackboard) relating to Part 1.

210

211

Assignment 4: Part 2
Overview
Upon completing this part of the assignment, you should be able to customize your game by adding and removing fish objects with a DataGridView object and by adjusting the GameSpecs parameters using a properties box. These features will be accessed using a menu bar that you will add. The procedure that should be used in completing Part 2, excluding customizations and modifications described in the assignment, now follows.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Add a Menu Bar


First we need to add a menu bar. This can be dragged over from the Toolbox and moved to the top of the form. Once there, you should choose the "Insert Standard Items" to give you File, Edit, Tools, Help items in your menu. You can delete Edit, since we won't be using it. The other common task you should do is insert the menu in a container, as shown below:

212

You can dock the container in the form, leaving only the Top box checked. Using a container helps the form keep track of its client area (where our fish are swimming). Uncheck the boxes on the Panel Visibility dialog that follows, then make sure the container is the same size as the menu. Next, add a Play item to the Tools menu. Attach this to the handler you already created for the button in Part 1, as shown below. The only change you need to make to the old version is to change the typecast, as highlighted. This is because the event is now generated by a menu item instead of a button. (Hint: if you are ever in doubt about what type of object the sender is, put a breakpoint in the code, then run it under the debugger to inspect it).

Now delete the button you added in Part 1 (which would generate an exception when you tried to typecast it) and test the menu. Its name should change just the way the button's did when you start and stop.

Step 2: Create a Customize form


This application--lame as it is, when compared to commercial games--would definitely benefit from the ability to customize the fish. So now we will create a DataViewGrid form that lets us add, delete and edit fish. The first step is to add a new Windows Form to the project called CustomizeFish. Add a toolstrip to the top of the new form, embed it in a container, and place two text-only buttons on it--Add Fish and Delete Fish, as shown below:

213

Place a DataViewGrid control on it, calling it dataGridFish. Using the common tasks control on the grid, dock it in the parent container then add a project data source--the FishSet class created in Part 1, as shown below:

Remove some of the extraneous data columns from the display but be sure to leave the Picture column in (as shown below):

214

Adjust the row height (Hint: look in the RowTemplate properties) and rebuild. You should have something that looks like the following:

215

Step 3: Coding your form


The DataGridView control is amazing because of how much you get for very little effort. To bring the form display into our project, we'll need to do the following:

Add a handler to the Tools | Customize menu option on our Aquarium form that brings up our form. Attach our FishSet object to the grid.

It makes sense to do the second task first, since it involves modifying the CustomizeFish form's constructor as follows:

216

The reason for including the Aquarium object in the constructor will become apparent only when we start implementing add and delete functionality. Otherwise this is a perfectly standard way to attach a collection to a DataGridView. In the Aquarium form designer, double-click the Tools | Customize menu option to generate the handler. The code should look like the following:

Very straightforward, it stops the clock, creates the CustomizeFish window, then launches it with Show()--as opposed to ShowDialog(). The reasoning here is that since the form edits the collection directly, you might as well be able to watch the changes as you make them. Either one seems to work. The resulting window should appear as follows:

217

If your images don't appear like this, you may need to adjust the display properties of your column (Hint: think Zoom).

Step 4: Implementing add fish functionality


The next step in customizing is being able to add new fish--using images from a file--to our collection. To do this, we'll need to create a dialog suitable for adding fish. Add a new Windows form to your project called AddFish and lay out controls roughly as follows:

218

The controls are as follows (by number): 1. listFishType: A list box with Fish and Crab added to the contents, as shown. This determines the type of fish to be created. 2. textName: A text box for the name we'll use for the fish. 3. textSpeed: A text box for the speed of the fish. 4. textPercentSize: A text box for our percent size value. 5. buttonBrowse: A button to launch a file-open dialog. 6. buttonReverse: A button to ensure our fish starts our point left 7. pictureBox1: To hold the image of the bitmap we load. 8. buttonOK: A button to end the dialog. Since we'll be doing some validation here, we'll be adding its Click handler. 9. buttonCancel: Ends the dialog.

219

In addition to these, you should drag an OpenFileDialog control on to the form called openFileDialog1 (the default), so we can use it to load pictures. It will appear in the grey area under the form. The dialog helps us create a new Fish object. To do so, we want to validate the data and assist the user as much as possible. We begin by setting a default for percent size of 1.0 in the constructor (to avoid confusion between 1.0 vs. 100.0) and declare some members. The code is as follows:

Before bringing in the bitmap, we need to understand the two things that could change how it is displayed. The first, of course, is the image itself. The second, however, is the percent size value. We want to allow the user to see what size the fish will be. Since changes in either parameter will require a redraw, we write a function that can be used by both: ResizeImage(), shown below:

Most of the ResizeImage() code deals with making sure a reasonable percentage value is in place--defaulting to 1.0 if there is any doubt. The key to the function (highlighted) is adjusting 220

the pictureBox1 size using the percentage specified. It is the same approach used in displaying fish on the screen, so it shows the user what size to expect. The first function that uses ResizeImage() is the handler for the Leave event of the textPercentSize control, which should be added using the properties box. This produces a simple call to ResizeImage() whenever we stop editing the percentage text and, for example, select another control.

The other time we use the ResizeImage() function is when we load a file. For this, we should implement a handler for the Browse button. This will open the openFileDialog1 object and, if the user chooses OK, will attempt to create a Bitmap from the file name. The code is as follows:

Notice that the attempt to open the bitmap is in a try...catch block. This is necessary because any non-image file the user tried to load would crash the program. We also need to let the user reverse the image of a fish facing in the wrong direction (i.e., to the right), which we do by implementing a handler on the reverse button as follows:

221

After checking to make sure we've loaded a Bitmap, this handler applies the same flip we used in Part 1. Reassigning an image to a text box (or any other control) is a good way to force a redraw. The final logic we need to implement is the OK button, which you should double-click to create the handler. In this handler, we are going to create the Fish object if we have enough data. It is defined as follows:

In the yellow highlighted area, we test for the following:


A bitmap is present. A name is present. A speed is present and greater than 0.

222

A fish type is selected in the listFishType box. This is critical, since it determines what type of fish to select.

In the green highlighted section, we make a selection based on the menu item returned from the listFishType list box. If it doesn't match "Fish" or "Crab", then we end the dialog by setting the result to DialogResult.Cancel and close the window. In the light blue highlighted section, we add the data from the dialog to our fish. We then set the DialogResult to OK and close the window. This code has one defect in that if you click OK and something is wrong, it either just sits there or it closes the form without telling you why. As a customization you should write some code that gives the user a clue as to what went wrong and gives the user an opportunity to correct it. This will probably involve work in the highlighted yellow and green areas. Having completed the dialog, you can now return to the CustomizeFish dialog and write a handler for the "Add Fish" button. The code can be written as follows:

Here, we take advantage of the AddFish() function already written in the Aquarium class, which will add your fish even if the CustomizeFish form is still open (you can also move fish around and resize them). The last two lines, setting the dataGridFish.DataSource to null and then back to m_fish, forces it to update and display the new row.

Step 5: Implementing delete fish functionality


Deleting fish is a lot easier than adding them--especially since doing so doesn't need a complicated dialog. The delete button handler is as follows:

223

The function works as follows:


We identify the specific fish using the selected row in the grid, then access it from our FishSet object, m_fish. We first test the row to be sure it's on range. We call the Aquarium.RemoveFish() member written in Part 1 that removes the control and the Fish object from the FishSet. We set the dataGridFish.DataSource to null and back to m_fish again, forcing it to regenerate with the deleted row removed.

Step 6: Implementing a property grid for GameSpecs


The last object we need to edit is our GameSpecs object, to allow the user to change the game parameter settings. Since we are probably bored with writing complex dialogs by now, we'll do this the quick and dirty way--using a PropertyGrid control. The control is hard to find in the ToolBox (I found it in the alphabetical listing of all controls), but it allows us to take advantage of the fact that everything in our GameSpecs class is a property. To implement it, do the following:

Create an empty form called GameSpecsEdit. Drag an empty property grid on it and make it fully docked. Set the HelpVisible and ToolbarVisible properties to false. These are useless for our class.

Modify the constructor to take a GameSpecs argument, then attach the property grid to the object passed in. This can be done as shown (highlighted):

224

Finally, in the Aquarium form create a handler for the "Options" button (already on the toolbar) that calls up the GameSpecs Edit dialog we just created. This can be done as follows:

When your program is running, the properties box should display as follows:

You should test to verify that it allows you to change your game's options. You have now completed your File | Tools implementation.

Step 7: Fill in Report


Fill in that portion of the Assignment 4 report sheet (on Blackboard) relating to Part 2.

225

226

Assignment 4: Part 3
Overview
In Part 3 of the assignment, we will be extending the menu interface started in Part 2 to support our Aquarium application. Specifically, we will implement the common activities on the file menu: New, Open, Save, Save As, Print and Print Preview. The procedure used in completing Part 3, excluding customizations and modifications described in the assignment, now follows.

Instructions
To complete this portion of the assignment, you will perform the following steps.

Step 1: Implement File | New option


In a typical Windows application, File | New creates a blank document. Sometimes, as in the case of Word or PowerPoint, it opens up a menu of forms to allow customizations before the document is created. In our case, however, we just want it to create a new game using the same built-in fish we already have. To do this, we need to first remove all the fish that were already in play. We do this by creating a RemoveAll() function as follows:

This function is just an elaborate way of calling RemoveFish() on each Fish object in m_fish. The problem is, RemoveFish() removes the fish from m_fish as part of its actions, which means you can't use it in a foreach loop on m_fish. One solution would be to iterate backwards using a standard for loop. My choice, however, was to create another list of Fish (highlighted in yellow), then use that list to remove the fish calling RemoveFish(). Once this was done, we can set

227

m_fish to null. We couldn't do that right off the bat because RemoveFish() also removes the associated FishPicture control from the Aquarium.Controls collection. We do that to keep old fish from displaying. Once we've reset the fish, the handler for the File | New option is trivial, shown as follows:

Step 2: Implement File | Save and File | Save As options


It makes sense to implement the Save and Save As options at the same time because, properly designed, they can share a lot of code. The first thing you should do in preparation for this is to drag a FileSaveDialog control onto the form. You can then adjust its properties so it is named fileSave and uses the default aqu extension for the files. Also, adjust the filter so it shows only those files. These changes are highlighted below:

Next, you need to go to our other data classes (Fish, FishSet, Crab and GameSpecs) and add the [Serializable] attribute to the top of the class definition. This should be done right above the public class class-name line, as illustrated (highlighted in yellow) for the Fish class below:

228

For the Fish class (only), we also need to do one more thing, highlighted in green. Since PictureBox objects cannot be serialized, we need to signify this by placing a [NonSerialized] attribute right above the data member. It will then be skipped in the loading and saving processes. The final preparation is to create a typical m_filename string member and a FileName property in the Aquarium form class, as shown:

Now you can double-click the Save and Save As options on the menu to create the two handlers (the default names are fine). We'll make the saveToolStripMenuItem_Click handler do all the heavy lifting. If FileName is set, it automatically serializes the data. If not, it will pop up the file dialog box. To implement the saveAsToolStripMenuItem_Click handler, we'll just null out FileName, then call the Save handler. Both handlers are shown below:

229

In the Save (saveToolStripMenuItem_Click) handler, the process begins with popping up our SaveFileDialog (saveFile) if we don't have a FileName value. We use the dialog's FileName property to set the Aquarium.FileName property (assuming the user didn't cancel out). We then enter the actual process of saving--the hardest part of which was finding the namespaces where the classes and functions were hiding (these are in the comments and code, for your benefit): 1. We create a BinaryFormatter object fmt with binary formatting being the normal way we serialize (as noted in the lectures and readings). 2. We open our file for writing, creating a FileStream variable strm. 3. Using our formatter's members, we first serialize our m_game (Game) then m_fish (FishSet) members. These are the only data elements we need to recreate a game-particularly the collection of fish and settings we were using. 4. We close the stream so it doesn't lock up other programs or users. As a matter of good programming practice, it would be crazy to have all this file access code running unhandled--since file activities are ripe for exceptions. Therefore, as a customization,

230

you should place this code in a try...catch block that pops up a message to the user if something goes wrong. You can, and should, now save a file. That way, you'll have something to test when you want to implement your Open function.

Step 3: Implementing File | Open


It makes sense to implement the Save function before the Open function for two reasons:

A lot of the code can be copied and modified (by design). You need to have a saved file to test your Open function.

As a preparation to implementing File | Open, drag an OpenFileDialog control into your Bingo form and call it openFile. Next you can double-click the Open item on the file menu to create the openToolStripMenuItem_Click handler. The handler should be defined as follows:

231

The key thing to remember in writing the Open handler is that a lot of the Save handler code can still be used. Furthermore, the file handling code that needs to be modified (highlighted in green) is generally in the right place (objects must be saved and opened in the same order!). Because our application deals with controls, we also have two extra things to do (highlighted in yellow). Specifically, we need to:

Call RemoveAll() prior to loading in the new data. Otherwise, we'll have all those old FishPicture controls sitting around. Call AddAll() to regenerate the controls once we've loaded in the fish.

The AddAll() function, shown beneath the Open handler, goes through the fish and, for each one, attaches the control to the form using the Aquarium.Controls.Add(f) inherited member, then attaches our event handler to the FishPicture.

Step 4. Implementing File | Print and File | Print Preview


As noted in the lecture and readings, printing can get ugly in a hurry--particularly in a formbased application. The problem here is that even if .NET forms knew what they looked like (they don't), the entire printing process is based on the notion of pagination. The functions that handle printing generate one page at a time then render each to the printer. Even for a text document, this can take some work. Having said all this, there are some things about printing that .NET makes reasonably painless. You should begin, therefore, by dragging a PrintPreviewDialog, PrintDialog and PrintDocument control onto your Aquarium form. Next, you can create handlers for the Print Preview (printPreviewToolStripMenuItem_Click) and Print (printToolStripMenuItem_Click) items on the File menu by double clicking them. Finally, select the Print Document object (at the bottom of your form) and in the events menu, create a PrintPage handler called printDocument1_PrintPage. To repeat the readings, printing occurs through repeated calls to the document's PrintPage handler. Thus, as long as we have a reasonable handler attached to our print document object, we should be able to print. Moreover, since printing and print preview work the same way (they simply direct their output to different devices), if we can print, we should also be able to print preview. But, here comes the messy stuff. In .NET 2.0, forms don't really know how to render themselves to a printer. So we handle this by taking a screen capture of the application window, save it to a bitmap, then write that bitmap to whatever device is passed into the printDocument1_PrintPage handler. The code for creating the bitmap is presented below:

232

Since this involves drawing, we will not focus on explaining it in detail here. In essence, it creates an empty bitmap of the right size (memoryImage), then fills it with the CopyFromScreen() function. More detail is provided in the Resources and Bitmaps and Printing lectures and readings. The PrintPage handler draws and is written as follows:

The two menu handler functions (File | Print and File | Print Preview) are as follows:

The three lines with print in them are completely standard--attaching the document to the dialog, then showing the dialog and, if the user indicates OK to print, calling the Print() member on the document. The two unusual functions are:

233

A call to the form's Update() member to get rid of the File menu, which was otherwise included in the screen capture. The CaptureScreen() function (shown in the text code) call, which takes the form area and copies it into a bitmap.

The Print Preview handler is even simpler, because the printDocument1.Print() step is omitted (since the print preview dialog takes care of that). The dialog captures the fish, as shown:

Step 5: Implementing File| Exit


To end a form-based application, you only need to close its main window. Thus, you can implement the following handler for File | Exit:

234

Upon completing this, you have one final customization. That is to create your own "About" box (just create a form with an OK button and fancy it up). It should have the names of your group members in it and a version number, which you can make up.

Step 6: Fill in Report


Fill in that portion of the Assignment 4 report sheet (on Blackboard) relating to Part 3.

235

Vous aimerez peut-être aussi