C#.NET 3: Advanced Algorithms and Database...

414
C#.NET 3: Advanced Algorithms and Database Access Lesson 1: Introduction and Multidimensional Arrays Understanding the Learning Sandbox Environment Visual Cues Code Snippets The OST Plug-In Arrays Revisited Multidimensional Arrays Declaring, Allocating, and Initializing Multidimensional Arrays Accessing Multidimensional Array Elements Debugging Classes Matching Game - A Coding Tutorial Matching Game User Interface Prototypes Creating the User Interface Adding the Code Lesson 2: Jagged Arrays and Array Class Methods Jagged Arrays Declaring, Allocating, and Initializing Jagged Arrays Accessing Jagged Array Elements Mixing Jagged Arrays and Multidimensional Arrays The Array Object Array Length Versus GetLength() Array Rank Clearing Array Element Contents Copying an Array Resizing an Array Other Class Array Properties and Methods Math Drop - A Coding Tutorial Math Drop Game User Interface Prototypes Creating the User Interface Adding the Code Lesson 3: Array Indexers and Sorting Indexers Sorting Arrays Tiler - A Coding Tutorial Tiler Game User Interface Prototypes Creating the User Interface Adding the Code Lesson 4: Object Relationships and the Object Class Inheritance The Object Base Class DrawShapes - A Coding Tutorial Draw Shapes User Interface Prototypes

Transcript of C#.NET 3: Advanced Algorithms and Database...

Page 1: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

C#.NET 3: Advanced Algorithms and Database AccessLesson 1: Int ro duct io n and Mult idimensio nal Arrays

Understanding the Learning Sandbox EnvironmentVisual CuesCode SnippetsThe OST Plug-In

Arrays Revisited

Multidimensional ArraysDeclaring, Allocating, and Initializing Multidimensional ArraysAccessing Multidimensional Array Elements

Debugging Classes

Matching Game - A Coding TutorialMatching Game User Interface Proto typesCreating the User InterfaceAdding the Code

Lesson 2: Jagged Arrays and Array Class Met ho dsJagged Arrays

Declaring, Allocating, and Initializing Jagged ArraysAccessing Jagged Array ElementsMixing Jagged Arrays and Multidimensional Arrays

The Array ObjectArray Length Versus GetLength()Array RankClearing Array Element ContentsCopying an ArrayResizing an ArrayOther Class Array Properties and Methods

Math Drop - A Coding TutorialMath Drop Game User Interface Proto typesCreating the User InterfaceAdding the Code

Lesson 3: Array Indexers and So rt ingIndexers

Sorting Arrays

Tiler - A Coding TutorialTiler Game User Interface Proto typesCreating the User InterfaceAdding the Code

Lesson 4: Object Relat io nships and t he Object ClassInheritance

The Object Base Class

DrawShapes - A Coding TutorialDraw Shapes User Interface Proto types

Page 2: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating the User InterfaceAdding the Code

Lesson 5: Abst ract and Sealed ClassesConceptualizing

Abstract ClassesCalling Base Class ConstructorsSealed Classes

PopIt - A Coding TutorialPopIt User Interface Proto typesCreating the User InterfaceAdding the Code

Lesson 6 : St ruct Dat a T ype, Enums, and Cast ingStructs

Enums

Casting

ColorMatch - A Coding TutorialUser Interface Proto typesCreating the User InterfaceAdding the Code

Lesson 7: Int erf aces and Int erf ace Cast ingInterfaces

What is an Interface?Multiple InheritanceInterfaces Versus Abstract ClassesCreating an Interface

Shape Capture - A Coding TutorialUser Interface Proto typesObject Identification and DiscussionCreating the User InterfaceAdding the Code

Lesson 8 : Delegat es and Event sDelegates

What is a Delegate?Working with DelegatesAnonymous Delegate MethodsMulticast Delegate InstancesWhy Delegates?

EventsEvents are delegates.NET EventsSubscriber and Publisher

Balloons - A Coding TutorialUser Interface Proto typesCreating the User Interface

Page 3: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Adding the Code

Lesson 9 : Lambda Expressio nsLamba Expressions

What is a Lambda Expression?Lambda Expression SyntaxLambda Statements and ExpressionsLambda Expressions and Event HandlersUsageLambda Expressions as AlgorithmsLambda Expression Trees

Targets - A Coding TutorialUser Interface Proto typesCreating the User InterfaceAdding the Code

Lesson 10: Except io ns and Except io n HandlingIntroduction

Creating an Exception

Try/Catch/Finally Block

Throwing an Exception

Nested Exceptions

Extending Exceptions

Custom Exceptions

To Catch or Not

Exceptions and the Using Syntax

Coding Tutorial

Lesson 11: Co llect io ns and GenericsCollections and Generics

IntroductionArrayList vs. List GenericOther Generic Co llection ClassesCreating Generic Classes

Coins - A Coding TutorialUser Interface Proto typesCreating the User InterfaceAdding the Code

Lesson 12: Creat ing and Manipulat ing St ringsStrings

Immutability and Empty Versus Null

StringBuilder

Regular Expressions

Lesson 13: Int ro duct io n t o Dat abase Access Using .NETWhat are Databases?

Page 4: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Data Sources Window

Working with Datasets

Lesson 14: Wo rking Wit h Dat abases and Visual Dat a Co mpo nent sMaking the Connection

Modifying Database Data

Working With Datasets

Lesson 15: A Dat abase GUI Applicat io nCourse Pro ject

The AssignmentUser InterfaceClass Design DiagramTOE ChartUse Cases - MainFormUse Cases - CategoriesFormUse Cases - CategoryBreakdownFormDataSet DesignerCoding Requirements

Application Development StepsFirst StepsCategories ComboBoxPopulating the Register DataGridViewInserting a New RecordRegister Balance and Nullable Data TypesDeleting a Register EntryUpdate CategoriesCategory Breakdown ReportsTab Order

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 5: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Introduction and Multidimensional Arrays

Welcome to the O'Reilly School o f Technology's C# .NET 3: Advanced C# Pro gramming course!

Course ObjectivesWhen you complete this course, you will be able to :

create and represent more complex data using enumerations, structs, arrays and classes.enhance structs and classes using indexers, casting, interfaces, abstraction and sealing.handle and create custom events using delegates and lambda expressions.create custom exceptions.use generics with co llections.overcome performance limitations and the immutability o f strings by using the StringBuilder class.Use regular expressions for concise pattern matching and replacement.Access data in a database, and use visual data components.

In this course, you will go deeper into many advanced programming concepts such as multidimensional and jagged arrays,array indexers, abstract and sealed classes, structs, enums, casting, interfaces, delegates, events, lambda expressions,generics, the StringBuilder class, regular expressions, and advanced exception handling. You'll also get an introduction toworking with databases and visual data components.

You will create several applications throughout the course, which will enhance your pro fessional portfo lio and also contributetoward certificate completion.

Lesson Objectives

When you complete this lesson, you will be able to :

use the Learning Sandbox Environmentuse Arraysuse Multidimensional ArraysDebug Classescreate a Matching Game - A Coding Tutorial

Learning with O'Reilly School of Technology CoursesAs with every O'Reilly School o f Technology course, we'll take a user-active approach to learning. This means that you(the user) will be active! You'll learn by do ing, building live programs, testing them and experimenting with them—hands-on!

To learn a new skill o r techno logy, you have to experiment. The more you experiment, the more you learn. Our systemis designed to maximize experimentation and help you learn to learn a new skill.

We'll program as much as possible to be sure that the principles sink in and stay with you.

Each time we discuss a new concept, you'll put it into code and see what YOU can do with it. On occasion we'll evengive you code that doesn't work, so you can see common mistakes and how to recover from them. Making mistakesis actually another good way to learn.

Above all, we want to help you to learn to learn. We give you the too ls to take contro l o f your own learning experience.

When you complete an OST course, you know the subject matter, and you know how to expand your knowledge, soyou can handle changes like software and operating system updates.

Here are some tips for using O'Reilly School o f Technology courses effectively:

T ype t he co de. Resist the temptation to cut and paste the example code we give you. Typing the codeactually gives you a feel fo r the programming task. Then play around with the examples to find out what elseyou can make them do, and to check your understanding. It's highly unlikely you'll break anything by

Page 6: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

you can make them do, and to check your understanding. It's highly unlikely you'll break anything byexperimentation. If you do break something, that's an indication to us that we need to improve our system!T ake yo ur t ime. Learning takes time. Rushing can have negative effects on your progress. Slow down andlet your brain absorb the new information thoroughly. Taking your time helps to maintain a relaxed, positiveapproach. It also gives you the chance to try new things and learn more than you o therwise would if youblew through all o f the coursework too quickly.Experiment . Wander from the path o ften and explore the possibilities. We can't anticipate all o f yourquestions and ideas, so it's up to you to experiment and create on your own. Your instructor will help if yougo completely o ff the rails.Accept guidance, but do n't depend o n it . Try to so lve problems on your own. Going frommisunderstanding to understanding is the best way to acquire a new skill. Part o f what you're learning isproblem so lving. Of course, you can always contact your instructor fo r hints when you need them.Use all available reso urces! In real- life problem-so lving, you aren't bound by false limitations; in OSTcourses, you are free to use any resources at your disposal to so lve problems you encounter: the Internet,reference books, and online help are all fair game.Have f un! Relax, keep practicing, and don't be afraid to make mistakes! Your instructor will keep you at ituntil you've mastered the skill. We want you to get that satisfied, "I'm so coo l! I did it!" feeling. And you'll havesome pro jects to show off when you're done.

Understanding the Learning Sandbox EnvironmentMost o f the time we'll be working in Visual Studio , Microsoft's Integrated Development Environment (IDE) for workingwith C#.

Visual Cues

CODE TO TYPE:

In boxes like this, we'll ask you to type code (or remove code)in Visual Studio. Code to add will look like this, and code to remove will look like this. These boxes signal that it's time to experiment!

OBSERVE:

Similarly, when we want you to simply observe some code or result,we will put it in an "OBSERVE" box like this.

We might use color highlighting to help explain the code.

You are not expected to type anything from these boxes.

T ext t hat co rrespo nds t o t he co lo r o f the code in an OBSERVE box will explain t hat part icular co dein mo re det ail.

When you see this icon , go ahead and save your work. You can save whenever you like; we want you toget into the habit o f saving your pro jects frequently. In fact, whenever you pause to think, move your cursorover to the on the Too lbar and click on it.

Code Snippets

Whenever we present snippets o f code, you'll want to create a sample pro ject where you can enter that code.We'll prompt you to create test pro ject(s) fo r each lesson. As we progress through each lesson, you'll createmethods and call those methods from the Form constructor under the InitializeComponent() line o f code.Eventually you'll create more topic-specific methods, and comment out the call to the methods that you're notusing.

The OST Plug-In

We've added a new menu item to the Visual Studio system that we think you'll like. You can now use the OSTmenu to get to your syllabus for this course at any time. Your menu may display o ther courses you'veenro lled in as well.

Page 7: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Tip If your Visual Studio menus start to get confusing in the course o f your work, you can always getback to the default view by selecting this course from the OST menu.

Arrays RevisitedFor this lesson, create a test pro ject using File | New | Pro ject , change the Name of the pro ject toMult iDimensio nalArrays, and click OK.

You may recall that an array is a reference data type that ho lds multiple values or elements o f a specific data type. Eventhough an array can ho ld multiple values, each value is referenced through a single variable name. Array referencevariables are stored on the stack, but the values they reference are stored on the heap.

NoteRemember that although we allocate memory for arrays on the heap using the C# new keyword, we mayomit the new keyword when declaring and initializing an array. If we are reallocating memory for analready declared keyword, we still need to use the new keyword.

Let's try an example that shows how we can declare, allocate, and initialize arrays. Modify Fo rm1.cs as shown:

Page 8: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); ArraysReview1();}

private void ArraysReview1(){ // Integer array declaration with a value of null. int[] declaredArray; // Integer array declaration with pre-allocated space for five integer elements, // all values initialized to default value for integers (0). int[] declaredAllocatedWithDefaultInitializationArray = new int[5]; // Changing the number of array elements to 25, all values initialized to default // value for integers (0). // Note that any previously stored values in the array elements are erased. declaredAllocatedWithDefaultInitializationArray = new int[25]; // Integer array declaration with pre-allocated space for five integer elements, // with array element initialization. int[] initializedArray = new int[5]{ 1, 2, 3, 4, 5 }; // Integer array declaration with pre-allocated space for five integer elements, // with array element initialization, omitting array size. int[] initializedSizeOmittedArray = new int[]{ 1, 2, 3, 4, 5 }; // Integer array declaration with pre-allocated space for five integer elements, // with array element initialization, omitting new keyword. int[] initializedAbbreviatedSyntaxArray = { 1, 2, 3, 4, 5 };}...

Make sure to add breakpo ints to enable stepping through the code to clarify the code behavior. To add a breakpo int,click in the left margin next to the line o f code where you want to include a break. When you run the program, executionwill pause at that po int.

Click to save your changes, and then click to run the program; note how the values and types change for thearrays we created. We typically refer to the number o f elements an array can ho ld as its size; we can also call itdimension. So far, we've used only one-dimensional arrays.—we'll introduce multidimensional arrays next.

Multidimensional ArraysArrays can have more than one dimension. You can visualize a one-dimensional array as a single co lumn of dataelements, a two-dimensional array as a spreadsheet o f rows and co lumns, or as a plane that can be represented by xand y values, and a three-dimensional array as a cube. Beyond three dimensions, you might be able to imagine thefourth dimension is time, or maybe a property o f the element at a specific location.

Here's an example o f multi-dimensional data tht may help you get a handle on multidimensional arrays. Supposeyou're a movie mogul. In the next year, you plan to make three different movies: one about alien shark zombies, one afeel-good love story, and the third, a superhero action movie. You begin to work out your budget by calculating whatyou will need to spend for one month o f work on just the alien shark zombies movie. You create a list o f budget items:costumes, pay for crew members, food and transportation for everyone, and so on. We could consider this task to beone-dimensional. Next, when you budget fo r all o f these things for more than one month, you'll add a seconddimension. When you budget fo r your o ther movies, that's a third dimension o f your movie studio budget. After amonth goes by, you need to track your actual expenses for all o f the costs for each o f the movies for all o f thepreceding months—a fourth dimension. If you want to compare the budgeted costs to actual costs, that could be

Page 9: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

considered a fifth dimension. Now if your movie studio has o ther divisions, say for documentaries, shorts, corporatefilms, and television productions, you might need to add a sixth dimension!

Note If you do want to know more about visualizing n-dimension arrays, there is an excellent discussion onstackoverflow.

Declaring, Allocating, and Initializing Multidimensional Arrays

So, how do we declare, allocate, and initialize a two-dimensional array?

Modify Fo rm1.cs as shown (add breakpo ints to enable stepping through the code to clarify the codebehavior):

CODE TO TYPE:

.

.

. public Form1(){InitializeComponent();

ArraysReview1(); ArraysReview2();}... private void ArraysReview2(){ // Two-dimensional array declaration with a value of null. int[,] declared2DArray; // Two-dimensional array declaration with pre-allocated space for // one row and two columns, // all values initialized to default value for integers (0). int[,] declaredAllocatedWithDefaultInitialization2DArray = new int[1, 2]; // Two-dimensional array declaration with pre-allocated space for // two rows and three columns, with array element initialization. int[,] initialized2DArray = new int[2, 3] { { 987, 876, 654 }, { 543, 432, 321 } }; // Two-dimensional array declaration with pre-allocated space for // two rows and three columns, with array element initialization, // omitting arrray size int[,] initializedSizeOmitted2DArray = new int[ , ] { { 987, 876, 654 }, { 543, 432, 321 } }; // Two-dimensional array declaration with pre-allocated space for // two rows and three columns, with array element initialization, // omitting new keyword int[,] initializedAbbreviatedSyntax2DArray = { { 987, 876, 654 }, { 543, 432, 321 } };}...

Click to save your changes, and then click to run the program. The values and types change for thearrays we created. When initializing two-dimensional arrays, note that you group each row of co lumn valueswithin separate curly braces {}, separated by commas.

How do you visualize or represent the elements o f a two-dimensional array?

Page 10: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Modify Fo rm1.cs as shown:

CODE TO TYPE:

.

.

. public Form1(){InitializeComponent();

ArraysReview1(); ArraysReview2(); ArraysReview3();}... private void ArraysReview3(){ // Two-dimensional array initialized with values to help visualize the structure string[,] spreadsheet2DArray = { {"A1 or R1C1", "B1 or R1C2", "C1 or R1C3"}, {"A2 or R2C1", "B2 or R2C2", "C2 or R2C3"}, {"A3 or R3C1", "B3 or R3C2", "C3 or R3C3"}, {"A4 or R4C1", "B4 or R4C2", "C4 or R4C3"} };}...

Click to save your changes, and then click to run the program. The values and types change for thearrays we created.

In the code snippet above, A1 (co lumn A, row 1) and R1C1 (Row 1 Column 1) represent a standardspreadsheet referencing convention to help you visualize the assignment o f values to the two-dimensionalarray elements; it would look like this in a spreadsheet:

Note Remember, array indexes start at zero (0), not one (1)! The first row in the first co lumn wouldactually be referenced as 0 , 0 .

In order to create arrays beyond two dimensions, we'll continue the pattern.

Modify Fo rm1.cs as shown (always make sure to add breakpo ints to enable stepping through the code toclarify the code behavior):

Page 11: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){InitializeComponent();

ArraysReview1(); ArraysReview2(); ArraysReview3(); ArraysReview4();}... private void ArraysReview4(){ // Three-dimensional arrays // Declared three-dimensional array int[,,] declared3DArray; // Default initialized three-dimensional array with two rows, three columns, // and four sheets int[,,] defaultInitialized3DArray = new int[2, 3, 4]; // Initialized three-dimensional array int[,,] initialized3DArray = new int[2, 3, 4] { { {98,87,76,65}, {45,67,78,89}, {33,44,55,66} }, { {54,43,32,21}, {12,23,34,45}, {77,88,99,11} } }; // Initialized three-dimensional array omitting new keyword (abbreviated syntax) int[,,] initialized3DArray = { { {98,87,76,65}, {45,67,78,89}, {33,44,55,66} }, { {54,43,32,21}, {12,23,34,45}, {77,88,99,11} } }; // Four-dimensional array // Declared four-dimensional array int[,,,] declared4DArray; // Default initialized four-dimensional array int[,,,] defaultInitialized4DArray = new int[1, 2, 3, 4];}...

Click to save your changes, and then click to run the program. The values and types change for thearrays we created.

Accessing Multidimensional Array Elements

Accessing elements o f multidimensional arrays is similar to accessing array elements o f single-dimensionalarrays, but with an additional dimension specifier.

Modify Fo rm1.cs as shown:

Page 12: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent();

ArraysReview1(); ArraysReview2(); ArraysReview3(); ArraysReview4(); ArraysReview5();}... private void ArraysReview5(){ // Create a two-dimensional array int[,] my2DArray = { { 22, 33, 44 }, { 77, 66, 55 } }; // Display the current value in the second row, third element of 55 Console.WriteLine("Original Row 2, third element: " + my2DArray[1, 2]); // Change the value in the second row, third element to 50; my2DArray[1, 2] = 50; // Display the new value in the second row, third element of 50 Console.WriteLine("New Row 2, third element: " + my2DArray[1, 2]);}...

Click to save your changes, and then click to run the program. The values and types for the arrays wecreated change at the breaks.

In the code above, we change the value o f the seco nd [1, 2] array row, t hird [1, 2] array co lumn element from5 to 50 .

Debugging ClassesIt's possible that throughout the lessons in this course, you may attempt to step into class code and fail because o fautomatic step-over, and see this dialog box:

To prevent automatic step-over, you can either place a breakpo int within the class element you want to step into , oryou can change the Studio Property that contro ls this behavior in Debug | Opt io ns and Set t ings. Select theDebugging in the options tree, and uncheck St ep o ver pro pert ies and o perat o rs (Managed o nly) as shown:

Page 13: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Matching Game - A Coding TutorialFor our coding tutorial, we'll create a matching game. The goal o f the game is to click on cards (Button contro ls) andflip over pairs to find the matches. We'll use a two-dimensional array to store Button contro ls that will serve as each

square o f the game. We'll use a MenuStrip ( ) contro l to create a menu and a Panel contro l to containour Button squares.

Matching Game User Interface Prototypes

Below are pro to types o f our main user interface (UI), the menu, and the different game states:

Main UI - Starting Game State:

Game Menu:

Page 14: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

First Selection Game State:

Mismatch Game State:

Page 15: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Match Game State:

Creating the User Interface

Let's start creating our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to Mat chingGame and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to Mat ching.

Click to save your changes.

Now let's add the contro ls we'll need.

Add a MenuStrip contro l by dragging the MenuStrip from the Too lbox and dropping it on the MainForm. TheMenuStrip will dock to the top o f the Form beneath the Form Titlebar by default. Change the Name property o fthe MenuStrip to mainMenuSt rip. Click on the MenuStrip, and in the text box that appears, click again, andtype in &File as shown:

Page 16: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click to save your changes.

Note Keep in mind that the ampersand (&) character becomes a shortcut key for menu items whenyou access menus using a keyboard.

Add the Exit menu item under the File menu. Click the text box that appears under the File menu, and enterE&xit :

Click away from the menu when you finish.

As you entered each menu item, Visual Studio generated a default name for that menu item. We can keep thedefault name for the Exit menu option (exitToo lStripMenuItem) for this pro ject. If we had multiple top menuitems, we would want to edit the default menu names to prevent name conflicts.

Add the event handler C# code for the Exit menu option. Use the mouse to open the File menu, and double-click the Exit menu item to generate the Exit event handler. Add the code to the Exit event handler as shown:

Page 17: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Click to save your changes, and then click to run the program. Select File | Exit to test your work sofar and exit the matching game.

Next, let's add the contro l we need for our game in the form of a container to ho ld our Buttons. We'll use a

Panel contro l ( ) to help make dynamic contro l resizing easier. We'll have our game board resizeautomatically when the player resizes the program. We set the Panel contro l to dock in the parent contro l,which is the MainForm. If the user resizes the MainForm, the Panel contro l automatically resizes as well.

Add a Panel contro l by dragging the Panel contro l from the Containers set o f the Too lbox and dropping it onthe Form. Change the Name property o f the Panel to mainPanel. Click the black arrow at the top right o f thePanel contro l, then select Do ck in Parent Co nt ainer to change the Panel Docking property as shown:

Click to save your changes. The final user interface in the Design Editor will look like this:

And that's it fo r the user interface for our matching game! Let's move on to the coding.

Adding the Code

Page 18: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

For our matching game, we're go ing to learn how to use a two-dimensional array, more about working withuser interface (UI) elements, how to dynamically add contro ls to a Form, how to automatically resize andrelocate contro ls when a Form size is changed, work more with Form events, and get a preview of techniquesfrom a later lesson on creating generic event handlers for multiple contro ls. Let's get go ing!

Add code to create the Buttons for our grid squares and save each Button in a two-dimensional array.

Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.public partial class MainForm : Form{ private Button[,] _buttons; // Two-dimensional array of Button controls private int _buttonRows = 4; // Number of rows (and columns) of Button controls public MainForm() { // Create Button controls createButtons(); // Now initialize components InitializeComponent(); }

private void createButtons() { // Dimension two-dimensional array _buttons = new Button[_buttonRows, _buttonRows]; // Loop through each row and column of buttons for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { // Create the new Button, setting Button properties Button button = new Button() { Text = "?", Font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold), ForeColor = Color.Black }; // Set the current array element to reference the new Button _buttons[row, col] = button; } } }}...

Click to save your changes. You can run it, but it will be the same as the last run for now. Let's take acloser look at the code we added:

Page 19: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public partial class MainForm : Form{ private Button[,] _buttons; // Two-dimensional array of Button controls private int _buttonRows = 4; // Number of rows (and columns) of Button controls public MainForm() { // Create Button controls createButtons(); // Now initialize components InitializeComponent(); } private void createButtons() { // Dimension two-dimensional array _buttons = new Button[_buttonRows, _buttonRows]; // Loop through each row and column of buttons for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { // Create the new Button, setting Button properties Button button = new Button() { Text = "?", Font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold), ForeColor = Color.Black }; // Set the current array element to reference the new Button _buttons[row, col] = button; } } }}...

We declare the private class variable _but t o nRo ws to set the dimensions o f our grid. Because the grid is asquare, _but t o nRo ws can represent both the horizontal and vertical dimensions. We also create a privateclass two-dimensional array variable called _but t o ns. This variable will ho ld Button contro ls. We didn'tspecify the array size for either dimension, that way we can create the array later dynamically .

We add the private class method creat eBut t o ns to allocate the _but t o ns array, using the_squaresCo unt variable as the size for both dimensions (But t o n[_but t o nRo ws, _but t o nRo ws]). Then,we use two f o r loops to iterate through the size o f each array dimension, essentially iterating through therows and co lumns o f the array. For each array cell, we create a But t o n contro l, setting a few of the Buttonproperties, and assign this contro l to the corresponding cell using _but t o ns[row, co l].

We also add a call to creat eBut t o ns method, but we call this method before the call to InitializeComponent.In the past, we've always added code after this call. In this program, we're go ing to be using more o f the Formevents. Because some of the Form events will use the _but t o ns multidimensional array, we need to makesure that this variable is declared before any Form creation events "fire."

You may be wondering why we aren't setting the Name property o f each Button we create and place in thearray. That's because we won't be accessing the Button using the Button name, but instead by referencing thecontents o f the array cell.

Page 20: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Let's add the code we need to use the _but t o ns array to draw our playing grid. We'll add a Form Layoutevent.

Click in the title area o f the MainForm in the Design Editor, then click the Event s icon in the PropertiesWindow. Double-click the Layo ut event, bringing up the Code Editor with the newly added event handler fo rthe Layout Form event. Modify this event handler in MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.private void MainForm_Layout(object sender, LayoutEventArgs e){ // Get the height and width for each Button using the inside height and // width of the Panel control int buttonWidth = mainPanel.ClientRectangle.Width / _buttonRows; int buttonHeight = mainPanel.ClientRectangle.Height / _buttonRows; // Loop through and set the location and dimensions of each Button control for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { _buttons[row, col].SetBounds(buttonWidth * col, buttonHeight * row, buttonWidth, buttonHeight); } } // Add the Button controls to the Panel Controls collection if (mainPanel.Controls.Count == 0) { for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { mainPanel.Controls.Add(_buttons[row, col]); } } }}...

and to run the program. The game grid is now drawn and each square is a Button. Once the program isrunning, grab the edge o f the game and resize the Form. The Button contro ls should resize and repositioncorrectly.

Note The button contro ls may take a minute or two to resize.

Let's see how this code works:

Page 21: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void MainForm_Layout(object sender, LayoutEventArgs e){ // Get the height and width for each Button using the inside height and // width of the Panel control int buttonWidth = mainPanel.ClientRectangle.Width / _buttonRows; int buttonHeight = mainPanel.ClientRectangle.Height / _buttonRows; // Loop through and set the location and dimensions of each Button control for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { _buttons[row, col].SetBounds(buttonWidth * col, buttonHeight * row, buttonWidth, buttonHeight); } } // Add the Button controls to the Panel Controls collection if (mainPanel.Controls.Count == 0) { for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { mainPanel.Controls.Add(_buttons[row, col]); } } }}...

The Layo ut event occurs whenever the Form detects that it should reposition any o f the contro ls placed onthe Form. This happens when the Form is first created, and again if the Form is resized. We determine thewidth and height (but t o nWidt h and but t o nHeight ) o f all the Button contro ls by dividing the available innersize o f the Panel contro l (mainPanel.Client Rect angle ) by the number o f Button contro ls. Once we knowthe width and height o f each Button, we loop through each Button in the two-dimensional array, setting thelocation and size o f each Button using the Set Bo unds method o f the Button.

Because we created our Button contro ls at runtime (or dynamically), we need to add the contro ls to the Panelcontro l to make them visible. The Co nt ro ls object contains all o f the contro ls fo r a contro l. Before we addthem, we make sure that the Panel Contro ls Co unt is zero in order to prevent adding the Button contro lsmore than once. We could have put this code in another event, but we've placed the code here in case wewant to add code later to resize our grid.

Now that we have the game grid displayed, we need to respond to Button click events. Normally, we wouldadd individual event handlers for our Button contro ls, but fo r the playing grid, we need only a single Clickevent handler fo r all o f the Button contro ls, as long as we can distinguish which Button was clicked. We'll workwith generic, multi-contro l event handlers more a bit later in the course, but fo r now we'll create the singleClick event handler, and learn how to attach this event handler to each o f the dynamically created Button Clickevents.

Modify MainFo rm.cs as shown:

Page 22: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void createButtons(){ // Dimension two-dimensional array _buttons = new Button[_buttonRows, _buttonRows]; // Loop through each row and column of buttons for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { // Create the new Button, setting Button properties Button button = new Button() { Text = "?", Font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold), ForeColor = Color.Black }; // Set the current array element to reference the new Button _buttons[row, col] = button; // Add an event handler for this Button (same one for all of the Button controls) _buttons[row, col].Click += new EventHandler(buttonClickHandler); } }}

private void buttonClickHandler(object sender, EventArgs e){ // Get the Button control we're responding to Button button = (Button)sender; button.Text = "X";}...

and to run the program. Click any Button on the grid to see the Button Text change to X.

Let's take a look at how this code works:

Page 23: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void createButtons(){ // Dimension two-dimensional array _buttons = new Button[_buttonRows, _buttonRows]; // Loop through each row and column of buttons for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { // Create the new Button, setting Button properties Button button = new Button() { Text = "?", Font = new Font(FontFamily.GenericSansSerif, 20, FontStyle.Bold), ForeColor = Color.Black }; // Set the current array element to reference the new Button _buttons[row, col] = button; // Add an event handler for this Button (same one for all of the Button controls) _buttons[row, col].Click += new EventHandler(buttonClickHandler); } }}

private void buttonClickHandler(object sender, EventArgs e){ // Get the Button control we're responding to Button button = (Button)sender; button.Text = "X";}...

We add code to the createButtons method that uses the addition assignment operator to add anEvent Handler to the Click event o f each Button. When creating this event handler, we pass the name of amethod, but t o nClickHandler, that will serve as the event handler. Then we create thebut t o nClickHandler method, supplying the necessary parameters appropriate to an Event Handler.Whenever the but t o nClickHandler method is called, the contro l, in our case a Button, that called the eventhandler is included as the sender parameter o f the but t o nClickHandler method. The sender parameter iso f type o bject so it's compatible with all o f the contro ls they all inherit from o bject . We cast the senderparameter to (But t o n) so we can use the sender variable as a Button contro l. Using the but t o n variable,then we change the Text property o f but t o n to "X" to confirm that the event handler is working.

Before we complete the but t o nClickHandler, we need to find a way to determine which letter each Buttonwill contain, and then set each Button. To do that, we'll use a property o f the Button contro l (and manycontro ls) called the T ag property. This property allows us to assign our own information to each contro l. TheTag property has a data type o f object, so storing a string with the letter o f the Button works, but we'll have tomake sure we use the ToString method o f object when we try to read the value. Let's add that code now.Modify MainFo rm.cs as shown:

Page 24: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public MainForm(){ // Create Button controls createButtons(); // Now initialize components InitializeComponent(); // Set the card letters setCards();}...private void setCards(){ string _alphabet = "ABCDEFGHABCDEFGH"; Random random = new Random(); // Loop through each Button for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { // Find a random number into card text string int position = random.Next(0, _alphabet.Length); // Extract the card text string cardText = _alphabet[position].ToString(); // Store the card text in the Tag property of our Button control ((Button)_buttons[row, col]).Tag = cardText; // Use a temporary string to create a new card text string excluding // the previously used card text string holdString = ""; if (position > 0) holdString = _alphabet.Substring(0, position); if (position < _alphabet.Length) holdString += _alphabet.Substring(position + 1, _alphabet.Length - position - 1); // Replace previous card text string with the temporary card text string _alphabet = holdString; } }}...private void buttonClickHandler(object sender, EventArgs e){ // Get the Button control we're responding to Button button = (Button)sender; button.Text = "X"; button.Text = button.Tag.ToString();}...

Page 25: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

and to run the program. As you click on a Button, the Button will display the letter associated with it.

Let's see how this code works:

Page 26: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public MainForm(){ // Create Button controls createButtons(); // Now initialize components InitializeComponent(); // Set the card letters setCards();}...private void setCards(){ string _alphabet = "ABCDEFGHABCDEFGH"; Random random = new Random(); // Loop through each Button for (int row = 0; row < _buttonRows; row++) { for (int col = 0; col < _buttonRows; col++) { // Find a random number into card text string int position = random.Next(0, _alphabet.Length); // Extract the card text string cardText = _alphabet[position].ToString(); // Store the card text in the Tag property of our Button control ((Button)_buttons[row, col]).Tag = cardText; // Use a temporary string to create a new card text string excluding // the previously used card text string holdString = ""; if (position > 0) holdString = _alphabet.Substring(0, position); if (position < _alphabet.Length) holdString += _alphabet.Substring(position + 1, _alphabet.Length - position - 1); // Replace previous card text string with the temporary card text string _alphabet = holdString; } }}...private void buttonClickHandler(object sender, EventArgs e){ // Get the Button control we're responding to Button button = (Button)sender; button.Text = button.Tag.ToString();}...

In this code, we add a new method named set Cards that we call to assign a random letter from our

Page 27: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_alphabet to each Button contro l Tag property. The _alphabet variable ho lds all the letters we need for ourgame grid, with two copies o f each so there will be matches in the game. We use the _alphabet variable asthe master lookup o f letters we still need to add. As we loop through each Button contro l in our two-dimensional grid, we generate a random po sit io n variable that represents a position within the _alphabetvariable. We extract the letter, assign it to the T ag property, then reconstruct _alphabet by extracting theremaining portions o f the _alphabet . We use a temporary string, ho ldSt ring, to ho ld the new string, andthen assign ho ldSt ring to _alphabet .

We add a call to the set Cards method in the Form constructor fo llowing the call to the InitializeComponentmethod. We also update the Button event handler to change the Button Text property to that o f the Button T agvalue, using the ToString method to get the string value o f the stored object.

You may have noticed that in using the Random method, the parameters represent the range o f numbers;however, the first parameter, the lower boundary value, is inclusive, meaning it is included within the range,whereas the upper boundary is exclusive, meaning it is not included within the range.

Note

So, why are we using a temporary string variable ho ldSt ring, rather than deleting the foundletter from _alphabet ? I'm glad you asked. Strings are a reference type, meaning that when wedeclare a string, the string variable contains a reference data type that po ints to the actual stringcontents. Strings by definition are immutable, meaning that once they are created in memory,they cannot change. Any time you modify a string, you're actually creating a new string inmemory, and changing the reference string variable to reference (or po int to ) this new memorylocation. The previous memory is flagged for recovery by the garbage co llector. We'll revisitstrings in a later lesson in the course.

With all o f these changes, we only need to add a two final class variables, and update the Button eventhandler code to complete the coding for our matching game. Let's finish!

Modify MainFo rm.cs as shown below:

Page 28: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private Button _firstButton; // First selected Buttonprivate bool _picking = false; // Flag to indicate if we've selected first Button...private void buttonClickHandler(object sender, EventArgs e){ // Get the Button control we're responding to Button button = (Button)sender; button.Text = button.Tag.ToString(); // Make sure we aren't clicking on the same button both times // or we're selecting a button that's already part of a pair if (button.Text == "?") { if (!_picking) { // First pick _firstButton = button; button.Text = button.Tag.ToString(); button.ForeColor = Color.Red; } else if (_firstButton.Tag.ToString() == button.Tag.ToString()) { // Matched button.Text = button.Tag.ToString(); button.ForeColor = Color.Green; _firstButton.ForeColor = Color.Green; } else { // Second pick failed to match button.Text = button.Tag.ToString(); button.ForeColor = Color.Red; // We need to refresh the Button before sleeping or the Button // text change will not be visible button.Refresh(); // Pause for player to see the selected Button text System.Threading.Thread.Sleep(1000); // Change Button controls back to default text and colors _firstButton.Text = "?"; button.Text = "?"; button.ForeColor = Color.Black; _firstButton.ForeColor = Color.Black; } // Flip our picking flag _picking = !_picking; }}...

and to run the program. Now the matching game works!

Let's see how this code works:

Page 29: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private Button _firstButton; // First selected Buttonprivate bool _picking = false; // Flag to indicate if we've selected first Button...private void buttonClickHandler(object sender, EventArgs e){ // Get the Button control we're responding to Button button = (button)sender; // Make sure we aren't clicking on the same button both times // or we're selecting a button that's already part of a pair if (button.Text == "?") { if (!_picking) { // First pick _firstButton = button; button.Text = button.Tag.ToString(); button.ForeColor = Color.Red; } else if (_firstButton.Tag.ToString() == button.Tag.ToString()) { // Matched button.Text = button.Tag.ToString(); button.ForeColor = Color.Green; _firstButton.ForeColor = Color.Green; } else { // Second pick failed to match button.Text = button.Tag.ToString(); button.ForeColor = Color.Red; // We need to refresh the Button before sleeping or the Button // text change will not be visible button.Refresh(); // Pause for player to see the selected Button text System.Threading.Thread.Sleep(1000); // Change Button controls back to default text and colors _firstButton.Text = "?"; button.Text = "?"; button.ForeColor = Color.Black; _firstButton.ForeColor = Color.Black; } // Flip our picking flag _picking = !_picking; }}...

We add two new class variables. The _f irst But t o n variable is used to track the first Button contro l that isselected. The _picking variable is used to determine if we have already selected the first Button.

In the code we added to the but t o nClickHandler Button Click event handler, we make sure that we handleonly situations where the Button Text is a question mark. That means we aren't trying to click on a Button

Page 30: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

that's already been matched, and we aren't clicking on ourself once first selected. Then we make sure a firstButton has been selected by checking the _picking flag. The _picking variable is true once the first Buttonhas been clicked. When the first Button is clicked, we turn that Button text to red and show the letter stored inthe Tag property.

If a Button has already been selected, then we have to see if we have a match by comparing the T ag propertyo f the original _f irst But t o n and the current but t o n. With a match, we show the letter, and set both theoriginal and current Button text to green. If we don't find a match, we still show the letter, and change the co lorto red. When a mismatch occurs, we want to let the player see the mismatched letters fo r a short time, so weuse a Sleep method to make our program wait fo r the specified number o f milliseconds (1 second in ourcode). Once the sleep time has passed, we return both the original and current Button contro ls to their fo rmerstates with a black question mark.

Regardless o f whether the state is a first selection, a match, or a mismatch, we need to "flip" the game statusvariable, _picking. We do this with the negation operator, (!).

Just before we call the Sleep method, we call the Ref resh method o f our Button. Why? Because eventhough we changed the Button Text property to display the letter, the change to the Button contro l is notinstantaneous. A series o f events will take place that eventually cause the Button text to change and bedisplayed. These events may occur after we leave our event handler, so by using the sleep method to pauseour program, the text may not change. We call the Ref resh method to force the update to happenimmediately. Otherwise, the player will no t see the Button letter.

Note We could have skipped using the _picking variable. We could have reset _f irst But t o n to null,and checked for null rather than checking the _picking variable.

And there you have it! You've created a pretty fun game. Nice work!

Before you move on to the next lesson, do your homework. Right-click in the window where this lesson text appears and selectBack. Then select Quiz fo r this lesson in the syllabus and answer the quiz questions. When you finish the quiz questions, clickHand it in at the bottom of that window. Then do the same with any pro jects assigned for the lesson. Your instructor will gradeyour quizzes and pro jects and provide guidance if you need it.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 31: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Jagged Arrays and Array Class Methods

In this lesson, we'll cover "jagged arrays" which are esentially arrays o f arrays. We'll also cover properties and methods o f theArray class that we can use when working with arrays and then create a fun game called Math Drop where we'll learn how to usedrag and drop (we'll earn how to sort arrays in the next lesson).

This lesson includes these topics:

Jagged ArraysThe Array ObjectMath Drop - A Coding Tutorial

Jagged Arrays

Create a test pro ject using File | New | Pro ject , and name the pro ject JaggedArrays. Click to save yourchanges.

Declaring, Allocating, and Initializing Jagged Arrays

Until now, whenever we've used multidimensional arrays, each dimension was required to have the samenumber o f elements. The jagged array does not require that. When you use a jagged array, each dimensionwithn that array can have any number o f elements; one dimension could have five elements, another ten, andanother only two. This capability produces an asymmetrical grid that we refer to as a "jagged" array.

Within multidimensional arrays, the element count in each dimension is the same. Modify Fo rm1.cs asshown (add breakpo ints to enable stepping through the code to clarify the code behavior):

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray();}

private void Simple2DArray(){ // Simple two-row, four-column two-dimensional array of float values float[,] declaredAllocatedWithDefaultInitialization2DArray = new float[2, 4];}...

Click and to run the program.

In this two-dimensional array declaration, we create a grid with two rows and four co lumns, fo r a to tal o f eightindividual cells. Each row has exactly the same number o f fields: four.

Just like with standard multidimensional arrays, jagged arrays consist o f elements o f the same data type.Jagged arrays differ from multidimensional arrays in that we use a different syntax to add the array contents toeach element. Each row of a jagged array dimension ho lds an array that may have a different number o felements. So, rather than the symmetrical grid with two rows and four co lumns, a jagged array may becomprised o f a different number o f co lumns for each row. Let's try an example o f a jagged array declaration.

Modify Fo rm1.cs as shown:

Page 32: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration();}

private void JaggedArrayDeclaration(){ // A jagged array as a one-dimensional two-element array where each element is a one-dimensional array of floats float[][] sampleOne1DJaggedArray = new float[2][];}...

Click to save your changes and to run the program.

Note We can't use the same naming convention for jagged arrays as we did with multidimensionalarrays; read the comments carefully to help clarify the nature o f each piece code.

While the jagged array acts like a two-dimensional array, in fact, this jagged array is a one-dimensional array,where each element o f the array itself is an array. Because the data type held in each row is an array and assuch has multiple elements, this one-dimensional jagged array acts like a two-dimensional array, whereeach row may have a different number o f co lumns.

When declaring a jagged array, the first dimension, a one-dimensional array o f two elements, is allocated, butelements o f each o f the two elements have not yet been initialized. Before the elements o f the jagged arraymay be used, each element must be initialized. If we were to declare a jagged array and not use the newoperator, no memory would be allocated. Let's check out a jagged array declaration, fo llowed by initializationof the one-dimensional jagged array, as well as the initialization o f each element o f the jagged array.

Modify Fo rm1.cs as shown below:

Page 33: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitialization();}

private void JaggedArrayInitialization(){ // Declare a one-dimensional jagged array float[][] sampleTwo1DJaggedArray; // Initializing the one-dimensional jagged array with two elements, where each element is a one-dimensional array of floats sampleTwo1DJaggedArray = new float[2][]; // Initialize each of the two elements of the one-dimensional jagged array sampleTwo1DJaggedArray[0] = new float[4]; sampleTwo1DJaggedArray[1] = new float[6];}...

Click to save your changes and to run the program.

The first element consists o f a one-dimensional array that ho lds four floating po int numbers. The secondelement consists o f a one-dimensional array that ho lds six floating po int numbers.

We also could have initialized the jagged array elements using values, which allows us to omit the number o felements in the jagged array size.

Modify Fo rm1.cs as shown:

Page 34: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues();}

private void JaggedArrayInitializationWithValues(){ // A jagged array as a one-dimensinal two element array where each element is a one-dimensional array of floats float[][] sampleThree1DJaggedArray = new float[2][]; // Initialize each element of the jagged array using value initializers, omitting the array size sampleThree1DJaggedArray[0] = new float[] { 1, 2, 3, 4 }; sampleThree1DJaggedArray[1] = new float[] { 1, 2, 3, 4, 5, 6 };}...

Click to save your changes and to run the program.

You can also initialize the jagged array elements when you declare the array. The code snippet belowcombines the techniques we've already used, and also includes a shorthand method that is used o ften withjagged arrays.

Modify Fo rm1.cs as shown:

Page 35: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand();}

private void JaggedArrayCombinedWithShorthand(){ // Jagged array combined declaration and initialization using value initializers float[][] sampleFour1DJaggedArray = new float[][] { new float[] { 1, 2, 3, 4 }, new float[] { 1, 2, 3, 4, 5, 6 } }; // Shorthand version of jagged array combined declaration and initialization using value initializers float[][] sampleFive1DJaggedArray = { new float[] { 1, 2, 3, 4 }, new float[] { 1, 2, 3, 4, 5, 6 } };}...

Click to save your changes and to run the program.

Note

A jagged array is an array that contains arrays as elements. Arrays are data structures thatcontain multiple values held in individual index variables o f the same data type. Although thearray variable is stored on the stack, all o f the indexed variables associated with the array arestored on the heap as reference data types. Reference data types are always initialized to nulluntil explicitly initialized.

Accessing Jagged Array Elements

Accessing elements with jagged arrays is like accessing o ther array variables, except that we use the secondset o f brackets. Let's take a look at some examples o f setting and retrieving jagged array element values.

Modify Fo rm1.cs as shown (I'll remind you one last time, add breakpo ints to enable stepping through thecode to clarify the code behavior):

Page 36: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess();}

private void MultiArrayAccess(){ // Create a one-dimensional two-element jagged array of one-dimensional integer arrays int[][] sampleSix1DJaggedArray1DArray = { new int[] { 0, 2, 4, 6, 8, 10 }, new int[] { 1, 3, 5, 7, 9 } }; // Display the current value in the second row, fifth element of 9 Console.WriteLine("Original second row, fifth element: " + sampleSix1DJaggedArray1DArray[1][4]); // Output: Original second row, fifth element: 9 // Change the value in the second row, fifth element (last element) to 100 sampleSix1DJaggedArray1DArray[1][4] = 100; // Display the new value in the second row, fifth element of 100 Console.WriteLine("New second row, fifth element: " + sampleSix1DJaggedArray1DArray[1][4]); // Output: New second row, fifth element: 100}...

Click and to run the program. Remember, to view the conso le, select View | Out put .

Let's go over this code in more detail:

Page 37: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void MultiArrayAccess(){ // Create a one-dimensional two-element jagged array of one-dimensional integer arrays int[][] sampleSix1DJaggedArray1DArray = { new int[] { 0, 2, 4, 6, 8, 10 }, new int[] { 1, 3, 5, 7, 9 } }; // Display the current value in the second row, fifth element of 9 Console.WriteLine("Original second row, fifth element: " + sampleSix1DJaggedArray1DArray[1][4]); // Output: Original second row, fifth element: 9 // Change the value in the second row, fifth element (last element) to 100 sampleSix1DJaggedArray1DArray[1][4] = 100; // Display the new value in the second row, fifth element of 100 Console.WriteLine("New second row, fifth element: " + sampleSix1DJaggedArray1DArray[1][4]); // Output: New Row 2, fifth element: 100}...

We change the value o f the seco nd [1, 4] jagged array row, f if t h [1, 4 ] element in the array from 9 to 100 .

Mixing Jagged Arrays and Multidimensional Arrays

We can mix one-dimensional, multidimensional arrays, and jagged arrays together. Let's give that a try.

Modify Fo rm1.cs as shown:

Page 38: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess(); MixingArrays();}

private void MixingArrays(){ // A two-dimensional jagged array where each element is a one-dimensional array float[,][] sample2DJaggedArray1DArray = new float[1, 2][]; // A one-dimensional jagged array where each element is a two-dimensional array float[][,] sample1DJaggedArray2DArray = new float[2][,]; // A two-dimensional jagged array where each element is a two-dimensional array float[,][,] sample2DJaggedArray2DArray = new float[3, 4][,];}...

Click and to run the program.

The syntax for o ther dimensions fo llows the same patterns we've learned in earlier lessons for arrays.

The Array ObjectBefore we move on to explore how to code jagged arrays, let's discuss the Array class. The Array class providesproperties and methods for creating, manipulating, searching, and sorting one-dimensional, multidimensional, andjagged arrays. The Array class is the base class o f all arrays used in .NET, so we've already used one property o f theArray class: the Length property. The Length property returns the to tal number o f elements in all dimensions o f anarray.

Array Length Versus GetLength()

Alright. So we've already used the Lengt h property o f the Array class. We know that because our arraysinherit from the Array class, we can create an array, and then call the Length property directly. The Array classalso has the Get Lengt h() method. Length returns the to tal number o f elements in all dimensions, whereasthe GetLength() method returns the number o f elements in a specified dimension. For a one-dimensionalarray, Length and GetLength() will return the same element count. Let's experiment with some code that usesLength and GetLength().

Modify Fo rm1.cs as shown:

Page 39: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess(); MixingArrays(); ArrayLength();}

private void ArrayLength(){ // One-dimensional array int[] length1DArray = new int[4]; Console.WriteLine("1D Array - " + "L: " + length1DArray.Length + ", GL: " + length1DArray.GetLength(0) ); // Output: 1D Array - L: 4, GL: 4 // Two-dimensional array int[,] length2DArray = new int[4, 5]; Console.WriteLine("2D Array - " + "L: " + length2DArray.Length + ", GL(0): " + length2DArray.GetLength(0) + ", GL(1): " + length2DArray.GetLength(1) ); // Output: 2D Array - L: 20, GL(0): 4, GL(1): 5 // One-dimensional jagged array of one-dimensional arrays int[][] length1DJaggedArray1DArray = { new int[6], new int[5] }; Console.WriteLine("1D Jagged Array 1D Array - " + "L: " + length1DJaggedArray1DArray.Length + ", L(0): " + length1DJaggedArray1DArray[0].Length + ", L(1): " + length1DJaggedArray1DArray[1].Length + ", GL(0): " + length1DJaggedArray1DArray.GetLength(0) + ", GL(0,0): " + length1DJaggedArray1DArray[0].GetLength(0) + ", GL(1,0): " + length1DJaggedArray1DArray[1].GetLength(0) ); // Output: 1D Jagged Array 1D Array - L: 2, L(0): 6, L(1): 5, GL(0): 2, GL(0,0): 6, GL(1,0): 5}...

Click and to run the program.

Look closely at the code and make sure you understand the reasons for the results we get. The jagged array

Page 40: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

example seems to return the same values for Length and GetLength(). The first [] declares a one-dimensionalarray, so Length and GetLength() will return the same number o f elements. The second set o f [] extends ourone-dimensional array to a jagged array, where each element is also a one-dimensional array.

For GetLength(), the dimension parameter is 0-based, so the first dimension is indicated with 0 , as inlength1DArray.GetLength(0).

NoteYou might have expected GetLength() to return a value o f 11 (2 rows, the first row with 6elements, and the second row with 5 elements). However, the GetLength() method does notwork across to get the to tal number o f elements in the array elements, you would need to loopthrough each dimension and get the sum of each Length to get that result.

Array Rank

We've been using the term dimension to refer to the size o f an array, and then adding a number or "multi" as aprefix to indicate the number o f dimensions. The number o f dimensions in an array is referred to as the rank;the Array class includes a Rank property that will return the number o f dimensions in an array. When referringto the number o f elements within a specific dimension, we use the terms size and length—either term isacceptable.

Go ahead and try these examples to get a better grasp o f Rank.

Modify Fo rm1.cs as shown:

Page 41: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess(); MixingArrays(); ArrayLength(); ArrayRank();}

private void ArrayRank(){ // One-dimensional array int[] sample1DArray = new int[4]; Console.WriteLine("1D Array Rank: " + sample1DArray.Rank); // Output: 1D Array Rank: 1 // Two-dimensional array int[,] sample2DArray = new int[4, 5]; Console.WriteLine("2D Array Rank: " + sample2DArray.Rank); // Output: 2D Array Rank: 2 // Three-dimensional array int[,,] sample3DArray = new int[4, 5, 6]; Console.WriteLine("3D Array Rank: " + sample3DArray.Rank); // Output: 3D Array Rank: 3 // A one-dimensional jagged array where each element is a one-dimensional array float[][] sample1DJaggedArray1DArray = new float[1][]; sample1DJaggedArray1DArray[0] = new float[3]; Console.WriteLine("1D Jagged Array 1D Array Rank: " + sample1DJaggedArray1DArray.Rank + ", (0): " + sample1DJaggedArray1DArray[0].Rank ); // Output: 1D Jagged Array 1D Array Rank: 1, (0): 1 // A two-dimensional jagged array where each element is a one-dimensional array float[,][] sample2DJaggedArray1DArray = new float[1, 2][]; sample2DJaggedArray1DArray[0, 0] = new float[2]; sample2DJaggedArray1DArray[0, 1] = new float[3]; Console.WriteLine("2D Jagged Array 1D Array Rank: " + sample2DJaggedArray1DArray.Rank + ", (0, 0): " + sample2DJaggedArray1DArray[0, 0].Rank + ", (0, 1): " + sample2DJaggedArray1DArray[0, 1].Rank ); // Output: 2D Jagged Array 1D Array Rank: 2, (0, 0): 1, (0, 1): 1 // A one-dimensional jagged array where each element is a two-dimensional array float[][,] sample1DJaggedArray2DArray = new float[2][,]; sample1DJaggedArray2DArray[0] = new float[1, 2]; sample1DJaggedArray2DArray[1] = new float[2, 3];

Page 42: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Console.WriteLine("1D Jagged Array 2D Array Rank: " + sample1DJaggedArray2DArray.Rank + ", (0): " + sample1DJaggedArray2DArray[0].Rank + ", (1): " + sample1DJaggedArray2DArray[1].Rank ); // Output: 1D Jagged Array 2D Array Rank: 1, (0): 2, (1): 2 }...

Click and to run the program.

With the standard arrays, depending on the number o f dimensions, we get the Rank we'd expect. For jaggedarrays, we also get the Rank we expect, but we have to initialize each dimension to be able to query for theRank, o therwise, we would receive a null reference error.

So, why would we want to know the Rank o f an Array? We might be creating the arrays dynamically,depending on user-entered data. Or we might want to use the Rank property to loop through the Arrayelements.

Clearing Array Element Contents

Clearing the contents o f an array is a common task. To clear the contents o f an existing array, you could usethe Clear method o f the Array class, but usually this method is intended to clear a range o f elements. If youwant to clear the entire array, you can reallocate the array, or fo r reference data types, set the referenceelement o f the array to null. You can't use Clear as a method o f your array variable, but instead you mustreference the Array class directly, using Array.Clear() . Keep in mind that using a method by referencing theclass name is calling a static method o f the class, and not an instance method o f the class.

When clearing array elements using Clear, numeric data types are set to 0 , boo lean data types are set tofalse, and reference data types are set to null. If the only reference to the reference data types is in the arraybeing cleared, that referenced memory will be marked for recovery by the garbage co llector.

To use the Clear method, specify the array to be cleared, the starting element in the array, and the number o felements, including that element to be cleared. For a one-dimensional array, clearing is fairly intuitive, but withmultidimensional arrays, it's less so. The first element in a multidimensional array is a 0-based index into theentire array; the number o f elements to clear starts at this index position, and proceeds through the elements,even if they move to the next dimension. In o ther words, using Clear in a multidimensional array wraps fromrow to row.

Modify Fo rm1.cs as shown:

Page 43: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess(); MixingArrays(); ArrayLength(); ArrayRank(); ArrayClearing();}

private void ArrayClearing(){ // Clearing a one-dimensional array int[] clearSample1DArray = new int[] { 0, 1, 2, 3, 4, 5, 6 }; Array.Clear(clearSample1DArray, 2, 3);}...

In the code above, rather than output the array to the Conso le using a for loop, we use the ability o f theImmediate window to display arrays.

Set a breakpo int on the Array.Clear line o f code, then click and to run the program. When theDebugger pauses on the Array.Clear line o f code, bring up the Immediate Window. (To bring up the ImmediateWindow, click the Immediat e Windo w tab if it's visible, o therwise, select Debug | Windo ws | Immediat e .)In the Immediate Window, type in ? clearSample1DArray and press Ent er. Your results will look somethinglike this:

Let's execute the line o f code in the Debugger, and then output the array contents in the Immediate Window.

Pause the code using a Breakpo int on the Array.Clear line, then use the Debug option to Step Over this line o fcode and move to the next line o f code.

Page 44: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Note To step over a line o f code, click the St ep Over too lbar icon, or select Debug | St ep Over.

Now bring up the Immediate Window and type ? clearSample1DArray to output the contents o f the array.You should see this:

As expected, the Array.Clear method cleared numeric elements starting with the third element. Now let's seehow Clear works with a multidimensional array.

To test clearing multidimensional array elements, modify Fo rm1.cs as shown:

Page 45: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess(); MixingArrays(); ArrayLength(); ArrayRank(); ArrayClearing(); MultidimensionalArrayClearing();}

private void MultidimensionalArrayClearing(){ // Clearing a two-dimensional array int[,] clearSample2DArray = new int[,] { {0, 1, 2, 3}, {4, 5, 6, 7} }; Array.Clear(clearSample2DArray, 2, 3);}...

Set a Breakpo int on the Array.Clear line o f code, then click and to run the program. When theBreakpo int is highlighted, bring up the Immediate Window and enter ? clearSample2DArray to see thecontents o f the array before clearing. Next, Step Over the current line o f code, bring up the Immediate Window,and enter ? clearSample2DArray to see the array element contents after the clear.

Page 46: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

The elements are cleared, but because we were clearing three elements and we started the clear from the thirdof only four elements within the first dimension o f the array, we cleared not only the last two elements o f thefirst dimension, but also the first element o f the second dimension.

Clearing o ther multidimensional arrays o f greater rank works too; in fact we've seen that already with two-dimensional arrays.

Copying an Array

Frequently, you'll use an array task is to copy an array. When copying an array, you have the option o f ashallow o r a deep copy. A shallow copy copies only the elements o f the array, whether they are value datatypes, or reference data types. For reference data types, a shallow copy will no t copy the objects that arereferenced. After a shallow copy, you will have two arrays, the original array and the new array; both willreference the same objects in memory. If you delete the original array, the reference objects will persist. Thegarbage co llector will no t reclaim reference objects until all references to the object are removed.

In contrast to a shallow copy, a deep copy not only copies all o f the array elements, but creates copies o f anyobjects referenced by the array elements. A deep copy takes longer and so should be used only when youactually need a new copy o f the array.

All o f the Array class copy methods perform shallow copies. Deep copies typically require modifications to aclass used in the array to implement the deep copy process, although there are advanced techniques foraccomplishing a deep copy that are beyond the scope o f this course.

Be aware o f the data type o f each element you're storing in the array. Although we haven't covered C# structs(structures) yet, many o f the .Net elements are structs, such as Color. C# structs are value data types, and arestored on the stack. Also, don't confuse array assignment with copying an array. When we assign one arrayvariable to another array variable, both arrays refer to the same memory; using either variable accesses thesame array elements, including value data types. Using an Array copy method creates a new array variable,and copies the array elements into the new array. If you change a value data type in the original array, you willno t change a value data type in the new array.

The Clo ne method is the most efficient method we have in the Array class to use to perform a shallow copy.You may also use the Co py o r Co pyT o method. Clone and CopyTo require an array instance, whereasCopy is a static method. Note that Clone returns a data type o f object, and requires casting the returned objectto the correct array type.

Modify Fo rm1.cs as shown:

Page 47: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess(); MixingArrays(); ArrayLength(); ArrayRank(); ArrayClearing(); MultidimensionalArrayClearing(); ArrayCloning();}

private void ArrayCloning(){ // Using Clone // Create a one-dimensional jagged array int[][] original1DJaggedArray = new int[2][]; // Create each element of the jagged array, and initialize the values original1DJaggedArray[0] = new int[] { 1, 2 , 3, 4 }; original1DJaggedArray[1] = new int[] { 5, 6, 7 }; // Output rom the original array the value of the second element in the first jagged array Console.WriteLine("Original: " + original1DJaggedArray[0][1]); // Output: 2 // Clone the jagged array int[][] new1DJaggdArray = (int[][])original1DJaggedArray.Clone(); // Output from the new array the value of the second element in the first jagged array Console.WriteLine("New: " + new1DJaggdArray[0][1]); // Output: 2 // Modify in the original array the second element in the first jagged array original1DJaggedArray[0][1] = 10; // Output the same element from both jagged arrays to confirm the values are identical Console.WriteLine("Original After: " + original1DJaggedArray[0][1]); // Output: 10 Console.WriteLine("New After: " + new1DJaggdArray[0][1]); // Output: 10}...

Click and to run the program.

Page 48: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Resizing an Array

Frequently, we need to change the size, or dimensions, o f an array. To do this, we use the Resize method o fthe Array class. Because it changes the dimensions o f an array, this is also known as redimensioning.

When you resize an array, a new array is created, and any value data types are copied from the original arrayto the new array, and any reference data type references are copied as well. If the new array contains fewerelements, as many elements as possible are copied from the original array to the new array. If the new arraycontains more elements, the extra elements are initialized (just as with a new array) to default valuesaccording to the data type. The next code example demonstrates the syntax for resizing an array.

Modify Fo rm1.cs as shown:

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Simple2DArray(); JaggedArrayDeclaration(); JaggedArrayInitializationWithValues(); JaggedArrayCombinedWithShorthand(); MultiArrayAccess(); MixingArrays(); ArrayLength(); ArrayRank(); ArrayClearing(); MultidimensionalArrayClearing(); ArrayCloning(); ArrayResizing();}

private void ArrayResizing(){ // Resize a 1D Array string[] names = { "Bob", "Joe", "Tom", "Sam", "Fred" }; Array.Resize(ref names, names.Length - 1);}...

Click and to run the program. Use the Visual Studio Immediate window to check the value o f thenames array before and after the Resize action:

Page 49: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Other Class Array Properties and Methods

We've gone over some of the most common properties and methods available for the Array class. Toexperiment with o thers, create an Array and use Visual Studio Intellisense to bring up the complete list o fproperties and methods. We've mentioned using Intellisense before, and you've seen the Intellisense dialogappear from time to time. To use Intellisense, type an array name and a period. Intellisense displays theavailable properties and methods:

Another great too l in Visual Studio is the Object Browser. To access the Object Browser, use the F2 key, orthe View menu.

Select View | Object Bro wser. Scro ll down in the list until you find the msco rlib entries. You will seemultiple mscorlib entries, each fo llowed with by a .NET version in square brackets. Expand the latest version.In our example, the version is 4.0 .0 .0 . Scro ll down within the msco rlib [4 .0 .0 .0] entry and select Syst em :

Why did we choose Syst em? All arrays inherit from the Array class, and the Array class is in the Systemnamespace. You can simply type Array in the Studio Code Editor, and hover your mouse over the Array textuntil Intellisense appears:

Let's return to the Object Browser. Locate the Array class, and use that entry to see more o f the properties andmethods o f this class.

In the Object Browser, scro ll down in the System namespace, and select the Array entry. At the top right, in theObject Browser you see a list o f properties and methods o f the Array class. Scro ll down that list and select theLength property. At the bottom right, in the Object Browser is a description o f the Length property:

Page 50: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

As an alternative, you can use the website version o f the class at Online MSDN Array Class documentation.

Math Drop - A Coding TutorialLet's get busy coding now! We'll create a math addition game called Math Drop. The object o f the game is to selectfrom a list o f addition problems, dropping each o f the problems onto an answer (a Button contro l), until all o f theaddition problems are answered correctly. The game will return the results. We'll use a two-dimensional array to storeButton contro ls that will represent each square o f the game. We'll use a one-dimensional jagged array to ho ld the

answers dropped on each Button. We'll also use a SplitContainer ( ) contro l to separate thequestions from the answers. The answer Button contro ls will be contained in one o f the SplitContainer contro l panels,while the questions will be held in a ListBox contro l in the o ther panel. We'll also include a menu. Finally, we'll learnhow to implement a familiar UI feature called "drag and drop." Let's get started!

Math Drop Game User Interface Prototypes

Here's an image o f our main user interface (UI) and menu with the initial game state:

Main UI - Menu and Initial Game State

Page 51: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating the User Interface

Let's start creating our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to Mat hDro p and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to Mat h Dro p.

Change the StartPosition property to Cent erScreen. Click to save your changes.

Now let's add the contro ls we'll need.

Add a MenuStrip contro l by dragging the MenuStrip from the Too lbox and dropping it on the MainForm. TheMenuStrip, by default, will dock to the top o f the Form beneath the Form Titlebar. Change the Name property o fthe MenuStrip to mainMenuSt rip. Click on the MenuStrip, and click in the text box that appears, then type

&File . Click to save your changes.

Now add the Reset, horizontal separator, and Exit menu items under the File menu.

Click the text box that appears under the File menu, and enter &Reset . Beneath this menu item, click the textbox and type in a - (dash). Finally, click the next menu text box, and type E&xit . Click away from the menu toclose the editing o f the menu.

We'll keep the default names generated by Studio for each menu item.

Okay, now we'll add the event handler C# code for the Reset and Exit menu options.

Use the mouse to open the File menu, and double-click the Reset menu item to generate the Reset eventhandler fo r this menu item. Then, double-click the Exit menu item to generate the Exit event handler fo r thismenu item. To the Exit event handler, add the code as shown:

Page 52: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Click to save your changes.

Next, let's add the SplitContainer contro l and ListBox contro ls. By default, the SplitContainer will automaticallydock to the parent contro l (the Form) with vertical o rientation—we want that fo r this pro ject. We'll need to dockthe ListBox once we add it.

Click on the SplitContainer contro l in the Too lBox, and drag it onto the Form. Change the Name property tomainSplit Co nt ainer. Change the BackColor property to Cho co lat e under the Web tab o f the co lorselection dialog that appears when you click on the BackColor value. Drag a ListBox contro l from theToolBox onto the Panel1 panel o f the mainSplitContainer (the left-side panel). Change the Name property o fthe ListBox to quest io nsList Bo x, the BackColor property to SandyBro wn under the Web tab o f the co lorselection dialog that appears when you click on the BackColor value, and the Dock property to Fill byselecting the middle option that appears when selecting the Dock property value. Change the ListBox FontSize property to 12 and Font Bo ld property to T rue .

The final user interface in the Design Editor should look like this:

Page 53: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Note Although we can rename the SplitContainer contro l, we cannot rename the Panel contro l withinthe SplitContainer contro l. The Panels are named Panel1 and Panel2.

That's it fo r the user interface. Next, coding!

Adding the Code

The first coding step is to add a new class named Quest io n. Each question is composed o f two parts, thequestion, and the answer. Rather than store the question string in the ListBox, we'll store the actual Questionobject. We'll take advantage o f the fact that when a ListBox stores objects, the text that the ListBox displays isretrieved using the ToString method. We can override the parent class ToString method in the Question class,and code the Question class to return the question for this overridden method.

Right-click on the name Mat h Dro p in the So lution Explorer panel, select Add | Class, enter Quest io n in theName textbox, and click Add. Modify the Quest io n.cs class code as shown:

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace MathDrop{ public class Question { private string _theQuestion; public string TheQuestion { get { return _theQuestion; } set { _theQuestion = value; } } private int _theAnswer; public int TheAnswer { get { return _theAnswer; } set { _theAnswer = value; } } // Override ToString() so when adding array to ListBox the ListBox will // call this version of ToString to display the question public override string ToString() { return _theQuestion; } }}

Click to save your changes.

Page 54: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Override ToString() so when adding array to ListBox the ListBox will// call this version of ToString to display the questionpublic override string ToString(){ return _theQuestion;}...

We added a T o St ring method to the Question class, using the o verride qualifier to override the base classToString method.

Next, we'll add a couple class variables and a method to create the Buttons for our grid squares, saving eachButton in a two-dimensional array. We'll set the Button text to be a possible answer to each question. We'llalso create the one-dimensional array for our questions, and the one-dimensional jagged array for ouranswers. Finally, we'll call this method from the Form constructor.

Modify MainFo rm.cs as shown:

Page 55: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. public partial class MainForm1 : Form { private int _squaresCount = 5; // Number of squares (horizontal and vertical) private int _numberOfQuestions = 15; // Number of questions private Button[,] _buttons; // Buttons array private Question[] _questions; // Questions array private Question[][] _answers; // Answers jagged array public MainForm() { // Create the Button controls and arrays createButtonsAndArrays(); InitializeComponent(); } private void createButtonsAndArrays() { // Allocate a two-dimensional array (grid) for our squares using Button controls _buttons = new Button[_squaresCount, _squaresCount]; // Allocate one-dimensional array for our questions _questions = new Question[_numberOfQuestions]; // Allocate a two-dimensional jagged array for our answers _answers = new Question[_squaresCount * _squaresCount][]; // Sequential answer int buttonAnswer = 0; // Create each Button control in our array for (int row = 0; row < _squaresCount; row++) { for (int col = 0; col < _squaresCount; col++) { // Create and assign new Button Button button = new Button() { Text = buttonAnswer.ToString(), BackColor = Color.White, FlatStyle = FlatStyle.Popup, Cursor = Cursors.Hand, AllowDrop = true }; _buttons[row, col] = button; // Increment answer value buttonAnswer++; } } } }...

Click to save your changes.

Page 56: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public partial class MainForm1 : Form { private int _squaresCount = 5; // Number of squares (horizontal and vertical) private int _numberOfQuestions = 15; // Number of questions private Button[,] _buttons; // Buttons array private Question[] _questions; // Questions array private Question[][] _answers; // Answers jagged array public MainForm() { // Create the Button controls and arrays createButtonsAndArrays(); InitializeComponent(); } private void createButtonsAndArrays() { // Allocate a two-dimensional array (grid) for our squares using Button controls _buttons = new Button[_squaresCount, _squaresCount]; // Allocate one-dimensional array for our questions _questions = new Question[_numberOfQuestions]; // Allocate one-dimensional array for our questions _questions = new Question[_numberOfQuestions]; // Allocate a two-dimensional jagged array for our answers _answers = new Question[_squaresCount * _squaresCount][]; // Sequential answer int buttonAnswer = 0; // Create each Button control in our array for (int row = 0; row < _squaresCount; row++) { for (int col = 0; col < _squaresCount; col++) { // Create and assign new Button Button button = new Button() { Text = buttonAnswer.ToString(), BackColor = Color.White, FlatStyle = FlatStyle.Popup, Cursor = Cursors.Hand, AllowDrop = true }; _buttons[row, col] = button; // Increment answer value buttonAnswer++; } } }...

Some of this code may be familiar to you from our previous tutorials. We declare a one-dimensional array_quest io ns o f type Quest io n; this new class will ho ld our questions. We declare _answers as a one-

Page 57: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

dimensional jagged array o f type Quest io n. In the creat eBut t o nsAndArrays method, we create botharrays. The size o f each o f these arrays is determined by different constructs o f our program: the size o f_answers by our Button row and co lumn count, and the size o f the _quest io ns by the number o f questionswe create. We've added the class variable _numberOf Quest io ns that sets the number o f questions.

Within the creat eBut t o nsAndArrays method, we use an integer variable but t o nAnswer to create thepossible answers, assigning this value to the Button text when we create the but t o n. While creating thebut t o n, we also set some relevant properties including the Flat St yle (to ensure we have a nice borderaround each Button), the Curso r (to appear as a hand when the mouse passes over the Button), andAllo wDro p (to t rue ). We enable Allo wDro p to allow the Button to act as a drop target. Finally, we modify theForm constructor to call the creat eBut t o nsAndArrays method.

Next, let's add a Form Layout event handler, and add the code that draws the Button contro ls.

Click the MainForm, then select the Event icon in the Properties Window. Double-click the Layout event. Addthe code to the Layout event handler as shown:

CODE TO TYPE:

.

.

.private void MainForm_Layout(object sender, LayoutEventArgs e){ // Get the height and width for each Button using the inside height and // width of the Panel control int buttonWidth = mainSplitContainer.Panel2.ClientRectangle.Width / _squaresCount; int buttonHeight = mainSplitContainer.Panel2.ClientRectangle.Height / _squaresCount; // Loop through and set the location and dimensions of each Button control for (int row = 0; row < _squaresCount; row++) { for (int col = 0; col < _squaresCount; col++) _buttons[row, col].SetBounds(buttonWidth * col, buttonHeight * row, buttonWidth, buttonHeight); } // Add the Button controls to the Panel Controls collection if (mainSplitContainer.Panel2.Controls.Count == 0) { for (int row = 0; row < _squaresCount; row++) { for (int col = 0; col < _squaresCount; col++) mainSplitContainer.Panel2.Controls.Add(_buttons[row, col]); } }}...

and to run the program. The answer grid is drawn; each square is a Button. Once the program isrunning, grab the edge o f the game to resize the Form. The Button contro ls should resize and reposition thegame along with the ListBox.

Let's discuss this code.

Page 58: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void MainForm_Layout(object sender, LayoutEventArgs e){ // Get the height and width for each Button using the inside height and // width of the Panel control int buttonWidth = mainSplitContainer.Panel2.ClientRectangle.Width / _squaresCount; int buttonHeight = mainSplitContainer.Panel2.ClientRectangle.Height / _squaresCount; // Loop through and set the location and dimensions of each Button control for (int row = 0; row < _squaresCount; row++) { for (int col = 0; col < _squaresCount; col++) _buttons[row, col].SetBounds(buttonWidth * col, buttonHeight * row, buttonWidth, buttonHeight); } // Add the Button controls to the Panel Controls collection if (mainSplitContainer.Panel2.Controls.Count == 0) { for (int row = 0; row < _squaresCount; row++) { for (int col = 0; col < _squaresCount; col++) mainSplitContainer.Panel2.Controls.Add(_buttons[row, col]); } }}...

First we calculate the necessary height and width for each Button based on the inside width and height o f thePanel2 o f the SplitContainer. We loop through each o f the Button contro ls and set their locations anddimensions using Set Bo unds. Finally, we add each Button contro l to the SplitContainer Panel2 Contro lsobject.

Note

In the line that calls Set Bo unds, we used a single-line scope in the inner f o r loop without thecurly bracket scope operators. This is allowed if we have only a single line that applies to the forloop. We are omitting the curly bracketss in this tutorial fo r single-line scope to conserve spacein the coding sections, and to familiarize you with this common alternative coding method.You'll see this technique repeated throughout the tutorial with o ther single-line scopeoperations, such as if statements.

Next, we'll code the game reset. This method will clear both the questions array and the answers jaggedarray, populate our questions array, and populate the ListBox with the questions.

Modify MainFo rm.cs as shown:

Page 59: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. public MainForm(){ // Create the Button controls and arrays createButtonsAndArrays(); InitializeComponent(); // Clear questions and answers, create questions and populate ListBox resetProgram();}

private void resetProgram(){ Array.Clear(_questions, 0, _questions.Length); // Clear all of the answers from the jagged array and set the array index to null for (int i = 0; i < _answers.Length; i++) { if (_answers[i] != null) { Array.Clear(_answers[i], 0, _answers[i].Length); _answers[i] = null; } } int maxAnswer = _squaresCount * _squaresCount - 1; Random random = new Random(); for (int i = 0; i < _numberOfQuestions; i++) { int randomLeft = random.Next(0, maxAnswer + 1); int randomRight = random.Next(0, maxAnswer - randomLeft + 1); int answer = randomLeft + randomRight; string question = randomLeft.ToString() + " + " + randomRight.ToString(); _questions[i] = new Question() { TheQuestion = question, TheAnswer = answer }; } // Reset the questions in ListBox questionsListBox.Items.Clear(); for (int i = 0; i < _questions.Length; i++) questionsListBox.Items.Add(_questions[i]);}...

Click and to run the program. Now the questions appear.

Page 60: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public MainForm(){ // Create the Button controls and arrays createButtonsAndArrays(); InitializeComponent(); // Clear questions and answers, create questions and populate ListBox resetProgram();}

private void resetProgram(){ Array.Clear(_questions, 0, _questions.Length); // Clear all of the answers from the jagged array and set the array index to null for (int i = 0; i < _answers.Length; i++) { if (_answers[i] != null) { Array.Clear(_answers[i], 0, _answers[i].Length); _answers[i] = null; } } int maxAnswer = _squaresCount * _squaresCount - 1; Random random = new Random(); for (int i = 0; i < _numberOfQuestions; i++) { int randomLeft = random.Next(0, maxAnswer + 1); int randomRight = random.Next(0, maxAnswer - randomLeft + 1); int answer = randomLeft + randomRight; string question = randomLeft.ToString() + " + " + randomRight.ToString(); _questions[i] = new Question() { TheQuestion = question, TheAnswer = answer }; } // Reset the questions in ListBox questionsListBox.Items.Clear(); for (int i = 0; i < _questions.Length; i++) questionsListBox.Items.Add(_questions[i]);}...

The reset Pro gram method begins by clearing all o f the elements o f the _quest io ns array using theArray.Clear method. The _quest io ns array contains Quest io n objects, so calling the Array.Clear methodeffectively sets each array element to null.

Next, we clear the _answers jagged array, again using the Array.Clear; however, this time we use a f o r loopto iterate through each element o f our jagged array. Since jagged array is an array o f arrays, each element o fthe one-dimensional jagged array is itself an array, where each element o f that array is an object. Simplycalling Array.Clear would result in clearing the jagged array, but not the arrays that compose the jaggedarray. By looping through each element o f the jagged array and calling Array.Clear, we make sure that weclear all o f the Quest io n objects. After we've cleared each element o f the array, we clear that element fromthe jagged array manually by setting it to null. Alternatively, we could have waited until after we'd cleared all o f

Page 61: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

the jagged array manually by setting it to null. Alternatively, we could have waited until after we'd cleared all o fthe elements o f the jagged array, and then called Array.Clear on the jagged array itself.

Now that all o f the arrays are clear, we can create our questions. After we create each quest io n and answer,we create a But t o n, setting the T heQuest io n and T heAnswer properties using an inline technique. Thenwe Clear the ListBox that ho lds the questions, and loop through _quest io ns, adding each question to ourListBox using the Add method o f the It ems co llection. Remember, because we overrode the ToString()method in our Quest io n class, when we add a Quest io n object to the ListBox, it will display theT heQuest io n property o f the class.

Let's go over how we generated each question:

OBSERVE:

.

.

. int maxAnswer = _squaresCount * _squaresCount - 1; Random random = new Random();for (int i = 0; i < _numberOfQuestions; i++){ int randomLeft = random.Next(0, maxAnswer + 1); int randomRight = random.Next(0, maxAnswer - randomLeft + 1); int answer = randomLeft + randomRight; string question = randomLeft.ToString() + " + " + randomRight.ToString(); _questions[i] = new Question() { TheQuestion = question, TheAnswer = answer };}...

To create a question, first we generate a random number called rando mLef t fo r the left side o f our additionproblem. We add one to the random number because the second parameter to the Next method is exclusive.Because we have a limited number o f answer squares, we have to determine the highest possible answerwhich is known as our maxAnswer. This will serve as our upper boundary when we generate rando mLef t .Once we know the left-side value o f the addition problem, the right-side value, rando mRight , will also be arandom number, but its upper boundary is go ing to be the difference between our maximum possible answer,maxAnswer, and the first number o f the problem, rando mLef t . Now that we've got both o f the numbers weneed to compose the question, we can generate answer. We construct the entire question and answer byusing the T o St ring method o f each o f our numbers.

Next, we need to enable the File | Reset menu option, and updating our game if the user moves the splitter.The SplitContainer we used includes the ability to change the size o f each Panel.

Select the MainFo rm.cs [Design] window. Select the mainSplit Co nt ainer contro l, then click the Eventicon in the Properties Window. Double-click on the Split t erMo ved event to generate the event handler.Modify MainFo rm.cs as shown.

NoteSelecting the mainSplit Co nt ainer contro l might be confusing because you might be temptedto select one o f the Panel contro ls. You can be sure and select the contro l you want using thecontro l from the dropdown box at the top o f the Properties Window.

Page 62: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void resetToolStripMenuItem_Click(object sender, EventArgs e){ // Respond to the Reset menu option being selected resetProgram();}...private void mainSplitContainer_SplitterMoved(object sender, SplitterEventArgs e){ // Moved the splitter so force Layout event this.PerformLayout();}...

and to run the program. Now when you select File | Reset , new questions appear in the ListBox asthe Reset event handler calls the new reset method. Also, if you click and adjust the splitter, the gameprogram is redrawn.

Now let's add the drag and drop capability so the user can click and drag a question from the ListBox over ananswer Button and then drop the question onto the answer. Adjust the Studio code display to co llapse all o fthe existing code to definitions, allowing us to focus on just the new code (you can always click on the plus(+) icon in the left margin o f the Code Editor to expand code you want to see).

In MainForm.cs, right-click in the Code Editor and select Out lining | Co llapse t o Def init io ns to hide thecode in the sections we've already added.

Before we code the drag-and-drop functionality, let's discuss what we'll need, and how drag and drop works.

For drag-and-drop to work, we have to enable the target contro l to support it. We did that earlier when wecreated the Button contro ls. For basic support o f drag-and-drop, we need these events:

ListBox - MouseMove event: Potentially starts a drag operation, but must validate that the leftmouse button is down and that a valid ListBox item is selected.Button - DragEnter: Validate that the drag item is correct fo r the Button, and visually indicate that theButton is a valid target to complete the drag.Button - DragDrop: Validate that the drag item is correct fo r the Button, and complete the dropoperation.

There are o ther events we could respond to for more advanced drag-and-drop operations, but fo r now thesethree are fine. Let's go ahead and add the Button drag events. Because we have so many Buttons, rather thanadd an event fo r each Button, we'll add a single method to each Button to handle each o f these events.

First we'll add each individual event handler to the creat eBut t o nsAndArrays method. As you type in theline o f code, after you type in the += , a dialog prompts you to press the T ab key to automatically generate theevent handler code. Press the T ab key and enter the name of the event handler method as shown in the codelisting. Once you've entered the event handler method name, you will again be prompted to press the T ab keyto generate the event handler method. Press the T ab key to generate that method code as well. Repeat thisprocess for both event handlers. Modify the MainFo rm.cs code as shown:

Page 63: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void createButtonsAndArrays(){ // Allocate a two-dimensional array (grid) for our squares using Button controls _buttons = new Button[_squaresCount, _squaresCount]; // Allocate one-dimensional array for our questions _questions = new Question[_numberOfQuestions]; // Allocate a two-dimensional jagged array for our answers _answers = new Question[_squaresCount * _squaresCount][]; // Sequential answer int buttonAnswer = 0; // Create each Button control in our array for (int row = 0; row < _squaresCount; row++) { for (int col = 0; col < _squaresCount; col++) { // Create and assign new Button Button button = new Button() { Text = buttonAnswer.ToString(), BackColor = Color.White, FlatStyle = FlatStyle.Popup, Cursor = Cursors.Hand, Font = new Font(FontFamily.GenericSansSerif, 14, FontStyle.Bold), AllowDrop = true }; _buttons[row, col] = button; // Increment answer value buttonAnswer++; // Add the same event handler for the DragEnter event for each Button _buttons[row, col].DragEnter += new DragEventHandler(buttonDragEnterHandler); // Add the same event handler for the DragDrop event for each Button _buttons[row, col].DragDrop += new DragEventHandler(buttonDragDropHandler); } }}

void buttonDragEnterHandler(object sender, DragEventArgs e){ throw new NotImplementedException();} void buttonDragDropHandler(object sender, DragEventArgs e){ throw new NotImplementedException();}...

We've seen these event handlers before; using the event handler wizard to generate them makes it pretty

Page 64: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

convenient to add them. By default the event method handlers include code that implies that any attempt touse the event handler will generate a "not implemented exception." This technique is a handy reminder thatyou need to finish coding an event handler. We'll see more o f these exceptions in a later lesson.

The code we added doesn't do anything yet, so let's add the ListBox MouseMove event handler, and the codefor our Button drag event handlers which will allow us to test our code.

Select the ListBox and, using the Properties Window Event icon, double-click the MouseMove event togenerate the event handler. Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.private void buttonDragEnterHandler(object sender, DragEventArgs e){ throw new NotImplementedException(); // Only show drag effect for data of the correct type if (!e.Data.GetDataPresent(typeof(Question))) e.Effect = DragDropEffects.None; else // Show the Move effect cursor e.Effect = DragDropEffects.Move;}

private void buttonDragDropHandler(object sender, DragEventArgs e){ throw new NotImplementedException(); // Only process drag operation if correct data type if (e.Data.GetDataPresent(typeof(Question))) { // Extract question from drag data, and make sure the question is not null Question question = (Question)e.Data.GetData(typeof(Question)); if (question != null) { Button button = sender as Button; MessageBox.Show(button.Text + " selected"); } }}

private void questionsListBox_MouseMove(object sender, MouseEventArgs e){ // Make sure we have the left mouse button down, and a valid list view item under mouse if (e.Button == MouseButtons.Left) { if (questionsListBox.SelectedIndex != ListBox.NoMatches) questionsListBox.DoDragDrop((Question)questionsListBox.Items[questionsListBox.SelectedIndex], DragDropEffects.All); }}...

and to run the program. Click and drag an item in the ListBox to one o f the answer Button contro ls,noting the change in your cursor. When you release the drag, a message box appears that identifies theButton contro l that received the drop question.

Page 65: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void buttonDragEnterHandler(object sender, DragEventArgs e){ // Only show drag effect for data of the correct type if (!e.Data.GetDataPresent(typeof(Question))) e.Effect = DragDropEffects.None; else // Show the Move effect cursor e.Effect = DragDropEffects.Move;}

private void buttonDragDropHandler(object sender, DragEventArgs e){ // Only process drag operation if correct data type if (e.Data.GetDataPresent(typeof(Question))) { // Extract question from drag data, and make sure the question is not null Question question = (Question)e.Data.GetData(typeof(Question)); if (question != null) { Button button = sender as Button; MessageBox.Show(button.Text + " selected"); } }}

private void questionsListBox_MouseMove(object sender, MouseEventArgs e){ // Make sure we have the left mouse button down, and a valid list view item under mouse if (e.Button == MouseButtons.Left) { if (questionsListBox.SelectedIndex != ListBox.NoMatches) questionsListBox.DoDragDrop((Question)questionsListBox.Items[questionsListBox.SelectedIndex], DragDropEffects.All); }}...

We added privat e access modifiers to both o f the generated drag event handlers to be consistent with therest o f our methods and properties. In the but t o nDragDro pHandler method, we used a differentmechanism for casting our sender parameter to a Button using the as operator. Using as fo r casting will setthe new variable to null rather than raise an exception if there is a conversion failure.

For the ListBox MouseMove event, we make sure that the Lef t mouse button is down. Then, we check tomake sure that the Select edIndex property, which indicates the index o f the item selected in the ListBox,does not equal the ListBox No Mat ches property, and that we have a valid ListBox item selected. Finally, weinitiate the drag-and-drop process by calling the ListBox Do DragDro p method, sending the selectedquestion from the ListBox with whichever DragDro pEf f ect s we want to support (which is all o f them).

In the but t o nDragEnt erHandler, we need to validate that the object to be dropped is the correct data type.We do that by extracting the data using the Get Dat aPresent method and passing the desired type using theC# t ypeo f operator and our desired type, which is Quest io n. If we have the correct type, we'll set theEf f ect to DragDro pEf f ect s.Mo ve to show the move cursor; o therwise, we'll set it toDragDro pEf f ect s.No ne .

The final step is the drop, which is handled in the but t o nDragDro pHandler method. We validate the datatype o f the object being dropped, and if that's correct, we extract our question using the Get Dat a method, andpass in the type o f data we require. We use an explicit cast (Quest io n) to ensure our data is converted from

Page 66: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

object to Quest io n. Once we confirm that the extracted quest io n is not null, we display a MessageBo x thatindicates onto which Button contro l we dropped the question.

Let's finish our game code. Update the but t o nDragDro pHandler to test and notify the player whether theanswer is correct or incorrect, and remove the question from the ListBox if it is correct. We'll also add eachquestion that's dropped onto a Button answer to our jagged array so that we can review the results later.

Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

. private void buttonDragDropHandler(object sender, DragEventArgs e){ // Only process drag operation if correct data type if (e.Data.GetDataPresent(typeof(Question))) { // Extract question from drag data, and make sure the question is not null Question question = (Question)e.Data.GetData(typeof(Question)); if (question != null) { Button button = sender as Button; MessageBox.Show(button.Text + " selected"); // Store all of the answers in the _answers jagged array int answer; if (!int.TryParse(button.Text, out answer)) answer = 0; if (_answers[answer] == null) // The jagged array hasn't been created, so create the first entry _answers[answer] = new Question[1]; else // Already have entries in jagged array, resize to length + 1 Array.Resize(ref _answers[answer], _answers[answer].Length + 1); // Add the question to the jagged array _answers[answer][_answers[answer].Length - 1] = question; // Test if correct answer, display dialog box, remove from ListBox if (button.Text == question.TheAnswer.ToString()) { // Remove the question from the ListBox questionsListBox.Items.RemoveAt(questionsListBox.SelectedIndex); MessageBox.Show(button.Text + " is correct!"); } else MessageBox.Show("Wrong answer, try again."); } }}...

and to run the program. Drag a question onto an answer and see what happens!

Let's discuss this code.

Page 67: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void buttonDragDropHandler(object sender, DragEventArgs e){ // Only process drag operation if correct data type if (e.Data.GetDataPresent(typeof(Question))) { // Extract question from drag data, and make sure the question is not null Question question = (Question)e.Data.GetData(typeof(Question)); if (question != null) { Button button = sender as Button; // Store all of the answers in the _answers jagged array int answer; if (!int.TryParse(button.Text, out answer)) answer = 0; if (_answers[answer] == null) // The jaggd array hasn't been created, so create the first entry _answers[answer] = new Question[1]; else // Already have entires in jagged array, resize to length + 1 Array.Resize(ref _answers[answer], _answers[answer].Length + 1); // Add the question to the jagged array _answers[answer][_answers[answer].Length - 1] = question; // Test if correct answer, display dialog box, remove from ListBox if (button.Text == question.TheAnswer.ToString()) { // Remove the question from the ListBox questionsListBox.Items.RemoveAt(questionsListBox.SelectedIndex); MessageBox.Show(button.Text + " is correct!"); } else MessageBox.Show("Wrong answer, try again."); } }}...

Our _answers jagged array is 0-indexed, and we have one jagged array element fo r each o f our Buttoncontro ls. We use T ryParse to convert the answer, which is the Text o f the Button contro l. Not only is answerthe anwer to the question, but it is also the index into our jagged array. For each element in our jagged array,we have to test to determine if that array has been created. If no t, we create a one-dimensional Quest io narray with a size o f [1] . If the jagged array element already exists, we use Array.Resize to increase the sizeof the array by one more than the array Lengt h. Once we've increased the size o f the _answers jagged arrayelement, we add the quest io n to the new element. Finally, we test to see if the answer is correct by testingthe T ext o f the Button contro l to the T heAnswer property o f our quest io n. If the answer is correct, we alsouse Remo veAt to remove the question from our ListBox.

There you have it! You've just created a fun addition math game that uses drag-and-drop!

Before you move on to the next lesson, do your homework. Right-click in the window where this lesson text appears and selectBack. Then select Quiz fo r this lesson in the syllabus and answer the quiz questions. When you finish the quiz questions, clickHAND IT IN at the bottom of that window. Then do the same with the Pro ject(s) fo r the lesson. Your instructor will grade yourquiz(zes) and pro ject(s) and provide guidance if you need it.

Great job so far! See you in the next lesson....

Copyright © 1998-2014 O'Reilly Media, Inc.

Page 68: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 69: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Array Indexers and Sorting

In this lesson we'll learn about adding indexers to classes, as well as Array class sorting. Adding indexers makes it possible toaccess a class in the same way we access an array.

Here are the topics we'll cover:

IndexersSorting ArraysTiler - A Coding Tutorial

IndexersFor this lesson, create a test pro ject using File | New | Pro ject , and change the Name of the pro ject to

ArrayIndexers. Click to save your changes.

Until now, when we wanted to ho ld arrays o f a class, we created a class and used a jagged array. We've used anArrayList to ho ld a class co llection. We can also implement a co llection using an array and indexers. When we add anindexer to a class, the indexer is coded and works just like an accessor, except that the accessor signature includesthe t his keyword and includes array brackets ([ ]). Let's create some code without using indexers, then add an indexer.

Note Note that we have created another class within the Form1.cs file. When creating a class within anotherclass, you must either use public as the access modifier, o r omit the access modifier.

Modify Fo rm1.cs as shown:

Page 70: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. public partial class Form1 : Form{ public Form1() { InitializeComponent(); CreateWeekdays(); } private void CreateWeekdays() { // Create our Weekdays class, and initialize day of the week variables Weekdays weekDays = new Weekdays(); string weekDay = "Tuesday"; int dayOfWeek = 3; // Using methods of Weekdays class Console.WriteLine("Day of week (" + weekDay +") - " + weekDays.getDayOfWeek(weekDay)); // Output: Day of week (Tuesday) - 3 Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays.getDay(dayOfWeek)); // Output: Weekday (3) - Tuesday // Using array property of Weekdays class Console.WriteLine("Day of week (" + weekDay + ") - Not supported without doing an Array.Find"); // Output: Day of week (Tuesday) - Not supported without doing an Array.Find Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays.Days[dayOfWeek]); // Output: Weekday (3) - Wednesday Console.WriteLine("Set a final breakpoint on this line"); } private void Form1_Load(object sender, EventArgs e) { }}

class Weekdays{ private string[] _days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; public string[] Days { get { return _days; } } public int getDayOfWeek(string day) { int dayOfWeek = -1; for (int i = 0; i < _days.Length; i++) { if (_days[i] == day) dayOfWeek = i; }

Page 71: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

return dayOfWeek; } public string getDay(int dayOfWeek) { if (dayOfWeek > 0 && dayOfWeek <= _days.Length) return _days[dayOfWeek - 1]; else return string.Empty; }}...

Click to save your changes and to run the program.

Let's discuss this code.

Page 72: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public partial class Form1 : Form{ public Form1() { InitializeComponent(); CreateWeekdays(); } private void CreateWeekdays() { // Create our Weekdays class, and initialize day of the week variables Weekdays weekDays = new Weekdays(); string weekDay = "Tuesday"; int dayOfWeek = 3; // Using methods of Weekdays class Console.WriteLine("Day of week (" + weekDay +") - " + weekDays.getDayOfWeek(weekDay)); // Output: Day of week (Tuesday) - 3 Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays.getDay(dayOfWeek)); // Output: Weekday (3) - Tuesday // Using array property of Weekdays class Console.WriteLine("Day of week (" + weekDay + ") - Not supported without doing an Array.Find"); // Output: Day of week (Tuesday) - Not supported without doing an Array.Find Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays.Days[dayOfWeek]); // Output: Weekday (3) - Wednesday Console.WriteLine("Set a final breakpoint on this line"); }} class Weekdays{ private string[] _days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; public string[] Days { get { return _days; } } public int getDayOfWeek(string day) { int dayOfWeek = -1; for (int i = 0; i < _days.Length; i++) { if (_days[i] == day) dayOfWeek = i; } return dayOfWeek; } public string getDay(int dayOfWeek) {

Page 73: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

if (dayOfWeek > 0 && dayOfWeek <= _days.Length) return _days[dayOfWeek - 1]; else return string.Empty; }}...

We create a Weekdays class and add public methods to determine the numeric day o f the week given a weekdayname, or a weekday name given a numeric day o f the week (1-based). We also create an accessor to access theunderlying array o f week day names to allow for direct access using an array index. Note that we can't search our arraydirectly if we want to return the numeric day o f the week using a direct array access, unless we use the Find method o fthe Array class. Let's change the Weekdays class by coding indexers.

Modify Fo rm1.cs as shown:

Page 74: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. class Weekdays{ private string[] _days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; public string[] Days { get { return _days; } } public int getDayOfWeek(string day) { int dayOfWeek = -1; for (int i = 0; i < _days.Length; i++) { if (_days[i] == day) dayOfWeek = i; } return dayOfWeek; } public string getDay(int dayOfWeek) { if (dayOfWeek > 0 && dayOfWeek <= _days.Length) return _days[dayOfWeek - 1]; else return string.Empty; } public int this[string day] { get { int dayOfWeek = -1; for (int i = 0; i < _days.Length; i++) { if (_days[i] == day) dayOfWeek = i; } return dayOfWeek; } } public string this[int dayOfWeek] { get { if (dayOfWeek > 0 && dayOfWeek <= _days.Length) return _days[dayOfWeek - 1]; else return string.Empty; } }}...

Click to save your changes. Ignore any errors in the CreateWeekdays() method for now, since we're making themodifications in two phases.

Page 75: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. class Weekdays{ private string[] _days = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" }; public int this[string day] { get { int dayOfWeek = -1; for (int i = 0; i < _days.Length; i++) { if (_days[i] == day) dayOfWeek = i; } return dayOfWeek; } } public string this[int dayOfWeek] { get { if (dayOfWeek > 0 && dayOfWeek <= _days.Length) return _days[dayOfWeek - 1]; else return string.Empty; } }}...

We've added what looks like two get accessors. Rather than naming the accessor after a variable name, they'renamed t his, and are fo llowed by array square brackets, and what appear to be parameters. This syntax allows us touse our instance object (weekDays) as an array. The accessor parameters within the brackets are indeed ourparameters, and the number o f parameters and their data type constitute the signature o f our indexer. In our code, wehave both a string and an int indexer. Let's revise the code to access the array information using indexers.

Modify Fo rm1.cs as shown:

Page 76: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void CreateWeekdays(){ // Create our Weekdays class, and initialize day of the week variables Weekdays weekDays = new Weekdays(); string weekDay = "Tuesday"; int dayOfWeek = 3; // Using methods of Weekdays class // Using indexers Console.WriteLine("Day of week (" + weekDay +") - " + weekDays.getDayOfWeek(weekDay)); Console.WriteLine("Day of week (" + weekDay + ") - " + weekDays[weekDay]); // Output: Day of week (Tuesday) - 3 Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays.getDay(dayOfWeek)); Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays[dayOfWeek]); // Output: Weekday (3) - Tuesday // Using array property of Weekdays class Console.WriteLine("Day of week (" + weekDay + ") - Not supported without doing an Array.Find"); // Output: Day of week (Tuesday) - Not supported without doing an Array.Find Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays.Days[dayOfWeek]); // Output: Weekday (3) - Wednesday Console.WriteLine("Set a final breakpoint on this line");}...

Click to save your changes and to run the program.

Page 77: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void CreateWeekdays(){ // Create our Weekdays class, and initialize day of the week variables Weekdays weekDays = new Weekdays(); string weekDay = "Tuesday"; int dayOfWeek = 3; // Using indexers Console.WriteLine("Day of week (" + weekDay + ") - " + weekDays[weekDay]); // Output: Day of week (Tuesday) - 2 Console.WriteLine("Weekday (" + dayOfWeek + ") - " + weekDays[dayOfWeek]); // Output: Weekday (3) - Tuesday Console.WriteLine("Set a final breakpoint on this line");}...

The weekDays[weekDay] and weekDays[dayOf Week] indexer syntax is cleaner more intuitive.

Sorting ArraysNow let's get back to sorting arrays. In order fo r an array to be sorted, it has to implement code that supports themeans by which that array should be sorted. Simple data types like strings and numbers already include arrays thathave the ability to sort them. Let's look at a couple o f examples.

Modify Fo rm1.cs as shown:

Page 78: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. public Form1(){ InitializeComponent(); CreateWeekdays(); SortArrays();}

private void SortArrays(){ // Sort int array int[] intArray = new int[] { 8, 10, 2, 6, 3 }; foreach (int i in intArray) Console.Write(i + " "); Console.Write("\n"); // Output: 8 10 2 6 3 Array.Sort(intArray); foreach (int i in intArray) Console.Write(i + " "); Console.Write("\n"); // Output: 2 3 6 8 10 // Sort string array string[] stringArray = new string[] { "Bob", "Joe", "Adam", "Tom", "Sarah", "Charlie" }; foreach (string s in stringArray) Console.Write(s + " "); Console.Write("\n"); // Output: Bob Joe Adam Tom Sarah Charlie Array.Sort(stringArray); foreach (string s in stringArray) Console.Write(s + " "); Console.Write("\n"); // Output: Adam Bob Charlie Joe Sarah Tom}...

Click to save your changes and to run the program.

Page 79: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public Form1(){ InitializeComponent(); CreateWeekdays(); SortArrays();}

private void SortArrays(){ // Sort int array int[] intArray = new int[] { 8, 10, 2, 6, 3 }; foreach (int i in intArray) Console.Write(i + " "); Console.Write("\n"); // Output: 8 10 2 6 3 Array.Sort(intArray); foreach (int i in intArray) Console.Write(i + " "); Console.Write("\n"); // Output: 2 3 6 8 10 // Sort string array string[] stringArray = new string[] { "Bob", "Joe", "Adam", "Tom", "Sarah", "Charlie" }; foreach (string s in stringArray) Console.Write(s + " "); Console.Write("\n"); // Output: Bob Joe Adam Tom Sarah Charlie Array.Sort(stringArray); foreach (string s in stringArray) Console.Write(s + " "); Console.Write("\n"); // Output: Adam Bob Charlie Joe Sarah Tom}...

With both int and string data types, we creat e an array, display t he co nt ent s o f t he array bef o re so rt ing, callthe Array.So rt method to sort each array, and then display t he so rt ed co nt ent .

For non-simple data types such as our own class, sorting an array is more complex, and requires implemention o fsorting by inheriting our class from the ICo mparable interface, and implementing a method the Co mpareT o methodof the interface. An interface is a more advanced programming technique that allows us to group related functionalityand dictate what must be done to use that interface. For array sorting using ICo mparable , the interface dictates that wehave to provide the Co mpareT o method in order to use the interface. We'll go over interfaces later in this course.

Note The fo llowing code is fo r discussion only. You will no t type this code into your test pro ject as it dependson a class that we haven't coded—yet.

Page 80: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

public class Tile : IComparable{ public int Order; // Order property public string SortValue; // Sortable value property for scrambling public Bitmap Face; // Tile image property public int CompareTo(object sortObject) { Tile tile = null; if (sortObject != null) tile = sortObject as Tile; if (tile != null) return this.SortValue.CompareTo(tile.SortValue); else throw new ArgumentException("Object is not a Tile"); }}

In this code from our T ile class, you see that we inherited our class from the ICo mparable interface. We thenimplemented the required Co mpareT o method, which takes a generic o bject datatype. We cast the so rt Objectparameter using as to our T ile class. If the t ile object is null, we throw (or raise) an error; o therwise, we access theinstance object So rt Value property, calling its Co mpareT o method to compare to the current object. If you'rescratching your head trying to figure out how this works, I should tell you that this code employs a technique wehavent't used yet called recursio n. Recursion is a technique wherein the code, typically a method, that calls itself. Inour example, Co mpareT o is the method name from the interface that we implemented, and it's also the samemethod we call in t his.So rt Value .Co mpareT o . Remember, t his always refers to the instance object variable. We'llsave recursion for later, but fo r now, get familiar with the pattern for creating an array-sorting implementation.

Sorting can be either stable o r unstable. With a stable sort, the order o f elements o f equal sort precedence ismaintained from the original unsorted order, whereas unstable sorting does not attempt to maintain that original o rder.Array sorting using the ICo mparable interface is an unstable sort.

Okay, let's move on to our tutorial!

Tiler - A Coding TutorialFor our coding tutorial, we'll create a Tiler game. The object o f this game is to unscramble an image that's broken upinto a grid and randomized. To play, a player clicks on one o f the tiles, which will highlight, and then clicks on a secondtile. Once the second tile is selected, the two tiles swap places. We'll use indexers to make accessing arrays o f tileseasier, and use the class array sort to determine when the images are in the correct order. Let's get started!

Tiler Game User Interface Prototypes

Below is an image o f our main user interface (UI) and menu with the initial game state. Each o f the images inthe grid is a PictureBox.

Main UI - Menu and Initial Game State

Page 81: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating the User Interface

Let's create our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to T iler and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to T iler. Also ,change the StartPosition property to Cent erScreen. Set the FormBorderStyle to FixedSingle to prevent the

player from resizing the Form. Click to save your changes.

Now add the contro ls.

Add a MenuStrip contro l by dragging the MenuStrip from the Too lbox and dropping it on the MainForm. TheMenuStrip docks by default to the top o f the Form beneath the Form Titlebar. Change the Name property o f theMenuStrip to mainMenuSt rip. Click on the MenuStrip, and in the text box that appears, click again, and type

&File . Click to save your changes.

Now add the Reset, horizontal separator, and Exit menu items under the File menu.

Click the text box that appears under the File menu, and enter &Reset . Beneath this menu item, click the textbox and enter a - (dash). Finally, click the next menu text box and enter E&xit . Click away from the menu toclose the editing o f the menu.

We'll keep the default names generated by Studio for each menu item.

Let's add the event handler C# code for the Reset and Exit menu options, and for the Form Shown event. OurTiler game won't support automatic resizing by the user, but will support automatic resizing o f the Form whenthe image is loaded, because we want to maintain the original image size.

Use the mouse to open the File menu, and double-click the Reset menu item to generate the Reset eventhandler fo r this menu item. Then, double-click the Exit menu item to generate the Exit event handler fo r thismenu item. Add the code shown to the Exit event handler:

Page 82: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Select the MainForm by changing to the Design view and clicking the Form title bar. Click on the Properties

Window Event icon, and double-click on the Shown event to generate the Shown event handler. Click tosave your changes.

The Tiler game will use a Panel contro l ( ). Using a Panel contro l will allow for more efficientdynamic contro l resizing. We set the Panel contro l to dock in the parent contro l, the MainForm. If theMainForm is resized because o f the image size, the Panel contro l will automatically resize as well. Let's addthe Panel contro l now.

Drag the Panel contro l from the Too lbox and drop it on the Form. Change the Name property o f the Panel tomainPanel. Click the black arrow at the top right o f the Panel contro l as shown in the image below to change

the Panel Docking property, clicking on the Do ck in Parent Co nt ainer option. Click to save yourchanges.

Adding the Code

Next, we'll load the game image that will be scrambled. Create a single PictureBox contro l and load the imageonto the PictureBox contro l.

Page 83: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Or click this link.

Right-click the image above, select Save Pict ure as... and save it in your network (\\bean\winusers) , in theMy Do cument s/Visual St udio 2010/Pro ject s/T iler/T iler/bin/debug fo lder:

Modify the MainFo rm.cs code as shown:

Page 84: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private Bitmap _image = null; // Game image

public MainForm(){ // Load the image _image = new Bitmap("WaterLilies-400x400.jpg"); InitializeComponent(); // Create a PictureBox and load the image into it PictureBox pictureBox = new PictureBox(); pictureBox.Image = _image; pictureBox.Dock = DockStyle.Fill; mainPanel.Controls.Add(pictureBox);}

private void MainForm_Shown(object sender, EventArgs e){ // Set the Form dimensions int formHeight = this.Height; int formWidth = this.Width; // Get the Panel inner dimensions int panelHeight = mainPanel.ClientRectangle.Height; int panelWidth = mainPanel.ClientRectangle.Width; // Get the image dimensions int imageHeight = _image.Height; int imageWidth = _image.Width; // Adjust Form dimensions to allow space for entire image this.Height = formHeight - panelHeight + imageHeight; this.Width = formWidth - panelWidth + imageWidth;}...

and to run the program. The Tiler program will run and resize itself to display the image, but the user(you) can't resize it afterwards.

Page 85: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private Bitmap _image = null; // Game image

public MainForm(){ // Load the image _image = new Bitmap("WaterLilies-400x400.jpg");

InitializeComponent();

// Create a PictureBox and load the image into it PictureBox pictureBox = new PictureBox(); pictureBox.Image = _image; pictureBox.Dock = DockStyle.Fill; mainPanel.Controls.Add(pictureBox);}

private void MainForm_Shown(object sender, EventArgs e){ // Set the Form dimensions int formHeight = this.Height; int formWidth = this.Width; // Get the Panel inner dimensions int panelHeight = mainPanel.ClientRectangle.Height; int panelWidth = mainPanel.ClientRectangle.Width; // Get the image dimensions int imageHeight = _image.Height; int imageWidth = _image.Width; // Adjust Form dimensions to allow space for entire image this.Height = formHeight - panelHeight + imageHeight; this.Width = formWidth - panelWidth + imageWidth;}...

We add a class variable _image to ho ld our game image. We add code to create a PictureBox on the Panel.This code is only temporary so we can can confirm we were able to load the image. We also add code to theForm MainFo rm_Sho wn Shown event handler that determines the size difference between the inside Paneldimensions and the outside Form dimensions, and uses these values and the dimensions o f the image toreset the Form dimensions to display the entire image.

Next, add the Tile class we saw earlier that represents an individual tile.

Right-click on the T iler so lution in the So lution Explorer panel, select Add | Class, enter T ile in the Nametextbox, and click Add. Modify T ile .cs as shown:

Page 86: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Tiler{ public class Tile : IComparable { public string SortValue; // Sortable value field for scrambling public Bitmap Face; // Tile image field public int CompareTo(object sortObject) { Tile tile = null; // Test and cast parameter to Tile if (sortObject != null) tile = sortObject as Tile; // Perform recursive sort or throw exception if (tile != null) return this.SortValue.CompareTo(tile.SortValue); else throw new ArgumentException("Object is not a Tile"); } } }}

Click to save your changes.

Let's discuss this code.

Page 87: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Tiler{ public class Tile : IComparable { public string SortValue; // Sortable value field for scrambling public Bitmap Face; // Tile image field public int CompareTo(object sortObject) { Tile tile = null; // Test and cast parameter to Tile if (sortObject != null) tile = sortObject as Tile; // Perform recursive sort or throw exception if (tile != null) return this.SortValue.CompareTo(tile.SortValue); else throw new ArgumentException("Object is not a Tile"); } } }}

Our Tile class stores a Bitmap that represents the face o f the tile, so we add a using statement to include theSyst em.Drawing namespace. Yes, we could've just specified the full name to the Bitmap class, but it's morecommon, and convenient to use the shortest possible names o f classes. Also, our Tile class inherits fromthe ICo mparable interface, implementing the Co mpareT o method o f the interface.

Be aware that we're using fields rather than properties.

Let's move on to our Tiles class; we'll use it to ho ld an array o f tiles, implement indexers for accessingindividual tile objects, and the methods we need to make our Tiler game work with tiles.

Right-click on the T iler so lution in the So lution Explorer, select Add | Class, enter T iles in the Name textbox,and click Add. Modify T iles.cs as shown:

Page 88: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Tiler{ public class Tiles { private int _numberOfTiles; private Tile[] _tilesScrambled; private Tile[] _tilesSorted; private int _numberOfRows; private int _numberOfColumns; private Bitmap _image; public Bitmap Image { get { return _image; } } // Constructor public Tiles(int numberOfRows, int numberOfColumns, Bitmap image) { // Set class variables _numberOfRows = numberOfRows; _numberOfColumns = numberOfColumns; _numberOfTiles = numberOfRows * numberOfColumns; _image = image; // Create tile arrays _tilesScrambled = new Tile[_numberOfTiles]; _tilesSorted = new Tile[_numberOfTiles]; // Reset the tiles reset(); } // Indexer public Tile this[int index] { get { return _tilesScrambled[index]; } set { _tilesScrambled[index] = value; } } public void reset() { // Clear out all old tiles Array.Clear(_tilesScrambled, 0, _tilesScrambled.Length); Array.Clear(_tilesSorted, 0, _tilesSorted.Length); // Calculate image chunks int heightChunk = _image.Height / _numberOfRows; int widthChunk = _image.Width / _numberOfColumns; // Generate new tiles in sorted order, adding a GUID for later scrambling int imageCount = 0; for (int row = 0; row < _numberOfRows; row++)

Page 89: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

{ for (int col = 0; col < _numberOfColumns; col++) { // Determine image chunk dimensions Rectangle chunkRectangle = new Rectangle(widthChunk * col, heightChunk * row, widthChunk, heightChunk); // Create new Tile _tilesSorted[imageCount++] = new Tile() { Face = _image.Clone(chunkRectangle, _image.PixelFormat), SortValue = Guid.NewGuid().ToString() }; } } // Clone our sorted array _tilesScrambled = (Tile[])_tilesSorted.Clone(); // Sort to unscramble Array.Sort(_tilesScrambled); } }}

Click to save your changes.

Page 90: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Tiler{ public class Tiles { private int _numberOfTiles; private Tile[] _tilesScrambled; private Tile[] _tilesSorted; private int _numberOfRows; private int _numberOfColumns; private Bitmap _image; public Bitmap Image { get { return _image; } } // Constructor public Tiles(int numberOfRows, int numberOfColumns, Bitmap image) { // Set class variables _numberOfRows = numberOfRows; _numberOfColumns = numberOfColumns; _numberOfTiles = numberOfRows * numberOfColumns; _image = image; // Create tile arrays _tilesScrambled = new Tile[_numberOfTiles]; _tilesSorted = new Tile[_numberOfTiles]; // Reset the tiles reset(); } // Indexer public Tile this[int index] { get { return _tilesScrambled[index]; } set { _tilesScrambled[index] = value; } } public void reset() { // Clear out all old tiles Array.Clear(_tilesScrambled, 0, _tilesScrambled.Length); Array.Clear(_tilesSorted, 0, _tilesSorted.Length); // Calculate image chunks int heightChunk = _image.Height / _numberOfRows; int widthChunk = _image.Width / _numberOfColumns; // Generate new tiles in sorted order, adding a GUID for later scrambling int imageCount = 0; for (int row = 0; row < _numberOfRows; row++)

Page 91: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

{ for (int col = 0; col < _numberOfColumns; col++) { // Determine image chunk dimensions Rectangle chunkRectangle = new Rectangle(widthChunk * col, heightChunk * row, widthChunk, heightChunk); // Create new Tile _tilesSorted[imageCount++] = new Tile() { Face = _image.Clone(chunkRectangle, _image.PixelFormat), SortValue = Guid.NewGuid().ToString() }; } } // Clone our sorted array _tilesScrambled = (Tile[])_tilesSorted.Clone(); // Sort to unscramble Array.Sort(_tilesScrambled); } }}

Our Tiler class has quite a few components. The _t ilesScrambled array will ho ld individual T ile objects inan unsorted order. The _t ilesSo rt ed array is sorted using Array.So rt to ho ld sorted T ile objects. Todetermine whether the tile pieces are in the correct order, we'll add a method to this class that compares thetwo arrays, and when they are identical, our program knows that the tile pieces are in the correct order.

We do not have a default constructor (a constructor with no parameters), instead, we use the grid dimensionsand the grid image as parameters. We use these parameters to initialize class variables and create our imagearrays. Then we call the reset method to clear the arrays (except fo r the first time we call reset because wedon't need to clear then), slice the image into grid chunks, then add each image chunk as a T ile to the_t ilesSo rt ed array. Even though the images are being used in a two-dimensional PictureBox array, we'llstore them in a one-dimensional array, using imageCo unt to track the size (count) o f the _t ilesSo rt edarray. The reset method also uses the array Clo ne method to make a copy o f the _t ilesSo rt ed, then callisthe Array.So rt to create a scrambled version o f our tiles in the _t ilesScrambled array.

It may seem strange to call Array.So rt to unsort our tiles, and, well, yes, it is strange, but effective! We couldhave used the Random class like we did earlier, but I wanted you to try out the Guid.NewGuid method togenerate a globally unique identifier. These identifiers are random characters, and using them for the T ileSo rt Value sort property works well.

Now let's switch back to our Form and create our grid tiles. We'll need to add a class variable for our Tilesclass, create an instance o f our Tiles class, add the two-dimensional PictureBox array to ho ld the images,add a one-dimensional array to ho ld the loaded images, and create and call a method to create the grid. We'llalso need to remove the temporary code we used to display the image in a PictureBox. After we've added anddiscussed these changes, we'll update the Shown Form event handler to display the grid.

Modify the MainForm.cs code as shown:

Page 92: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private Tiles _myTiles = null; // Tiles objectprivate PictureBox[,] _imageGrid = null; // Picturebox two-dimensional arrayprivate int _numberOfRows = 4; // Number of rows in the gameprivate int _numberOfColumns = 4; // Number of columns in the game

public MainForm(){ // Load the image _image = new Bitmap("WaterLilies-400x400.jpg"); // Create tiles _myTiles = new Tiles(_numberOfRows, _numberOfColumns, _image); // Create image grid createImageGrid(); InitializeComponent(); // Create a PictureBox and load the image into it PictureBox pictureBox = new PictureBox(); pictureBox.Image = _image; pictureBox.Dock = DockStyle.Fill; mainPanel.Controls.Add(pictureBox);}

private void createImageGrid(){ // Allocate a two-dimensional array (grid) for our grid _imageGrid = new PictureBox[_numberOfRows, _numberOfColumns]; // Create each PictureBox control in our array int tileCount = 0; for (int row = 0; row < _numberOfRows; row++) { for (int col = 0; col < _numberOfColumns; col++) { // Create and assign new PictureBox PictureBox pictureBox = new PictureBox() { Image = _myTiles[tileCount].Face, BackColor = Color.Black, BorderStyle = BorderStyle.FixedSingle, Cursor = Cursors.Hand, Tag = tileCount }; _imageGrid[row, col] = pictureBox; // Increment tile counter tileCount++; } }}...

Click to save your changes.

Page 93: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private Tiles _myTiles = null; // Tiles objectprivate PictureBox[,] _imageGrid = null; // Picturebox two-dimensional array public MainForm(){ // Load the image _image = new Bitmap("WaterLilies-400x400.jpg"); // Create tiles _myTiles = new Tiles(_numberOfRows, _numberOfColumns, _image); // Create image grid createImageGrid(); InitializeComponent();}

private void createImageGrid(){ // Allocate a two-dimensional array (grid) for our grid _imageGrid = new PictureBox[_numberOfRows, _numberOfColumns]; // Create each PictureBox control in our array int tileCount = 0; for (int row = 0; row < _numberOfRows; row++) { for (int col = 0; col < _numberOfColumns; col++) { // Create and assign new PictureBox PictureBox pictureBox = new PictureBox() { Image = _myTiles[tileCount].Face, BackColor = Color.Black, BorderStyle = BorderStyle.FixedSingle, Cursor = Cursors.Hand, Tag = tileCount }; _imageGrid[row, col] = pictureBox; // Increment tile counter tileCount++; } }}...

We added and created our _myT iles T iles object, added the two-dimensional Pict ureBo x array_imageGrid, then used the creat eImageGrid method to create a PictureBox object, set some of itsproperties, including the T ag property to ho ld t ileCo unt , which we use as our array index into the T ilesobject _myT iles, which ho lds all o f the scrambled tiles (phew!). We use our index method in our usage o f_myT iles, where we access the scrambled tiles array using [t ileCo unt ]. The indexer hides the arrayembedded in the T iles class, but makes it convenient fo r us to access the individual tile, setting the tile Face(image) to the image o f the dynamic PictureBox, and then assigning the dynamic PictureBox to the PictureBoxwe created as part o f the image grid.

We've set the Bo rderSt yle to FixedSingle to prevent the user from resizing our game.

Now let's update the Shown Form event so we can see our image grid.

Modify the MainForm.cs code as shown:

Page 94: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void MainForm_Shown(object sender, EventArgs e){ // Set the Form dimensions int formHeight = this.Height; int formWidth = this.Width; // Get the Panel inner dimensions int panelHeight = mainPanel.ClientRectangle.Height; int panelWidth = mainPanel.ClientRectangle.Width; // Get the image dimensions int imageHeight = _image.Height; int imageWidth = _image.Width; // Adjust Form dimensions to allow space for entire image this.Height = formHeight - panelHeight + imageHeight; this.Width = formWidth - panelWidth + imageWidth; // Get the height and width for each PictureBox using the inside height and width of the Panel control int pictureBoxWidth = mainPanel.ClientRectangle.Width / _numberOfColumns; int pictureBoxHeight = mainPanel.ClientRectangle.Height / _numberOfRows; // Loop through and set the location and dimensions of each PictureBox control for (int row = 0; row < _numberOfRows; row++) { for (int col = 0; col < _numberOfColumns; col++) _imageGrid[row, col].SetBounds(pictureBoxWidth * col, pictureBoxHeight * row, pictureBoxWidth, pictureBoxHeight); } // Add the Picture controls to the Panel Controls collection if (mainPanel.Controls.Count == 0) { for (int row = 0; row < _numberOfRows; row++) { for (int col = 0; col < _numberOfColumns; col++) mainPanel.Controls.Add(_imageGrid[row, col]); } }}...

and to run the program. You can now see the scrambled images!

The code we added is almost identical to the code we've added in previous tutorials fo r dimensioning thecontro ls (a PictureBox in this tutorial rather than a Button) and adding the contro ls to the Contro ls co llection o fwhatever contro l is needed to display them (a Panel fo r this game).

So, what's left fo r us to do for our game? We still need to enable the selection and swapping o f images, checkto see if the image tiles are in the correct order, and enable game reset. Let's add image selection andswapping next. We'll need to add a swap method to our Tiles class first. Since we're editing the Tiles classanyway, we might as well add the method to determine if bo th tile arrays are identical.

Modify the Tiles.cs code as shown:

Page 95: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public void swap(int firstIndex, int secondIndex){ if (firstIndex != secondIndex && firstIndex >= 0 && firstIndex < _tilesScrambled.Length && secondIndex >= 0 && secondIndex < _tilesScrambled.Length) { Tile holdTile = _tilesScrambled[secondIndex]; _tilesScrambled[secondIndex] = _tilesScrambled[firstIndex]; _tilesScrambled[firstIndex] = holdTile; }} public bool isSolved(){ bool result = true; // Compare sorted and scrambled, and if they match, solved for (int i = 0; i < _numberOfColumns; i++) { if (_tilesScrambled[i].SortValue != _tilesSorted[i].SortValue) { result = false; break; } }

return result;}...

Click to save your changes. Let's discuss this code.

Page 96: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public void swap(int firstIndex, int secondIndex){ if (firstIndex != secondIndex && firstIndex >= 0 && firstIndex < _tilesScrambled.Length && secondIndex >= 0 && secondIndex < _tilesScrambled.Length) { Tile holdTile = _tilesScrambled[secondIndex]; _tilesScrambled[secondIndex] = _tilesScrambled[firstIndex]; _tilesScrambled[firstIndex] = holdTile; }}

public bool isSolved(){ bool result = true; // Compare sorted and scrambled, and if they match, solved for (int i = 0; i < _numberOfColumns; i++) { if (_tilesScrambled[i].SortValue != _tilesSorted[i].SortValue) { result = false; break; } } return result;}...

The swap method takes two parameters: f irst Index as the first array index, and seco ndIndex as thesecond array index. We make sure our code executes some boundary checks as good programmingpractice, then proceed to swap the tiles, using an intermediate ho ldT ile during the swap. The isSo lvedmethod returns t rue o r f alse depending on whether all elements in both o f the arrays are equal. What do wemean by equal in this context? All o f the array elements we reference are T ile variables, which means we'reverifying that each corresponding element in each array is referencing the same T ile object in memory.

Let's head back to our Form to add the selection code by adding a MouseUp event handler to eachPictureBox, as well as the test to see if the grid puzzle is so lved. We'll add the event handler code first.Remember, you can add the code by typing it in exactly as indicated, or you can use the event handler wizardthat appears, and press the T ab key when prompted to automatically generate the code.

Modify the MainForm.cs code as shown:

Page 97: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void createImageGrid(){ // Allocate a two-dimensional array (grid) for our grid _imageGrid = new PictureBox[_numberOfRows, _numberOfColumns]; // Create each PictureBox control in our array int tileCount = 0; for (int row = 0; row < _numberOfRows; row++) { for (int col = 0; col < _numberOfColumns; col++) { // Create and assign new PictureBox PictureBox pictureBox = new PictureBox() { Image = _myTiles[tileCount].Face, BackColor = Color.Black, BorderStyle = BorderStyle.FixedSingle, Cursor = Cursors.Hand, Tag = tileCount }; _imageGrid[row, col] = pictureBox; // Increment tile counter tileCount++; // Add single event handler for all PictureBox MouseUp events _imageGrid[row, col].MouseUp += new MouseEventHandler(imageMouseUpHandler); } }}

void imageMouseUpHandler(object sender, MouseEventArgs e){ throw new NotImplementedException();}...

We added an event handler, now let's add the Shown event method code, and a necessary class variable.

Click to save your changes. Modify the MainForm.cs code as shown:

Page 98: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private PictureBox _firstPictureBox = null; // First selected PictureBox...void imageMouseUpHandler(object sender, MouseEventArgs e){ // Get the PictureBox control we're responding to PictureBox pictureBox = sender as PictureBox; if (_firstPictureBox == null) { // First pick _firstPictureBox = pictureBox; // Draw border to indicate selected tile pictureBox.CreateGraphics().DrawRectangle( new Pen(Color.Red, 3.0F), 0, 0, pictureBox.ClientRectangle.Width - 1, pictureBox.ClientRectangle.Height - 1); } else { // Get array indices int firstIndex = (int)_firstPictureBox.Tag; int currentIndex = (int)pictureBox.Tag; // Swap positions, reset images _myTiles.swap(firstIndex, currentIndex); _firstPictureBox.Image = _myTiles[firstIndex].Face; pictureBox.Image = _myTiles[currentIndex].Face; _firstPictureBox = null; if (_myTiles.isSolved()) MessageBox.Show("Solved!", this.Text); }}...

and to run the program. You can now play the complete game, except fo r resetting.

Let's discuss this code.

Page 99: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private PictureBox _firstPictureBox = null; // First selected PictureBox...void imageMouseUpHandler(object sender, MouseEventArgs e){ // Get the PictureBox control we're responding to PictureBox pictureBox = sender as PictureBox; if (_firstPictureBox == null) { // First pick _firstPictureBox = pictureBox; // Draw border to indicate selected tile pictureBox.CreateGraphics().DrawRectangle( new Pen(Color.Red, 3.0F), 0, 0, pictureBox.ClientRectangle.Width - 1, pictureBox.ClientRectangle.Height - 1); } else { // Get array indices int firstIndex = (int)_firstPictureBox.Tag; int currentIndex = (int)pictureBox.Tag; // Swap positions, reset images _myTiles.swap(firstIndex, currentIndex); _firstPictureBox.Image = _myTiles[firstIndex].Face; pictureBox.Image = _myTiles[currentIndex].Face; _firstPictureBox = null; if (_myTiles.isSolved()) MessageBox.Show("Solved!", this.Text); }}...

In the imageMo useUpHandler, we determine which Picturebox contro l was selected, and set pict ureBo x.Then we determine whether the selected PictureBox is the first selection or the second selection by testing tofind out whether _f irst Pict ureBo x (the class variable we added) is null. If it is null, then we know we have afirst selection, so we save the selected PictureBox for later and highlight the selected PictureBox by drawing ared square around the edge o f the image. If we have a second selection, we extract the array indices from thePictureBox T ag property, and call our swap method o f the Tiles class. Once swapped in the array, we need toswap the images displayed on each PictureBox; to do that we grab the Face property using the indexersyntax, and reset _f irst Pict ureBo x to null to indicate we have no current selection. Finally, we call theisSo lved method o f the Tiles object to determine whether all o f the tiles are in their correct position, anddisplay a message box to indicate the puzzle is so lved!

We're almost done. We just need to add code to reset our game. Let's do that now:

Page 100: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void resetToolStripMenuItem_Click(object sender, EventArgs e){ // Reset tiles _myTiles.reset(); // Display tiles int tileCount = 0; for (int row = 0; row < _numberOfRows; row++) { for (int col = 0; col < _numberOfColumns; col++) { _imageGrid[row, col].Image = _myTiles[tileCount++].Face; } }}...

and to run the program. Now, you can play the game and reset the game! Game complete!

Let's discuss this code.

OBSERVE:

.

.

.private void resetToolStripMenuItem_Click(object sender, EventArgs e){ // Reset tiles _myTiles.reset(); // Display tiles int tileCount = 0; for (int row = 0; row < _numberOfRows; row++) { for (int col = 0; col < _numberOfColumns; col++) { _imageGrid[row, col].Image = _myTiles[tileCount++].Face; } }}...

Resetting the game invo lves resetting the tiles, which is done by calling reset () , and displaying the newscrambled images. We use the indexer syntax _myT iles[t ileCo unt ++] , setting the PictureBox Imageproperty to the tile Face property.

That's it! Another great game! Make sure you work on the homework and turn in any pro jects before youmove on to the next lesson...

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 101: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Object Relationships and the Object Class

This lesson covers the concept o f inheritance. We'll discuss the difference between specialization and generalization, revisitpo lymorphism, and contrast inheritance with containment. We'll also devote time to discussing the object class. Oh, and we'llcreate a fun shape drawing program along the way!

This lesson includes the fo llowing topics:

InheritanceThe Object Base ClassDrawShapes - A Coding Tutorial

InheritanceWhat exactly is inheritance? Inheritance is the ability o f an object to gain characteristics through a relationship withanother object. In C#, we use classes as blueprints fo r objects, so inheritance means relating one class to another.Let's consider an example.

Shape Inherit ance Hierarchy

This image portrays a relationship, or hierarchy, o f an ellipse and a rectangle to a generic object shape. Here are afew characteristics that a generic shape object might have in common with an ellipse and a rectangle:

SizeLocationColorOutline or so lid

When we consider the relationship between a shape and an ellipse and rectangle, we can say that an ellipse "is a"shape, and a rectangle "is a" shape. What exactly does this re lat io nship hierarchy mean? A shape containsgeneral properties, whereas a rectangle or ellipse is a more specialized version o f a shape. As we move down theinheritance hierarchy, objects become more specialized. As we move up the inheritance hierarchy, objects becomemore generalized. Let's try out some code to remind us o f how we declare inheritance in C# using the co lonoperator after a class declaration.

For this lesson, create a test pro ject using File | New | Pro ject , and change the Name of the pro ject to Inherit ance .

Click to save your changes.

Modify the Fo rm1.cs class code in the lesson test pro ject as shown:

Page 102: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); Ellipse ellipse = new Ellipse(); Rectangle rectangle = new Rectangle();}...public class Shape{ public Shape() { Console.WriteLine("Shape constructor"); }} public class Ellipse : Shape{ public Ellipse() { Console.WriteLine("Ellipse constructor"); }} public class Rectangle : Shape{ public Rectangle() { Console.WriteLine("Rectangle constructor"); }}...

Click and to save and run the program.

Note We added three classes to Form1.cs. These classes are added after the Form1 class, but within thenamespace.

Page 103: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public Form1(){ InitializeComponent(); Ellipse ellipse = new Ellipse(); Rectangle rectangle = new Rectangle();} public class Shape{ public Shape() { Console.WriteLine("Shape constructor"); }}

public class Ellipse : Shape{ public Ellipse() { Console.WriteLine("Ellipse constructor"); }}

public class Rectangle : Shape{ public Rectangle() { Console.WriteLine("Rectangle constructor"); }}...

First, we declare the Shape class, then as we declare both the Ellipse and Rect angle classes, we add the co lonoperator (:) fo llowed by the Shape class name to indicate that the Rect angle and Ellipse classes inherit from theShape class. When we run the program, we can see that the Shape class code is executed even though we don'texplicitly call it.

In this and the next few lessons, we'll use inheritance and o ther C# features that allow you to create and use classesmore effectively.

As you examine the inheritance hierarchy, you can say that the Shape class is the parent o f both the Ellipse class andRectangle class, and that the Ellipse and Rectangle classes are children o f the Shape class. These terms are alsocommonly used to refer to this parent and child relationship:

Child: child class, subclass, derived classParent : parent class, base class, superclass

When you inherit from a parent or "base" class, you gain the ability to use all o f the properties and methods o f theparent class within the child class. Creating base classes a valuable skill that helps you to avo id creating duplicatecode; however, when you create a base class to take advantage o f avo iding code duplication, make sure the parent-child relationship does express an "is a" relationship.

We've seen the "has a" relationship many times before in our code. In a "has a" relationship, one object containsanother object. For example, a Shape List class might contain an array o f Shape objects. It would be incorrect to saythat a Shape List object "is a" Shape object. Rather, the Shape List object "has a" or contains an array o f Shapeobjects; "has a" relationships are o ften referred to as containment.

In programming, polymorphism describes the ability to use a uniform set o f code (methods, properties, and such) towork with different data types. By creating class objects, we are creating new data types, and by applying inheritance

Page 104: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

relationships between classes, we are enabling the ability to use a uniform set o f code to work with different data types.We'll illustrate this method here in a bit.

Before we continue with our example, let's go over the override feature o f the C# language. Override will be useful asyou begin specialization. By subclassing a child class, we are indicating that it will have access to the methods andcharacteristics o f the parent class, but the child class may also possess additional methods and properties allowing itto "specialize." Often, the parent class contains a property or method that in general applies to all o f the children, buteach child might have a specialized version. To create such a relationship in C#, we create a property o f a methodusing the virt ual keyword, and in the child class, we use the o verride keyword to indicate that we're overriding theproperty or method o f the parent class. This combination o f virtual and override is an instance o f po lymorphism usingclasses. We'll use it in an example shortly.

The Object Base ClassBefore we get started with our example though, let's talk about the Object base class in .NET. All classes in .NET arederived from Object. Some classes are direct children o f Object, while o thers may have intermediate classes betweenthemselves and Object. Any classes we create also derive from Object, so all classes have access to the methods o fthe Object class. Some of the methods o f the Object class are declared as virtual, which means we can override themin our subclasses. In several o f o f our exercises, we've declared a ToString() class with override to override the baseObject method o f the same name. Here are a few of the Object class methods:

T o St ring (virtual): Returns a string representation o f an object instance.Equals (virtual): Determines if one instance o f class is equal to another instance o f a class. For a value datatype, that means the stored data is exactly the same at the bit level. For reference data types, that means theobject instances refer to the same exact object in memory.Get T ype : Returns the system data type o f an instance object.MemberwiseClo ne : Creates a shallow copy o f an instance object.Finalize (virtual): Facilitates cleanup o f unmanaged resources before being reclaimed by the garbageco llector.

Some of these methods are virtual, so you can create your own override methods. We won't be using any methods o fthe Object class in this lesson, but I wanted to introduce them becuse you will see them in later lessons.

Let's move on to the exercise!

DrawShapes - A Coding TutorialWe're go ing to create a fun program for drawing shapes. Actually, we're only go ing to implement the ellipse drawing,and you'll have to implement the Rectangle drawing in the pro ject. Right now, we'll create shape and ellipse classes,and apply po lymorphism using virt ual and o verride . We'll also learn a few things about drawing objects as well.Let's get started!

Draw Shapes User Interface Prototypes

Below are the Draw Shapes program interface elements, including the UI in the Design Editor, the runningprogram UI, and the co lor dialog UI:

Design Edit o r UI

Page 105: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Runt ime UI

Co lo r Dialo g UI

Page 106: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating the User Interface

Let's start creating our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to DrawShapes and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to DrawShapes. Also , change the StartPosition property to Cent erScreen. Set the FormBorderStyle to

FixedSingle to prevent Form resizing. Click to save your changes.

Now let's add the contro ls we'll need.

Add a MenuStrip contro l by dragging the MenuStrip from the Too lbox and dropping it on the MainForm. Bydefault, the MenuStrip will dock to the top o f the Form beneath the Form Titlebar. Change the Name property o fthe MenuStrip to mainMenuSt rip. Click on the MenuStrip, and in the text box that appears, click again, andenter &File . Click the text box that appears under the File menu, and enter &Reset . Beneath this menu item,click the text box and enter a - (dash). Finally, click the next menu text box, and enter E&xit . Click away from

the menu to stop editing the menu. Click to save your changes.

We'll keep the default names generated by Studio for each menu item.

Let's add the event handler C# code for the Reset and Exit menu options, and for the Form Load event.

Use the mouse to open the File menu, and double-click the Reset menu item to generate the Reset eventhandler fo r this menu item. Go back to the Design view and double-click the Exit menu item to generate theExit event handler fo r this menu item. Add the code shown below to the Exit event handler:

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Select the MainForm by clicking on the Form Titlebar. Click on the Properties Window Event s icon, and

Page 107: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

double-click the Lo ad event to generate the Load event handler. Click to save your changes.

In our program, we'll add a SplitContainer contro l, with horizontal o rientation. The top panel will contain ourdrawing contro ls, and the bottom panel will contain a PictureBox contro l as our drawing surface.

Drag a SplitContainer contro l onto the Form. Change the SplitContainer orientation by clicking on the smallblack arrow at the top right o f the contro l, and selecting Ho rizo nt al Split t er Orient at io n. Also , selectDo ck in Parent Co nt ainer if the contro l is not already docked to the Form. Change the Name property to

mainSplit Co nt ainer. Change the BorderStyle property to FixedSingle . Click to save your changes.

Drag a ComboBox contro l onto Panel1 (top). Change the Name property to ShapeComboBox, andDropDownStyle property to Dro pDo wnList .

Drag a PictureBox contro l onto Panel1, resize to match the size in the UI pro to type, and change the Nameproperty to shapeCo lo rPict ureBo x.

Drag a Button contro l onto Panel1, resize to match the size in the UI pro to type, change the Name property toco lo rBut t o n and the Text property to Co lo r.

Drag a CheckBox contro l onto Panel1, change the Name property to f illedCheckBo x, and the Text propertyto Filled.

Select the co lorButton Button contro l, click the Properties Window Event s icon, and double-click on the Click

event to generate the event handler. Click to save your changes.

Back in the Design view, drag a PictureBox contro l onto Panel2 (bottom). Click on the small black arrow at thetop right o f the contro l and select Do ck in Parent Co nt ainer. Change the Name property to

canvasPict ureBo x. Click to save your changes.

Adding the Code

Now that the UI is complete, let's add the code. First, we'll create our Shape class.

Right-click on the So lution Explorer, select Add | Class, enter Shape in the Name textbox, and click Add.Modify the Shape.cs class code as shown below:

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace DrawShapes{ public class Shape { public Color ShapeColor; public bool Filled; public int X; public int Y; public int Width; public int Height; public virtual void draw(Graphics graphics) { // Override this method in the child class } }}

Click to save your changes.

Page 108: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace DrawShapes{ public class Shape { public Color ShapeColor; public bool Filled; public int X; public int Y; public int Width; public int Height; public virtual void draw(Graphics graphics) { // Override this method in the child class } }}

For the Shape class, we added a using st at ement to allow us to use the Co lo r structure and Graphicsclass. We also made all o f our class variables public fields rather than accessor properties, in order to makethe class definition shorter. We added a draw method that takes a Graphics parameter. We made the drawmethod virt ual to allow it be overridden in any child classes.

Why do we want subclasses o f Shape to override the draw method? Athough we've discussedspecialization properties, specialization can also be methods. How we draw an ellipse differs from how wedraw a rectangle, so while all o f the Shape properties will be shared, the draw method will be overridden.Let's create our ellipse class next.

Right-click on the So lution Explorer, select Add | Class, enter Ellipse in the Name textbox, and click Add.Modify the Ellipse.cs class code as shown below:

Page 109: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace DrawShapes{ public class Ellipse : Shape { public Ellipse(Rectangle rectangle, Color shapeColor, bool filled) { base.X = rectangle.X; base.Y = rectangle.Y; base.Width = rectangle.Width; base.Height = rectangle.Height; base.ShapeColor = shapeColor; base.Filled = filled; } public override void draw(Graphics graphics) { if (base.Filled) graphics.FillEllipse(new SolidBrush(base.ShapeColor), base.X, base.Y, base.Width, base.Height); else graphics.DrawEllipse(new Pen(base.ShapeColor), base.X, base.Y, base.Width, base.Height); } }}

Click to save your changes.

Page 110: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace DrawShapes{ public class Ellipse : Shape { public Ellipse(Rectangle rectangle, Color shapeColor, bool filled) { base.X = rectangle.X; base.Y = rectangle.Y; base.Width = rectangle.Width; base.Height = rectangle.Height; base.ShapeColor = shapeColor; base.Filled = filled; } public override void draw(Graphics graphics) { if (base.Filled) graphics.FillEllipse(new SolidBrush(base.ShapeColor), base.X, base.Y, base.Width, base.Height); else graphics.DrawEllipse(new Pen(base.ShapeColor), base.X, base.Y, base.Width, base.Height); } }}

Once again we added the necessary using statement. We also added the inheritance o f Ellipse from Shapeusing the co lon (:) inheritance operator. We created a constructor that takes three parameters, rect angle ,shapeCo lo r, and f illed, and we use these parameters to set properties in the parent (base) class Shape ,using the base operator. We also added the draw method using the o verride keyword, indicating that theversion o f draw in the child Ellipse class is overriding the virtual version in the Shape class. In the childversion, we test to determine whether the ellipse is to be filled or an outline, and call the appropriateFillEllipse o r DrawEllipse method accordingly.

Because we created a virtual and specialized version o f the draw method, we'll be able to create simple code.The Ellipse class "is a" Shape class. Let's add a class that "has a" Shape class, or more specifically, anarray o f them.

Right-click on the So lution Explorer, select Add | Class, enter ShapesList in the Name textbox, and clickAdd. Modify the ShapesList .cs class code as shown:

Page 111: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace DrawShapes{ public class ShapesList { private Shape[] _shapes = null; // One-dimensional array of shape objects // Indexer public Shape this[int index] { get { return _shapes != null ? _shapes[index] : null; } } // Count of shapes public int Length { get { return _shapes != null ? _shapes.Length : 0; } } // Add a shape public int Add(Shape shape) { // Create or resize our shapes array if (_shapes == null) _shapes = new Shape[1]; else Array.Resize(ref _shapes, _shapes.Length + 1); _shapes[_shapes.Length - 1] = shape; return _shapes.Length; } // Clear all shapes public void Clear() { if (_shapes != null) { Array.Clear(_shapes, 0, _shapes.Length); _shapes = null; } } }}

Click to save your changes.

Page 112: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace DrawShapes{ public class ShapesList { private Shape[] _shapes = null; // One-dimensional array of shape objects // Indexer public Shape this[int index] { get { return _shapes != null ? _shapes[index] : null; } } // Count of shapes public int Length { get { return _shapes != null ? _shapes.Length : 0; } } // Add a shape public int Add(Shape shape) { // Create or resize our shapes array if (_shapes == null) _shapes = new Shape[1]; else Array.Resize(ref _shapes, _shapes.Length + 1); _shapes[_shapes.Length - 1] = shape; return _shapes.Length; } // Clear all shapes public void Clear() { if (_shapes != null) { Array.Clear(_shapes, 0, _shapes.Length); _shapes = null; } } }}

We added a one-dimensional Shape array _shapes, creating a "has a" or containment relationship. Weadded a read-only indexer using t his that uses a compact display and the ternary operator (? :) to facilitateaccess to Shape objects in the _shapes array. We added a read-only accessor Lengt h that returns thenumber o f array elements. We also added two methods: Add to add a new Shape to our array, and Clear toremove all o f the Shape objects from our array.

So, what's the benefit o f inheritance and po lymorphism? The benefit lies in the fact that we did not have tospecify the Ellipse class anywhere in our code. Because an Ellipse is a Shape, we can store an Ellipse objectin our _shapes array.

Let's discuss how our code is go ing to work.

We'll draw our images on a PictureBox. We could draw them directly on the Panel contro l, but when you'redrawing lo ts o f images, do ing it on the Panel contro l could cause you to run into a flicker problem. We couldovercome that flicker problem by impementing a technique called "double buffering," but we won't need to doany o f that. The PictureBox contro l already includes double buffering.

Page 113: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

The Draw Shapes program will wait fo r you to click and ho ld the mouse down to indicate a starting drawlocation. Then, as you drag your mouse, the shape is drawn and stretched. This drawing display is known asrubber-banding, because it looks and acts like a rubber band—you pull one end, and it stretches. Once yourlet the mouse up, the image is added to the ShapesList.

To create the rubber-banding effect, we'll use and constantly change the dimensions o f an Ellipse object. Asthe mouse moves, the dimensions must be changed to reflect the new size. With each mouse move, weupdate the Ellipse dimensions (this includes location and size), and then raise the Paint event using theInvalidate() method. In the Paint event, we'll first clear the PictureBox, then draw all o f the shapes stored in ourShapesList, then draw the Ellipse object we're working with. Once the mouse is released, the Ellipse is addedto our ShapesList.

Let's get started by adding the event handlers we need. Because we're drawing on the PictureBox in Panel2,we'll need to generate events for this contro l.

Select the canvasPictureBox, select the Properties Window Event s Icon, and double-click each o f thefo llowing events to generate event handlers: Mo useDo wn, Mo useMo ve , Mo useUp, and Paint .

Next, let's add a few class variables we need, and add code to the Form Load event.

Modify the code in MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.private ShapesList _shapeList = new ShapesList(); // List of shapesprivate Shape _draggingCurrentShape; // Current shapeprivate bool _dragging = false; // Are we rubber bandingprivate int _startingX = 0; // Starting X locationprivate int _startingY = 0; // Starting Y locationprivate Color _shapeColor = Color.Red; // Shape color

private void MainForm_Load(object sender, EventArgs e){ // Populate shape ComboBox and select first entry shapeComboBox.Items.Add("Ellipse"); shapeComboBox.Items.Add("Rectangle"); shapeComboBox.SelectedIndex = 0; // Set the default color shapeColorPictureBox.BackColor = _shapeColor;}...

Click to save your changes.

Each o f the class variables comments help you to understand their purpose. The Form Load event addsentries to our ComboBox, selects the first entry, and updates the PictureBox that displays our drawing co lor.Let's add a bit more code to canvasPictureBox so we can draw shapes!

Modify the code in MainFo rm.cs as shown:

Page 114: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void canvasPictureBox_MouseDown(object sender, MouseEventArgs e){ if (!_dragging) { // Save starting point, convert to rectangle _startingX = e.X; _startingY = e.Y; Rectangle rectangle = new Rectangle(e.X, e.Y, 0, 0); // Create current Shape _draggingCurrentShape = new Ellipse(rectangle, _shapeColor, filledCheckBox.Checked); // Indicate we're drawing by "rubber-banding" _dragging = true; }}

private void canvasPictureBox_MouseMove(object sender, MouseEventArgs e){ if (_dragging) { // Determine and set current shape dimension based on current position _draggingCurrentShape.Width = e.X - _startingX; _draggingCurrentShape.Height = e.Y - _startingY; // Invalidate to raise Paint event canvasPictureBox.Invalidate(); }}

private void canvasPictureBox_MouseUp(object sender, MouseEventArgs e){ // Add to shape list if (_dragging) { // Add new shape and turn drag off _shapeList.Add(_draggingCurrentShape); _dragging = false; // Invalidate to raise Paint event canvasPictureBox.Invalidate(); }}

private void canvasPictureBox_Paint(object sender, PaintEventArgs e){ // Clear everything e.Graphics.Clear(canvasPictureBox.BackColor); // Draw all previous shapes for (int i = 0; i < _shapeList.Length; i++) _shapeList[i].draw(e.Graphics); // Draw current if (_dragging) _draggingCurrentShape.draw(e.Graphics);}...

Page 115: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

and to run the program. Now you're able to draw ellipse shapes! Don't fo rget to test the Filled option.

Let's discuss this code:

Page 116: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void canvasPictureBox_MouseDown(object sender, MouseEventArgs e){if (!_dragging){ // Save starting point, convert to rectangle _startingX = e.X; _startingY = e.Y; Rectangle rectangle = new Rectangle(e.X, e.Y, 0, 0); // Create current Shape _draggingCurrentShape = new Ellipse(rectangle, _shapeColor, filledCheckBox.Checked); // Indicate we're drawing by "rubber-banding" _dragging = true; }}

private void canvasPictureBox_MouseMove(object sender, MouseEventArgs e){ if (_dragging) { // Determine and set current shape dimension based on current position _draggingCurrentShape.Width = e.X - _startingX; _draggingCurrentShape.Height = e.Y - _startingY; // Invalidate to raise Paint event canvasPictureBox.Invalidate(); }}

private void canvasPictureBox_MouseUp(object sender, MouseEventArgs e){ // Add to shape list if (_dragging) { // Add new shape and turn drag off _shapeList.Add(_draggingCurrentShape); _dragging = false; // Invalidate to raise Paint event canvasPictureBox.Invalidate(); }}

private void canvasPictureBox_Paint(object sender, PaintEventArgs e){ // Clear everything e.Graphics.Clear(canvasPictureBox.BackColor); // Draw all previous shapes for (int i = 0; i < _shapeList.Length; i++) _shapeList[i].draw(e.Graphics); // Draw current if (_dragging) _draggingCurrentShape.draw(e.Graphics);}...

Page 117: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

We've updated all o f the canvasPict ureBo x events. When the canvasPict ureBo x_Mo useDo wn event israised, we check _dragging, and if we're not already rubber banding, we preserve the location o f the mouseclick as our starting po int. Then we create an Ellipse object, using a rectangle object fo r the dimensions andand the Checked property o f the CheckBox, save the Ellipse object in _draggingCurrent Shape , and setthe _dragging to t rue . Note that this variable is a Shape object. Other than the Ellipse class, this code is theonly place you'll see child-specific code. When you work on the course pro ject, you want to modify the codein this location to check the ComboBox, and determine which child-specific code you need. You won't needto worry about changing the data type o f _draggingCurrent Shape , because its data type is a base o f all o fthe shapes you create.

As you move the mouse, the canvasPict ureBo x_Mo useMo ve event is raised. In this event handler, weadjust the dimensions o f _draggingCurrent Shape , and raise the Paint event by calling the Invalidat emethod o f the PictureBox. We also verify that we're rubber banding. Once the canvasPict ureBo x_Mo useUpevent is raised, we add _draggingCurrent Shape to the _shapeList variable using the Add method, set_dragging to f alse to indicate that we've stopped rubber banding, and again, raise the Paint event.

When the canvasPict ureBo x_Paint Paint event handler is raised, we clear the PictureBox, and draw all o fthe shapes in the _shapeList object. We use indexer syntax by referencing [i] . For each shape, we call thatshapes draw method. Remember, the _shapeList consists o f an array that stores Shape objects, but we'restoring actual child class objects in the array. As we call each draw method, we're actually calling the childmethod that overrode the parent class method. To conclude the Paint event, we determine whether we'redrawing, and if we are, draw our current shape.

Isn't that coo l?

We're not quite finished though. We still need to enable the ability to change the shape co lor, and reset thecanvas.

Modify the code in MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.private void colorButton_Click(object sender, EventArgs e){ // Display a ColorDialog ColorDialog colorDialog = new ColorDialog(); colorDialog.AllowFullOpen = false; colorDialog.ShowHelp = false; colorDialog.Color = _shapeColor; if (colorDialog.ShowDialog() == DialogResult.OK) { // Set current color, and update UI displaying current color _shapeColor = colorDialog.Color; shapeColorPictureBox.BackColor = colorDialog.Color; }}

private void resetToolStripMenuItem_Click(object sender, EventArgs e){ // Clear the shapes and raise Paint event _shapeList.Clear(); canvasPictureBox.Invalidate();}...

Click to save your changes.

and to run the program. Now you can change the shape co lor, and clear the canvas.

Page 118: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void colorButton_Click(object sender, EventArgs e){ // Display a ColorDialog ColorDialog colorDialog = new ColorDialog(); colorDialog.AllowFullOpen = false; colorDialog.ShowHelp = false; colorDialog.Color = _shapeColor; if (colorDialog.ShowDialog() == DialogResult.OK) { // Set current color, and update UI displaying current color _shapeColor = colorDialog.Color; shapeColorPictureBox.BackColor = colorDialog.Color; }}

private void resetToolStripMenuItem_Click(object sender, EventArgs e){ // Clear the shapes and raise Paint event _shapeList.Clear(); canvasPictureBox.Invalidate();}...

The reset T o o lSt ripMenuIt em_Click event we use to reset the canvas is fairly straightforward. We call theClear method o f the _shapeList object to remove all the shapes, then raise the Paint event usingInvalidat e .

The co lo rBut t o n_Click event is raised when the user tries to change the shape co lor. We create aninstance o f a Co lo rDialo g contro l, set a few properties (most notably the current shape co lor stored in the_shapeCo lo r variable) show the co lor dialog, and then test to see if the user clicked the ok button bycomparing against Dialo gResult .OK. If the ok button was clicked, then we set our _shapeCo lo r classvariable to the Co lo r selected by the user, and change the shapeCo lo rPict ureBo x.BackCo lo r so that thenew co lor is displayed.

You've just completed a shape drawing program! While creating the program, you've also learned aboutgraphics drawing, generalization through inheritance, and specialization through overriding a virtual methodin a base class. Nice go ing!

Make sure you finish up any homework assignments and pro ject before you move on to the next lesson...

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 119: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Abstract and Sealed Classes

In this lesson, we'll continue to explore the class functionality built into C#. We'll learn how to make abstract and sealedclasses, and how to call a base class constructor from a child class. We'll also create a fun new game using thesecomponents, as well as some new UI elements.

This lesson covers these topics:

ConceptualizingPopIt - A Coding Tutorial

ConceptualizingWe've already touched briefly on the subject o f class design. As you learn more o f the C# language and gain moreexperience writing your own software, you'll also gain confidence designing your own classes.

Abstract Classes

We've seen the virt ual and override keywords used with class methods. The virt ual keyword helped us toestablish a method in a base class that child classes overrode, making the base and child class compatible.On occassion we may want to require that derived (child) classes implement certain functionality. Essentially,an abstract class is an incomplete class:

An abstract class cannot be instantiated.To use an abstract class, it must serve as a base class.All abstract methods and accessor properties must be implemented by the derived class.All abstract methods and properties in an abstract class are implicitly virtual and require theo verride keyword in the derived class.Abstract classes may include non-abstract methods, accessor properties, and fields.Constructors cannot be abstract in an abstract class.

Let's create a couple o f classes so we can see the syntax.

Select File | New | Pro ject , and change the Name of the pro ject to Abst ract Classes.

Right-click the pro ject in the So lution Explorer and select Add | Class..., enter Parent Class fo r the className, and click Add. Modify Parent Class.cs as shown:

Page 120: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. public abstract class ParentClass{ // Fields cannot be abstract public int FieldVariable; // Properties may or may not be abstract private int _property; public int Property { get { return _property; } set { _property = value; } } public abstract int AbstractProperty { get; set; } // Constructors cannot be abstract public ParentClass() { } // Methods may or may not be abstract public void AMethod() { } public abstract void AbstractMethod();} ...

Click to save your changes.

Page 121: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

public abstract class ParentClass{ // Fields cannot be abstract public int FieldVariable; // Properties may or may not be abstract private int _property; public int Property { get { return _property; } set { _property = value; } } public abstract int AbstractProperty { get; set; } // Constructors cannot be abstract public ParentClass() { } // Methods may or may not be abstract public void AMethod() { } public abstract void AbstractMethod();}

As you study the abstract class definition for Parent Class, take note o f the usage o f the abst ract keyword.Also note that fo r the abstract accessor, we terminated the get and set accessor statements withoutimplementation, and for the abstract method we terminated the method definition without an implementation.Let's look at a derived class for the Parent Class.

Right-click the pro ject in the So lution Explorer and select Add | Class..., enter ChildClass fo r the className, and click Add. Modify ChildClass.cs as shown:

CODE TO TYPE:

.

.

.public class ChildClass : ParentClass{ // Implement abstract property private int _abstractProperty; public override int AbstractProperty { get { return _abstractProperty; } set { _abstractProperty = value; } } // Implement abstract method public override void AbstractMethod() { }}...

Click to save your changes.

Page 122: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public class ChildClass : ParentClass{ // Implement abstract property private int _abstractProperty; public override int AbstractProperty { get { return _abstractProperty; } set { _abstractProperty = value; } } // Implement abstract method public override void AbstractMethod() { }}...

In our derived ChildClass class, we inherit from Parent Class. We're required to implement any abstractmethods or properties; we did that using the o verride keyword.

NoteIn the base abstract class Parent Class, the Abst ract Pro pert y property is an automaticproperty with no backing private variable. We could have implemented this property in ourderived class as an automatic property, but we opted instead to create the _abst ract Pro pert ybacking variable.

Calling Base Class Constructors

We didn't attempt to call our base class constructor in our code. Does that mean that the base classconstructor wasn't called? Nope. Constructors exist fo r every class, even if we don't code them explicitly. Allclasses have an empty default constructor provided by .NET, unless an explicit constructor is coded. Whenwe create a derived class (and all classes derive at least from the Object class), .NET works its way up theinheritance chain until it reaches the final base class, then proceeds to work back down the inheritance chain,calling each constructor, until the final (or leaf) child class constructor is called and the child class isinstantiated. Rather than settle fo r the default constructors, we can contro l which constructor o f a base class iscalled. Let's look at the syntax for calling a base class constructor from a derived class.

Modify Parent Class.cs as shown:

CODE TO TYPE:

.

.

. // Fields cannot be abstractpublic int FieldVariable;

// Constructorpublic ParentClass(int fieldVariable){ FieldVariable = fieldVariable;}...

Click to save your changes.

Page 123: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public int FieldVariable;

// Constructorpublic ParentClass(int fieldVariable){ FieldVariable = fieldVariable;}...

We added a new constructor to the class that takes a single parameter, f ie ldVariable , which is used toinitialize the class field FieldVariable . Let's see what it takes to inherit from this class, calling the constructorwith a parameter.

Modify ChildClass.cs as shown below:

CODE TO TYPE:

.

.

.// Constructor invoking base constructorpublic ChildClass(int fieldVariable) : base(fieldVariable){}...

Click to save your changes.

OBSERVE:

.

.

.// Constructor invoking base constructorpublic ChildClass(int fieldVariable) : base(fieldVariable){}...

We modified our abstract ParentClass class to have a constructor with a single parameter. Once we madethis change, our ChildClass required that we provide a constructor with the same signature. Without anyconstructors, our ChildClass default constructor was implicitly calling the default constructor o f theParentClass. Once we provide a constructor with a parameter, .NET will no longer implicitly call the defaultconstructor, so we have to provide a matching constructor with the same signature in our derivedParentClass class.

Now that we've clarified why we had to change our base class constructor to have a parameter, note in theChildClass class how we called the base class constructor by using the co lon operator (:), fo llowed by thebase keyword and the appropriate parameters. The base keyword in this context stands for the constructor o four base class. Which constructor is called depends on the signature used. Since we only have a singleconstructor, we use the one that matches. Notice that the parameter sent to our base constructor,f ie ldVariable , is the same variable we received in our child constructor.

Sealed Classes

Page 124: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

When we're overriding code in a base class, or being forced to implement code in an abstract class, we maydecide that we do not want any further inheritance to occur with our classes. To prevent any further derivation,we use the sealed keyword. Sealed may apply to an entire class, or to individual overrides o f methods orproperties. Here's a summary o f the properties o f sealed:

Sealed classes prevent any further derivation.Sealed methods or properties apply only to overridden methods or properties.A sealed class cannot be used as a base class.Abstract classes cannot be sealed classes (because sealed classes prevent derivation).Sealed classes benefit from run-time optimizations.

Let's see a piece o f code that embodies these rules, using our previous example.

Right-click the pro ject in the So lution Explorer and select Add | Class.... Enter SealedChildClass fo r theclass Name, then click Add. Modify SealedChildClass.cs as shown:

CODE TO TYPE:

.

.

. public sealed class SealedChildClass : ParentClass{ // Seal abstract property implementation, unnecessary in a sealed class private int _abstractProperty; public sealed override int AbstractProperty { get { return _abstractProperty; } set { _abstractProperty = value; } } // Seal abstract method implementation, unnecessary in a sealed class public sealed override void AbstractMethod() { }}...

Click to save your changes.

Page 125: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public sealed class SealedChildClass : ParentClass{ // Seal abstract property implementation, unnecessary in a sealed class private int _abstractProperty; public sealed override int AbstractProperty { get { return _abstractProperty; } set { _abstractProperty = value; } } // Seal abstract method implementation, unnecessary in a sealed class public sealed override void AbstractMethod() { }}...

In this code, we created two classes; we used sealed to prevent further derivation. We sealed the class soSealedChildClass cannot serve as a base class for another class, and we sealed the implemented propertyand method, also preventing any derived class from overriding the implementations.

Wait—derived class? Didn't we just say we sealed the class anyway? We do not have to seal a class to sealmethods or properties that we've overridden from a base class. Conversely, we do not have to seal anymethods or properties in a sealed class. In our code, using the sealed keyword in the method and propertyis unnecessary because the entire class is sealed. If we unsealed the class, then the method and propertysealed would apply.

Okay, let's have some fun!

PopIt - A Coding TutorialIn this tutorial, we'll create a fun program that displays shapes randomly in a continually increasing number, allowingthe user a limited amount o f time to click on each shape to make it disappear. We'll use many o f the techniques we'veused before, but we're go ing to use abstract and sealed, a base constructor invocation, and incorporate a ProgressBarin the StatusStrip. Let's go!

PopIt User Interface Prototypes

Here are the PopIt program interface elements, including the UI in the Design Editor, and the running programUI:

Design Edit o r UI

Page 126: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Runt ime UI

Creating the User Interface

Let's start creating our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to Po pIt and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to Po pIt . Click

to save your changes.

Now let's add the contro ls we'll need.

Add a MenuStrip contro l by dragging the MenuStrip from the Too lbox and dropping it on the MainForm. TheMenuStrip by default will dock to the top o f the Form beneath the Form Titlebar. Change the Name property o fthe MenuStrip to mainMenuSt rip. Click on the MenuStrip, click in the text box that appears, and enter &File .

Page 127: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Add another menu item and name it E&xit . Click to save your changes.

We'll keep the default names generated by Studio for the menu items.

Let's add the event handler C# code for the Exit menu option, and for the Form Shown event.

Use the mouse to open the File menu, and double-click the Exit menu item to generate the Exit eventhandler. Add the code shown below to the Exit event handler:

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Select the MainForm by clicking on the Form Titlebar. Click on the Properties Window Event icon, then

double-click the Sho wn event to generate the Shown event handler. Click to save your changes.

We'll use a StatusStrip to display the running count o f the number o f shapes popped, and a ProgressBar onthe StatusStrip to show a countdown until the time expires and the display refreshes. Then we'll add aPictureBox for our drawing surface.

Drag a StatusStrip contro l onto the Form. Change the Name property to mainSt at usSt rip. Click the drop-down contro l on the StatusStrip and select the St at usLabel. Change the Name property o f the StatusLabelto po psT o o lSt ripSt at usLabel. Change the Text property to Po ps: 0 . Set the Spring property o f theStatusLabel to T rue .

Click the drop-down contro l on the StatusStrip and select Pro gressBar. Change the Name property o f theProgressBar to remainingT o o lSt ripPro gressBar. Change the Size.Width property o f the ProgressBar to

100. Click to save your changes.

We'll change o ther properties o f the ProgressBar dynamically at runtime.

Now let's add the PictureBox and the events we'll need later.

Drag a PictureBox contro l onto the Form. Click on the small black arrow at the top right o f the contro l, andselect Do ck in Parent Co nt ainer. Change the Name property to mainPict ureBo x. With the PictureBoxcontro l selected in the Form, select the Properties Window Event icon, then, in turn, double-click on the

Mo useUp and Paint events to generate their event handlers. Click to save your changes.

Finally, we'll use a Timer contro l to generate timed events to make our game move along.

Drag a Timer contro l onto the Form. Change the Name property to pro cessT imer. Change the Enabledproperty to t rue . Change the Interval property to 200 . Select the Properties Window Event icon, and double-click on the Tick event to generate the event handler.

Adding the Code

Now that the UI is complete, let's add the code. First, we'll create our Popper class.

Right-click the Po pIt so lution in the So lution Explorer, select Add | Class, enter Po pper in the Nametextbox, then click Add. Modify Po pper.cs as shown:

Page 128: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace PopIt{ public abstract class Popper { public Color PopperColor; public int X; public int Y; public int Width; public int Height; public Popper(Rectangle rectangle, Color color) { X = rectangle.X; Y = rectangle.Y; Width = rectangle.Width; Height = rectangle.Height; PopperColor = color; } public Rectangle GetRectangle() { return new Rectangle(X, Y, Width, Height); } public abstract void Draw(Graphics graphics); public abstract bool Hit(Point point); }}

Click to save your changes.

Page 129: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace PopIt{ public abstract class Popper { public Color PopperColor; public int X; public int Y; public int Width; public int Height; public Popper(Rectangle rectangle, Color color) { X = rectangle.X; Y = rectangle.Y; Width = rectangle.Width; Height = rectangle.Height; PopperColor = color; } public Rectangle GetRectangle() { return new Rectangle(X, Y, Width, Height); } public abstract void Draw(Graphics graphics); public abstract bool Hit(Point point); }}

We added the abst ract keyword to the class and to a couple o f class methods. The class methods have noimplementation. We also created a non-default constructor, so any derived classes from Po pper will have tocall this non-default constructor, as well as implement the two abstract methods.

We created a non-abstract method, Get Rect angle , that all derived classes can use to return a Rectanglestructure from the class fields. Once again, we used fields rather than properties for convenience and brevity.

Note

When coding your own applications, use automatic properties. The methods, fields, properties,and similar parts o f a class, comprise the class signature, but a field has a different signaturethan a property. If you need to change the field to a property, all code that derives from the classwill essentially be broken. However, if your fields are automatic properties, you could addbacking private class variables and change your code without breaking the class signature.

Let's add two more classes that derive from our Po pper abstract class, to use for shapes for our game.

Right-click the Po pIt so lution in the So lution Explorer, select Add | Class, enter Rect anglePo pper in theName textbox, and click Add. Modify Rect anglePo pper.cs as shown:

Page 130: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace PopIt{ public sealed class RectanglePopper : Popper { public RectanglePopper(Rectangle rectangle, Color color) : base(rectangle, color) { // Constructor to enable us to call base constructor } public override void Draw(Graphics graphics) { // Draw rectangle graphics.DrawRectangle(new Pen(base.PopperColor), base.X, base.Y, base.Width, base.Height); } public override bool Hit(Point point) { // Call abstract base class method return base.GetRectangle().Contains(point); } }}

Right-click the Po pIt so lution in the So lution Explorer, select Add | Class, enter Ro undPo pper in the Nametextbox, and click Add. Modify Ro undPo pper.cs as shown:

Page 131: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace PopIt{ public sealed class RoundPopper : Popper { public RoundPopper(Rectangle rectangle, Color color) : base(rectangle, color) { // Constructor to enable us to call base constructor } public override void Draw(Graphics graphics) { // Draw ellipse graphics.DrawEllipse(new Pen(base.PopperColor), base.X, base.Y, base.Width, base.Height); } public override bool Hit(Point point) { // Call abstract base class method return base.GetRectangle().Contains(point); } }}

Click to save your changes. Other than the name, these two classes are identical. Let's discuss theRoundPopper class as representative o f both classes.

Page 132: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace PopIt{ public sealed class RoundPopper : Popper { public RoundPopper(Rectangle rectangle, Color color) : base(rectangle, color) { // Constructor to enable us to call base constructor } public override void Draw(Graphics graphics) { // Draw ellipse graphics.DrawEllipse(new Pen(base.PopperColor), base.X, base.Y, base.Width, base.Height); } public override bool Hit(Point point) { // Call abstract base class method return base.GetRectangle().Contains(point); } }}

We created the sealed Ro undPo pper class that inherits from the abstract class Po pper. Because theparent (base) class is abstract, we implemented Draw and Hit methods using the o verride keyword, asrequired when overriding a virtual base method (by definition, all abstract methods in a base class are virtual).We also implemented a constructor fo r the Ro undPo pper class to enable calling the base class constructorwith the correct signature. Finally, in our implementation o f Hit , which takes a Po int structure as a parameter,we use the base class Get Rect angle method that creates a Rectangle structure from the base class fields(for convenience). With this returned Rectangle structure, we call the Co nt ains method to determine whetherour po int parameter is within the Rectangle; this is a convenient way to check for hit detection.

We'll also need our own co llection class.

Right-click the Po pIt so lution in the So lution Explorer, select Add | Class, enter Po ppersList in the Nametextbox, and click Add. Modify Po ppersList .cs as shown:

Page 133: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace PopIt{ public class PoppersList { private Popper[] _poppers = null; // Indexer public Popper this[int index] { get { return _poppers != null ? _poppers[index] : null; } } // Count of poppers public int Length { get { return _poppers != null ? _poppers.Length : 0; } } // Add a popper public int Add(Popper popper) { // Create or resize our poppers array if (_poppers == null) _poppers = new Popper[1]; else Array.Resize(ref _poppers, _poppers.Length + 1); _poppers[_poppers.Length - 1] = popper; return _poppers.Length; } public void Delete(Popper popper) { if (_poppers != null) { if (_poppers.Length > 1) { Popper[] newPopper = new Popper[_poppers.Length - 1]; int count = 0; for (int i = 0; i < _poppers.Length; i++) { if (_poppers[i] != popper) newPopper[count++] = _poppers[i]; } Clear(); _poppers = newPopper; } else Clear(); } } // Clear all poppers public void Clear() { if (_poppers != null) { // Remove reference to popper objects, then undimension poppers array Array.Clear(_poppers, 0, _poppers.Length); _poppers = null;

Page 134: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} } }}

Click to save your changes.

Page 135: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace PopIt{ public class PoppersList { private Popper[] _poppers = null; // Indexer public Popper this[int index] { get { return _poppers != null ? _poppers[index] : null; } } // Count of poppers public int Length { get { return _poppers != null ? _poppers.Length : 0; } } // Add a popper public int Add(Popper popper) { // Create or resize our poppers array if (_poppers == null) _poppers = new Popper[1]; else Array.Resize(ref _poppers, _poppers.Length + 1); _poppers[_poppers.Length - 1] = popper; return _poppers.Length; } public void Delete(Popper popper) { if (_poppers != null) { // Only delete if we have at least one item in array if (_poppers.Length > 1) { // Create new array with 1 less element Popper[] newPopper = new Popper[_poppers.Length - 1]; // Copy all array elements but the one we're deleting int count = 0; for (int i = 0; i < _poppers.Length; i++) { if (_poppers[i] != popper) newPopper[count++] = _poppers[i]; } // Clear and remove old array Clear(); _poppers = newPopper; } else // Only clear the array Clear(); } } // Clear all poppers public void Clear() {

Page 136: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

if (_poppers != null) { // Remove reference to popper objects, then undimension poppers array Array.Clear(_poppers, 0, _poppers.Length); _poppers = null; } } }}

The Delet e method removes a Popper object from the array. In essence, this algorithm requires thecontinuous creation o f a new array that's one size smaller than the current array, copying all o f the currentelements into the new array, and skipping the element to be deleted. The algorithm is not very efficient; you'llhave a chance to invent a better algorithm in your upcoming pro ject.

Now that we've created all o f the classes we need, let's move on to coding the Form, and making the gamework. First, we'll add our Form class variables.

Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

. PoppersList _poppersList = new PoppersList(); // Poppers listprivate Color _popperColor = Color.Red; // Popper colorprivate int _hits = 0; // Hit countprivate int _popperCount = 5; // Number of poppersprivate bool _togglePopper = false; // A toggle for determining which popper to displayprivate int _timerMilliseconds = 2000; // Refresh timeprivate int _popperSize = 30; // Size of our popperprivate int _maximumPoppers = 10; // Maximum number of poppersRandom _random = new Random(); // Random generator...

Click to save your changes.

These variables have been named according to their purpose; these names are fo llowed with a shortdescription. An element we'll pay particular attention to is the Random class variable. Previously, when we'veused the Random object, we've used a local variable in the method that needed to generate the randomnumber. So, why are we making this Random object a class variable? The Random object uses the numberof ticks in the current time when creating the Random object, so if you recreate a new Random object veryquickly, you tend to get the same number. One way around this problem is to use a single Random object,like we're do ing in our code. Another option would be to use a different constructor o f the Random object witha seed parameter value. You could change this seed value with every call, and if the seed is random, then thenumber generated is random. It's easier and more efficient to use a single Random object.

Next, let's update the ProgressBar properties in our Form constructor.

Modify MainFo rm.cs as shown:

Page 137: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

public MainForm(){ InitializeComponent(); // Set up progress bar remainingToolStripProgressBar.Minimum = 0; remainingToolStripProgressBar.Maximum = _timerMilliseconds; remainingToolStripProgressBar.Step = processTimer.Interval;}

Click to save your changes.

We set the range o f our ProgressBar from 0 to the number o f milliseconds until our timer goes o ff: 2000milliseconds or, two seconds. We also set up the ProgressBar Step property that measures and displays therelative amount that the ProgressBar changes. We want the program to refresh every two seconds, but every1/10 o f a second, we want the progress bar to move to show how much time is left. Our timer is set to raise aTick event every 200 milliseconds as well. We'll see more about this concept shortly.

Let's create a method to add a Popper to our Popper list.

Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

. private void addPoppers(){ // Add until we reach the current maximum while (_poppersList.Length < _popperCount) { // Generate random x and y positions (don't worry if they overlap) int x = _random.Next(0, mainPictureBox.ClientRectangle.Width - _popperSize - 1); int y = _random.Next(0, mainPictureBox.ClientRectangle.Height - _popperSize - 1); // Create a rectangle from Rectangle rectangle = new Rectangle(x, y, _popperSize, _popperSize); if (_togglePopper) _poppersList.Add(new RectanglePopper(rectangle, _popperColor)); else _poppersList.Add(new RoundPopper(rectangle, _popperColor)); _togglePopper = !_togglePopper; }}...

Click to save your changes.

Let's discuss this code.

Page 138: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void addPoppers(){ // Add until we reach the current maximum while (_poppersList.Length < _popperCount) { // Generate random x and y positions (don't worry if they overlap) int x = _random.Next(0, mainPictureBox.ClientRectangle.Width - _popperSize - 1); int y = _random.Next(0, mainPictureBox.ClientRectangle.Height - _popperSize - 1); // Create a rectangle from Rectangle rectangle = new Rectangle(x, y, _popperSize, _popperSize); if (_togglePopper) _poppersList.Add(new RectanglePopper(rectangle, _popperColor)); else _poppersList.Add(new RoundPopper(rectangle, _popperColor)); _togglePopper = !_togglePopper; }}...

We use the addPo ppers method to make sure we have the maximum number o f Popper shapes in the_po ppersList . This method uses the while syntax to verify that the Lengt h (count o f elements) is less thanthe current value o f _po pperCo unt , looping until we've added the correct number. When we create a Popper,we a generate random location within the Client Rect angle o f the PictureBox, accounting for the size o f thePopper and the edge o f the PictureBox. We use a boo lean class variable _t o gglePo pper to toggle betweenthe Rect anglePo pper class and Ro undPo pper class.

Now that we can add Poppers to our co llection class, we need to draw them. Let's update the PictureBoxPaint event so that it always draws whatever is in our co llection, and the Form Shown event to finally make thePoppers appear!

Modify MainFo rm.cs as shown:

CODE TO TYPE:

private void mainPictureBox_Paint(object sender, PaintEventArgs e){ // Clear everything e.Graphics.Clear(mainPictureBox.BackColor); // Draw all poppers for (int i = 0; i < _poppersList.Length; i++) _poppersList[i].Draw(e.Graphics);}

private void MainForm_Shown(object sender, EventArgs e){ addPoppers(); mainPictureBox.Invalidate();}...

Click to save your changes, then click to run the program.

Page 139: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

We always clear the PictureBox, then loop through all o f the Poppers in our co llection, calling the Drawmethod o f each Popper.

Finally, we need to add the PictureBox MouseUp event code, and the Timer Tick event code, to make thegame fully functional.

Modify MainFo rm.cs as shown:

Page 140: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void processTimer_Tick(object sender, EventArgs e){ // Disable timer processTimer.Enabled = false; // Determine next progress bar value int value = remainingToolStripProgressBar.Value - remainingToolStripProgressBar.Step; // Prevent progress bar value from going below 0, and update progress bar if (value < 0) value = 0; remainingToolStripProgressBar.Value = value; // Only process if we are short any poppers, and we've reached the end of the timer if (_poppersList.Length < _popperCount && value == 0) { if (_poppersList.Length == 0) { // Only increment popper count if cleared within time frame if (_popperCount <= _maximumPoppers) _popperCount += 1; } // Add necessary poppers addPoppers(); } // Reset progress bar if (value == 0) remainingToolStripProgressBar.Value = remainingToolStripProgressBar.Maximum; // Re-enable timer and force repaint processTimer.Enabled = true; mainPictureBox.Invalidate();}

private void mainPictureBox_MouseUp(object sender, MouseEventArgs e){ // Disable timer processTimer.Enabled = false; // Check all poppers to see if we hit for (int i = 0; i < _poppersList.Length; i++) { if (_poppersList[i].Hit(new Point(e.X, e.Y))) { // Update status label with hits using pre-increment popsToolStripStatusLabel.Text = "Pops: " + (++_hits).ToString(); // Delete popper from list _poppersList.Delete(_poppersList[i]); } } // Re-enable timer and force repaint processTimer.Enabled = true; mainPictureBox.Invalidate();}...

and to run the program. You can now play the PopIt game!

Let's discuss this code.

Page 141: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void processTimer_Tick(object sender, EventArgs e){ // Disable timer processTimer.Enabled = false; // Determine next progress bar value int value = remainingToolStripProgressBar.Value - remainingToolStripProgressBar.Step; // Prevent progress bar value from going below 0, and update progress bar if (value < 0) value = 0; remainingToolStripProgressBar.Value = value; // Only process if we are short any poppers, and we've reached the end of the timer if (_poppersList.Length < _popperCount && value == 0) { if (_poppersList.Length == 0) { // Only increment popper count if cleared within time frame if (_popperCount <= _maximumPoppers) _popperCount += 1; } // Add necessary poppers addPoppers(); } // Reset progress bar if (value == 0) remainingToolStripProgressBar.Value = remainingToolStripProgressBar.Maximum; // Re-enable timer and force repaint processTimer.Enabled = true; mainPictureBox.Invalidate();}

private void mainPictureBox_MouseUp(object sender, MouseEventArgs e){ // Disable timer processTimer.Enabled = false; // Check all poppers to see if we hit for (int i = 0; i < _poppersList.Length; i++) { if (_poppersList[i].Hit(new Point(e.X, e.Y))) { // Update status label with hits using pre-increment popsToolStripStatusLabel.Text = "Pops: " + (++_hits).ToString(); // Delete popper from list _poppersList.Delete(_poppersList[i]); } } // Re-enable timer and force repaint processTimer.Enabled = true; mainPictureBox.Invalidate();}...

The pro cessT imer_T ick event is raised with every Timer event; it's used to determine whether we need toincrease the number o f Poppers on the screen, and call the addPo ppers method. This event also updatesthe ProgressBar. The mainPict ureBo x_Mo useUp event occurs whenever a user clicks on the PictureBox.This event handler does hit detection using the Hit method o f each Popper. If a Popper is "popped," we use

Page 142: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

the Delet e method o f the _po ppersList to remove that Popper, and update the StatusStrip. For theStatusStrip update, we use (++hit s).T o St ring() , which uses pre-increment to increment the hit variablebefore we evaluate it, then wraps the ++hit s with parantheses, allowing us to treat the entire expression as asingle integer and call the T o St ring method.

In both methods, we've highlighted code that disables and enables the Timer and calls the Invalidat emethod o f the PictureBox to raise the Paint event. We disable the timer to prevent more events fromhappening while we're processing the current events.

That's our PopIt game!

Before you move on to the next lesson, do your homework. Right-click in the window where this lesson text appears and selectBack. Then select Quiz fo r this lesson in the syllabus and answer the quiz questions. When you finish the quiz questions, clickHand it in at the bottom of that window. Then do the same with the Pro ject(s) fo r the lesson. Your instructor will grade yourquiz(zes) and pro ject(s) and provide guidance if needed.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 143: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Struct Data Type, Enums, and Casting

In this lesson, we'll fo rmally introduce structs, a fundamental data type and a cousin o f classes. Then we'll talk about casting.

This lesson includes these topics:

StructsEnumsCastingColorMatch - A Coding Tutorial

StructsWe've mentioned st ruct s a few times in this course without really defining what they are. Structs are similar toclasses, with a few important differences, one o f the more important being that a class is a reference data type,whereas a struct is a value data type. Because a struct is a value data type, it has these characteristics:

Structs are copied when using the assignment operator.Structs may be instantiated without using the new keyword.Structs cannot declare default constructors, nor a destructor.Fields in structs cannot be initialized unless they are declared as a constant or static.Structs cannot serve as a base class, nor inherit from another struct or class.

Unlike a class, a struct is ideal fo r ho lding small amounts o f seldom-modified data. We've seen that in the Po int objectwhich is used to store small amounts o f data. The code we'll be working through in a minute, illustrates a structdefinition, including a constructor, fields, and a method.

For this lesson, create a test pro ject using File | New | Pro ject , and change the Name of the pro ject to St ruct s. Click

to save it.

Right-click the St ruct pro ject name in the So lution Explorer, select Add | Class..., enter Perso n fo r the class Name,and click Add. Modify Perso n.cs as shown:

Page 144: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public struct class Person{ // Fields public string First; public string Middle; public string Last; // Constructor public Person(string first, string middle, string last) { First = first; Middle = middle; Last = last; } // Method public string FullName() { string result = ""; if (!String.IsNullOrEmpty(First)) result += First; if (!String.IsNullOrEmpty(Middle)) result += result.Length > 0 ? " " + Middle : Middle; if (!String.IsNullOrEmpty(Last)) result += result.Length > 0 ? " " + Last : Last; return result; }}...

Click to save your changes.

Let's discuss this code.

Page 145: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public struct Person{ // Fields public string First; public string Middle; public string Last; // Constructor public Person(string first, string middle, string last) { First = first; Middle = middle; Last = last; } // Method public string FullName() { string result = ""; if (!String.IsNullOrEmpty(First)) result += First; if (!String.IsNullOrEmpty(Middle)) result += result.Length > 0 ? " " + Middle : Middle; if (!String.IsNullOrEmpty(Last)) result += result.Length > 0 ? " " + Last : Last; return result; }}...

If the Perso n struct looks like a class, it should. How would we use the Perso n struct? Let's find out!

Modify Fo rm1.cs as shown:

Page 146: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); UsingStructs();}

private void UsingStructs(){ // Without new keyword Person studentName; studentName.First = "Mary"; studentName.Middle = "Ann"; studentName.Last = "Geewillickers"; Console.WriteLine(studentName.FullName()); // With new keyword Person instructorName = new Person("Thomas", "Edward", "Peachy"); Console.WriteLine(instructorName.FullName());}...

Click to save your changes and to run the program.

We'll use structs throughout the rest o f the lesson to give a clearer idea o f how and when to use them.

NoteIf you research structs online, be sure to qualify your search with C#. Structs have been a fundamentalelement o f most programming languages for many years, predating classes. Although C# structs aresimilar to structs in o ther languages, they are not exactly the same.

EnumsAs programmers, the ability to clarify our code is useful. Consider the fo llowing scenarios:

Boo lean method parameters using explicit true or false.Multistate method parameters.Switch statements.

This code snippet illustrates these scenarios.

Modify Fo rm1.cs as shown:

Page 147: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); UsingStructs(); Enums();}

private void Enums(){ // Cast a vote on a certain day, if possible string voteResult = myVote(2, true);}

private string myVote(int dayOfWeek, bool myChoice){ string voteResult = ""; switch (dayOfWeek) { case 6: voteResult = "Closed!"; break; case 0: voteResult = "Closed!"; break; default: voteResult = "Open"; break; } return voteResult;}...

Click to save your changes and to run the program.

Page 148: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public Form1(){ InitializeComponent(); UsingStructs(); Enums();} private void Enums(){ // Cast a vote on a certain day, if possible string voteResult = myVote(2, true);} private string myVote(int dayOfWeek, bool myChoice){ string voteResult = ""; switch (dayOfWeek) { case 6: voteResult = "Closed!"; break; case 0: voteResult = "Closed!"; break; default: voteResult = "Open"; break; } return voteResult;}...

The program illustrates common scenarios where the code is difficult to read, but we can use enum (enumerations) toimprove the code significantly. Enumerations resemble structs, but use the enum keyword to declare a distinct datatype that consists o f a set o f named constants. The named constants constitute the enumerator list, and become theavailable constants for this data type. Even though enumerations are a distinct data type, they consist o f an underlyingdefault data type (int), and is by far the most common data type used with enums. The first enumerator constant in theenumerator list has a value o f 0 , then 1, and so on, although you can override this sequence when you declare theenumeration. Let's update our code snippet to use enums.

Modify Fo rm1.cs as shown:

Page 149: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private enum DaysOfWeek { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat };private enum Vote { Yes, No, Abstain }; private void Enums(){ // Cast a vote on a certain day, if possible string voteResult = myVote(DaysOfWeek.Tue2, Vote.Abstainedtrue);}

private string myVote(intDaysOfWeek dayOfWeek, boolVote myChoice){ string voteResult = ""; switch (dayOfWeek) { case DaysOfWeek.Sat6: voteResult = "Closed!"; break; case DaysOfWeek.Sun0: voteResult = "Closed!"; break; default: voteResult = "Open"; break; } return voteResult;}...

Click to save your changes and to run the program.

Let's discuss this code.

Page 150: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private enum DaysOfWeek { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat };

private enum Vote { Yes, No, Abstain };

private void Enums(){ // Cast a vote on a certain day, if possible string voteResult = myVote(DaysOfWeek.Tue, Vote.Abstain);}

private string myVote(DaysOfWeek dayOfWeek, Vote myChoice){ string voteResult = ""; switch (dayOfWeek) { case DaysOfWeek.Sat: voteResult = "Closed!"; break; case DaysOfWeek.Sun: voteResult = "Closed!"; break; default: voteResult = "Open"; break; } return voteResult;}...

We added two enumerations using enum : DaysOf Week, and Vo t e . Once they are declared, we can use theseenums just like o ther data types. We use the DaysOf Week enumeration to make it cleaer which day we are passingas a parameter to myVo t e , by using DaysOf Week.T ue rather than a number. We were able to use the enum in aswitch statement to clarify which days the voting o ffices are closed (the weekend). We also illustrated changing thedefault underlying value o f the DaysOf Week Sun constant to 1 using Sun = 1. We used Vo t e to support a parameterwith three options: Yes, No , Abst ain, which gives us a multistate variable beyond true and false, and also clarifies thecode.

These scenarios are certainly not the only ways in which we might benefit from enums; as the lessons continue, we'llpo int out o ther opportunities.

CastingCasting is converting data o f one data type to another data type. We've already had opportunity to see casting in action.Casting can be implicit, where the data types o f the variables invo lved are compatible and can convert without fo rcingthe cast, o r explicit, where the cast operation requires additional coding. For example, with numeric data types, if thecast from one data type to another does not reso lve in a loss o f data, an implicit conversion is usually allowed. Implicitcasting without loss o f data is called widening. If data loss would occur, this cast is not implicitly allowed, and is callednarrowing.

In earlier lessons, we used the Co nvert class for casting, and we used T ryParse methods o f an object if it wasavailable. The Convert class can be used for explicit casting, even if an implicit cast would have worked. The TryParsemethod determines whether a conversion would work and result in a valid data type after the conversion. We have alsoseen explicit casting use the name of a data type surrounded by parentheses. Sometimes, no implicit o r explicit castwill work. Other times, even if a cast does work, the result is incorrect. This happens most o ften with classes (andstructs), so we may need to create our own casting so lution for our class.

When we create casting so lutions for our classes, we'll no tice that these user-defined casts resemble operatoroverloads. Also, when we create a cast, we must declare that the cast is either implicit (which means the cast appliesto both implicit and explicit casts) or explicit (which means that the cast applies only when do ing an explicit cast).Let's try an example.

Page 151: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Right-click the pro ject in the So lution Explorer, select Add | Class..., enter Fahrenheit fo r the class Name, and clickAdd. Right-click the pro ject again in the So lution Explorer, select Add | Class..., enter Celsius fo r the class Name,then click Add. Modify Fahrenheit .cs and Celsius.cs as shown:

CODE TO TYPE (Fahrenheit.cs):

.

.

. public struct class Fahrenheit{ // Public field public float Temperature; // Constructor public Fahrenheit(float temperature) { Temperature = temperature; } // Implicit cast from float to Fahrenheit public static implicit operator Fahrenheit(float temperature) { return new Fahrenheit(temperature); } // Implicit cast from Celsius to Fahrenheit public static implicit operator Fahrenheit(Celsius celsius) { return new Fahrenheit((celsius.Temperature * (5.0F / 9.0F)) + 32.0F); } // Explicit cast from Fahrenheit to long public static explicit operator long(Fahrenheit fahrenheit) { return (long)Math.Round(fahrenheit.Temperature, 0); } // Method to return Fahrenheit to Celsius conversion public float Celsius() { return ((Temperature - 32.0F) * (5.0F / 9.0F)); } // Display Fahrenheit value as a string public override string ToString() { return Temperature.ToString(); }}...

Page 152: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE (Celsius.cs):

.

.

.public struct class Celsius{ // Public field public float Temperature; // Constructor public Celsius(float temperature) { Temperature = temperature; } // Implicit cast from float to Celsius public static implicit operator Celsius(float temperature) { return new Celsius(temperature); } // Implicit cast from Fahrenheit to Celsius public static implicit operator Celsius(Fahrenheit fahrenheit) { return new Celsius((fahrenheit.Temperature - 32.0F) * (5.0F / 9.0F)); } // Explicit cast from Celsius to long public static explicit operator long(Celsius celsius) { return (long)Math.Round(celsius.Temperature, 0); } // Method to return Celsius to Fahrenheit conversion public float Fahrenheit() { return ((Temperature * (9.0F / 5.0F)) + 32); } // Display Celsius value as a string public override string ToString() { return Temperature.ToString(); }}...

Click to save your changes.

Page 153: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. public struct Fahrenheit{ // Public field public float Temperature; // Constructor public Fahrenheit(float temperature) { Temperature = temperature; } // Implicit cast from float to Fahrenheit public static implicit operator Fahrenheit(float temperature) { return new Fahrenheit(temperature); } // Implicit cast from Celsius to Fahrenheit public static implicit operator Fahrenheit(Celsius celsius) { return new Fahrenheit((celsius.Temperature * (5.0F / 9.0F)) + 32.0F); } // Explicit cast from Fahrenheit to long public static explicit operator long(Fahrenheit fahrenheit) { return (long)Math.Round(fahrenheit.Temperature, 0); } // Method to return Fahrenheit to Celsius conversion public float Celsius() { return ((Temperature - 32.0F) * (5.0F / 9.0F)); } // Display Fahrenheit value as a string public override string ToString() { return Temperature.ToString(); }}...public struct Celsius{ // Public field public float Temperature; // Constructor public Celsius(float temperature) { Temperature = temperature; } // Implicit cast from float to Celsius public static implicit operator Celsius(float temperature) { return new Celsius(temperature); } // Implicit cast from Fahrenheit to Celsius public static implicit operator Celsius(Fahrenheit fahrenheit) { return new Celsius((fahrenheit.Temperature - 32.0F) * (5.0F / 9.0F)); } // Explicit cast from Celsius to long public static explicit operator long(Celsius celsius) { return (long)Math.Round(celsius.Temperature, 0); } // Method to return Celsius to Fahrenheit conversion

Page 154: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

public float Fahrenheit() { return ((Temperature * (9.0F / 5.0F)) + 32); } // Display Celsius value as a string public override string ToString() { return Temperature.ToString(); }}...

Wow. First o f all, we're using structs, but all o f these techniques apply to a class as well. We included a public field toho ld our temperature value, and a constructor to support the use o f the new operator and to pass in a float value as thetemperature. Then we provide a series o f operator overloads in each class, using st at ic implicit o perat o r. Theimplicit indicates that we're do ing an implicit overload, which means it will work for both implicit and explicitconversions. o perat o r is fo llowed by the data type we want to convert to , and the parameter contains the data typefrom which we're converting. We also defined an explicit cast using st at ic explicit o perat o r. In our code we createdthe fo llowing casts:

Fahrenheit Celsius

T ype o f co nversio n Co nversio n Fro m Co nversio n T o Co nversio n Fro m Co nversio n T o

Implicit float Fahrenheit float Celsius

Implicit Celsius Fahrenheit Fahrenheit Celsius

Explicit Fahrenheit long Celsius long

So, how would we use these two structs? Let's check out an example.

Modify Fo rm1.cs as shown:

Page 155: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. public Form1(){ InitializeComponent(); UsingStructs(); Enums(); Casting();}

private void Casting(){ // Create a degree character char degree = (char)176; // Declare Fahrenheit struct using implicit float cast for assignment Fahrenheit fahrenheit = 44.0F; Console.WriteLine("{0:F}" + degree + "F is {1:F}" + degree + "C", fahrenheit, fahrenheit.Celsius()); // Output: 44°F is 6.67°C // Declare Fahrenheit struct using new operator fahrenheit = new Fahrenheit(35.75F); // Cast Fahrenheit to Celsius using implicit Fahrenheit to Celsius cast Celsius celsius = fahrenheit; Console.WriteLine("{0:F}" + degree + "C is {1:F}" + degree + "F", celsius, celsius.Fahrenheit()); // Output: 2.083333°C is 35.75°F long currentTemp = (long)fahrenheit; Console.WriteLine(fahrenheit + degree.ToString() + "F is approximately " + currentTemp + degree.ToString() + "F"); // Output: 35.75°F is approximately 36°F } . ..

Click to save your changes and to run the program.

Page 156: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public Form1(){ InitializeComponent(); UsingStructs(); Enums(); Casting();}

private void Casting(){ // Create a degree character char degree = (char)176; // Declare Fahrenheit struct using implicit float cast for assignment Fahrenheit fahrenheit = 44.0F; Console.WriteLine("{0:F}" + degree + "F is {1:F}" + degree + "C", fahrenheit, fahrenheit.Celsius()); // Output: 44°F is 6.67°C // Declare Fahrenheit struct using new operator fahrenheit = new Fahrenheit(35.75F); // Cast Fahrenheit to Celsius using implicit Fahrenheit to Celsius cast Celsius celsius = fahrenheit; Console.WriteLine("{0:F}" + degree + "C is {1:F}" + degree + "F", celsius, celsius.Fahrenheit()); // Output: 2.083333°C is 35.75°F long currentTemp = (long)fahrenheit; Console.WriteLine(fahrenheit + degree.ToString() + "F is approximately " + currentTemp + degree.ToString() + "F"); // Output: 35.75°F is approximately 36°F} . ..

We start o ff with a cast, not using our temperature conversion casts, but rather a strange (char)176 . This cast gives usthe degree symbol (°) and makes our output look nice, using a feature o f the char integral data type to access storedcharacters. Our "real" casting code begins by declaring a Fahrenheit variable, and assigning it the value o f 44.0F. Wecan assign a float value to a Fahrenheit struct directly because o f the implicit cast from float to Fahrenheit. Then weoutput the value o f f ahrenheit because we've provided the ToString override. We also output the equivalent value inCelsius by calling the Celsius method.

Next, we redeclare our f ahrenheit variable using the new operator. Our constructor allows us to use a float valuebecause the constuctor is defined with a parameter (temperature) that is o f a "float" type. Then we declare a Celsiusvariable, and assign it our f ahrenheit variable. We are able to execute this direct assignment between our structsbecause o f our implicit cast from Fahrenheit to Celsius, and, as a convenience, we do the conversion to maintainequivalence. We then output the Celsius values and the conversion to Fahrenheit.

The previous conversions used implicit casting, so we add an explicit casting from Fahrenheit to lo ng, using theexplicit cast (lo ng) , and display the results o f the explicit conversion.

Note that in our two structs we used floating-po int numbers rather than integers (5.0F rather than 5). If we'd used 5,.NET would have provided its own implicit cast, from integer to float; we avo id that implicit cast by using matching datatypes. Also, our output used a different syntax than we've seen before, using string formatting. We'll cover strings andthat ormatting syntax in a later lesson.

Now we're ready to work through a code tutorial!

Page 157: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

ColorMatch - A Coding TutorialIn our coding tutorial fo r this lesson, we'll create a co lor-matching game, but with a twist. Our co lor-matching game willuse the co lor triangle concept to differentiate between primary and complementary co lors, and test our ability todetermine which primary co lors match which o ther complementary co lors, and also to identify which co lors are mixedto produce the o ther co lors in either category. Here's the Color Triangle and a description o f the rules o f the game:

Color Triangle: Rules o f the Game

Primary co lors are red, blue, and yellow.Complementary co lors are green, orange, and purple.Identify the primary co lor fo r a complementary co lor.Identify the complementary co lor fo r a primary co lor.Identify the complementary co lor fo r two primary co lors.Identify the primary co lor fo r two complementary co lors.

In the Color Triangle, primary co lors are located at the triangle corners and complementary co lors are located on thesides. A primary co lor's complement is the side opposite its corner. The complement to any two co lors is the co loropposite the co lor between the two co lors.

Let's get coding!

User Interface Prototypes

Below are images o f the ColorMatch program interface elements, including the UI in the Design Editor, therunning program UI in its initial state, and a two co lor game state with a previous selection mismatch.

Design Edit o r UI

Page 158: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Init ial Runt ime UI

T wo Co lo r Game Mo de Runt ime UI - Previo us Select io n Mat ch

Page 159: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Single Co lo r Game Mo de Runt ime UI - Previo us Select io n Mismat ch

Creating the User Interface

Let's create our user interface.

Page 160: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Select File | New | Pro ject . Change the Name of the pro ject to Co lo rMat ch and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to Co lo rMat ch. Change the StartPosition property to Cent erScreen. Set the FormBorderStyle to FixedSingle to

prevent Form resizing. Click to save your changes.

Now let's add the contro ls we'll need.

Add a MenuStrip contro l by dragging the MenuStrip from the Too lbox and dropping it on the MainForm. TheMenuStrip by default will dock to the top o f the Form beneath the Form Titlebar. Change the Name property o fthe MenuStrip to mainMenuSt rip. Click on the MenuStrip, and in the text box that appears, click again, andenter &File .

Click the text box that appears under the File menu, and enter &Reset . Beneath this menu item, click the textbox and enter a - (dash). Finally, click the next menu text box, and enter E&xit . Click outside o f the menu. Click

to save your changes.

We'll keep the default names generated by Studio for each menu item.

Let's add the event handler C# code for the Reset and Exit menu options, the Form Load, Resize, and Shownevents.

Open the File menu and double-click the Reset menu item to generate the Reset event handler fo r this menuitem. Then, double-click the Exit menu item to generate the Exit event handler fo r this menu item. Add thiscode to the Exit event handler:

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Click to save your changes.

Select the MainForm by clicking on the Form Titlebar. Click on the Properties Window Event icon, anddouble-click the Lo ad event to generate the Load event handler. Repeat the process to generate the FormResize and Sho wn event handlers.

In our program, we'll use a StatusStrip to display the count o f the number o f single-co lor and two-co lormatches, as well as whether a player selection is a match or a mismatch. The StatusStrip will have threeToolStripStatusLabel contro ls.

Drag a StatusStrip contro l onto the Form. Change the Name property to mainSt at usSt rip. Click the drop-down contro l on the StatusStrip and select St at usLabel. Change the Name property o f the StatusLabel tosingleMat chesT o o lSt ripSt at usLabel. Change the Text property to Single Mat ches: 0 . Set the Springproperty to T rue . Add another StatusLabel, and change the Name property tomixMat chesT o o lSt ripSt at usLabel, the Text property to Mix Mat ches: 0 , and Spring property to T rue .Add another StatusLabel, change the name property to mat chT o o lSt ripSt at usLabel, Text property to

Mat ch!, and Spring property to T rue . Click to save your changes.

We'll use a TableLayoutPanel contro l with three co lumns. Both the left and rightco lumns will contain a Panel contro l that contains three dynamic Button contro ls; the center co lumn willcontain a PictureBox contro l that will serve as our drawing surface.

Drag a TableLayoutPanel contro l onto the Form. Change the Name property to t ableLayo ut Panel. Changethe Dock property to Fill by selecting the middle option that appears when selecting the Dock property value.Adjust the number o f rows and co lumns by clicking on the small black arrow at the top right o f the contro l and

Page 161: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

selecting Add Co lumn. Then Remo ve Last Ro w to create a layout with three co lumns and a single row.

Drag a Panel contro l onto the left co lumn of the TableLayoutPanel contro l, change the Name property toprimaryCo lo rsPanel, and Dock the Panel to the parent container.

Drag another Panel contro l onto the right co lumn of the TableLayoutPanel contro l, change the Name propertyto co mplement aryCo lo rsPanel, and Dock the Panel to the parent container.

Drag a PictureBox contro l onto the center co lumn of the TableLayoutPanel, and change the Name property toco lo rPict ureBo x. Click the Event s icon for the co lorPictureBox and double-click the Paint event to create

the appropriate event handler. Click to save your changes.

Adding the Code

Now that the UI is complete, let's add the code. First, we'll create a ColorTriangle struct. We can use the sametechnique to add a class to add a struct in its own file, by changing the class keyword to st ruct after we add it.

Right-click on the So lution Explorer, select Add | Class, enter Co lo rT riangle in the Name textbox, and clickAdd. Modify Co lo rT riangle .cs as shown:

Page 162: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ColorMatch{ public classstruct ColorTriangle { // In ColorMatrix, first row consists of primary colors // second row consists of complementary colors // Color complements are in different rows same column // Mix results of any two colors in same row is color not // selected but in other row private static Color[][] _colorMatrix = { new Color[]{Color.Blue, Color.Red, Color.Yellow}, // Primary colors new Color[]{Color.Orange, Color.Green, Color.Purple} // Complementary colors }; // Return primary color array public static Color[] PrimaryColors() { return _colorMatrix[0]; } // Return complementary color array public static Color[] ComplementaryColors() { return _colorMatrix[1]; } // Return complementary color public static Color GetComplement(Color searchColor) { Color complement = Color.Black; // Find color for (int row = 0; row < _colorMatrix.Length; row++) { // Determine column int col = Array.IndexOf(_colorMatrix[row], searchColor); // Color found, return same column color but from other row if (col >= 0) complement = _colorMatrix[(row == 0 ? 1 : 0)][col]; } return complement; } // Return complement of specified colors public static Color GetMixResult(Color colorOne, Color colorTwo) { Color mixResult = Color.Black; for (int row = 0; row < _colorMatrix.Length; row++) { int colColorOne = Array.IndexOf(_colorMatrix[row], colorOne); int colColorTwo = Array.IndexOf(_colorMatrix[row], colorTwo); if (colColorOne >= 0 && colColorTwo >= 0) mixResult = _colorMatrix[(row == 0 ? 1 : 0)][(3 - colColorOne - colColorTwo)]; } return mixResult; } }}

Click to save your changes.

Page 163: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Let's discuss this code.

Page 164: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ColorMatch{ public struct ColorTriangle { // In ColorMatrix, first row consists of primary colors // second row consists of complementary colors // Color complements are in different rows same column // Mix results of any two colors in same row is color not // selected but in other row private static Color[][] _colorMatrix = { new Color[]{Color.Blue, Color.Red, Color.Yellow}, // Primary colors new Color[]{Color.Orange, Color.Green, Color.Purple} // Complementary colors }; // Return primary color array public static Color[] PrimaryColors() { return _colorMatrix[0]; } // Return complementary color array public static Color[] ComplementaryColors() { return _colorMatrix[1]; } // Return complementary color public static Color GetComplement(Color searchColor) { Color complement = Color.Black; // Find color for (int row = 0; row < _colorMatrix.Length; row++) { // Determine column int col = Array.IndexOf(_colorMatrix[row], searchColor); // Color found, return same column color but from other row if (col >= 0) complement = _colorMatrix[(row == 0 ? 1 : 0)][col]; } return complement; } // Return complement of specified colors public static Color GetMixResult(Color colorOne, Color colorTwo) { Color mixResult = Color.Black; for (int row = 0; row < _colorMatrix.Length; row++) { int colColorOne = Array.IndexOf(_colorMatrix[row], colorOne); int colColorTwo = Array.IndexOf(_colorMatrix[row], colorTwo); if (colColorOne >= 0 && colColorTwo >= 0) mixResult = _colorMatrix[(row == 0 ? 1 : 0)][(3 - colColorOne - colColorTwo)]; } return mixResult; } }}

Page 165: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating a struct by adding a class may seem a bit strange, but it works well after you change class to struct.In the Co lo rT riangle struct, we created a private member variable, and a number o f methods. Note that theprivate member variable and all o f the methods are marked with the st at ic keyword. We want this struct to actjust like the Math object in .Net, so that we can use it without worrying about an instance o f the struct. Structsare value types, which means we can create a variable o f type Co lo rT riangle , but we don't need multiplevariables, so making this struct st at ic works perfectly fo r us. We didn't add the st at ic keyword to the st ructdefinition. Not only will Studio raise an error if you attempt to make a struct static, it wouldn't make sense. Wecan create variables that have a struct data type, but we can't create an instance where a struct wouldn't be areference type, so there is no reason to have a static struct.

In the Co lo rT riangle struct, the _co lo rMat rix jagged array contains two Color arrays that consist o f theprimary and complementary co lors. We created these items as jagged arrays to make them more convenientto work with. We also used an Array object method that we haven't seen before, Array.IndexOf , that helps usfind an array element within an array. Finally, there are array indices code in a couple o f locations where we'vecombined a ternary operator to toggle between element 0 or 1 (row) o f our jagged array, and determinedwhich element in the jagged array was the correct complement fo r the two-co lor game mode. In both cases,we wrapped the index calculation within parantheses to make sure that the calculation was completed beforeany attempts were made to access the array via the index.

Next, we need to implement our Form code so that it will:

Draw our buttons in both Panel contro ls.Draw our co lor splats, either a single co lor fo r single-co lor game mode, or two co lors for two-co lor game mode.Track Button clicks to test fo r accuracy, and update our StatusStrip with the results.

Let's begin by adding a couple o f class variables, another struct, and an enum.

Modify MainFo rm.cs as shown:

Page 166: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private Button[] _primaryButtons; // Array of primary color buttonsprivate Button[] _complementaryButtons; // Array of complementary color buttonsprivate Color[] _bothColorSets = // Array of primary and complementary colors for random color selection new Color[ColorTriangle.PrimaryColors().Length + ColorTriangle.ComplementaryColors().Length];

// Track game modeprivate struct Splat{ public static bool Single = false; // Single color game state public static Color FirstColor; // Single and two color game state color public static Color SecondColor; // Two color game state color public static int SingleMatches = 0; // Number of single color game state matches public static int MixMatches = 0; // Number of two color game state matches}

// Matching enum for StatusStrip updatesprivate enum Match{ Match, // User selected correct matching color Mismatch, // User selected incorrect matching color Ignore, // Ignore a color selection Reset // Reset the StatusStrip};...

Click to save your changes.

All o f our class variables include comments to help you understand their purposes. Our Splat struct is alsoaptly-named and documented. The Splat fields are all static, as we'll be using this structure to track our gamemode: either single-co lor or two-co lor. The Splat struct is declared within our Form class and made privatebecause our Form code is the only code that needs to reference this struct. We've also added an enum calledMatch that we'll use to make it easier to update the StatusStrip.

Next, let's create our Button contro ls, create our Button click event handler stub, and update the Form Loadevent to call our create Button contro ls method. You may use the Tab method to have Studio auto-generatethe event handler stub method. We'll also populate the Color array that ho lds both sets o f co lors in the FormLoad event.

Note A stub is place-ho lder or partially implemented code that provides token or limited functionalityuntil we can complete the implementation.

Modify MainFo rm.cs as shown:

Page 167: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void createButtons(){ // Create primary and complementary buttons _primaryButtons = new Button[ColorTriangle.PrimaryColors().Length]; for (int i = 0; i < ColorTriangle.PrimaryColors().Length; i++) { // Create and assign new Button Button button = new Button() { Text = "", BackColor = ColorTriangle.PrimaryColors()[i], FlatStyle = FlatStyle.Popup, Cursor = Cursors.Hand }; // Add Click event handler for all buttons, and add button to PictureBox button.Click += new EventHandler(buttonClickHandler); _primaryButtons[i] = button; primaryColorsPanel.Controls.Add(button); } // Create complementary buttons _complementaryButtons = new Button[ColorTriangle.ComplementaryColors().Length]; for (int i = 0; i < ColorTriangle.ComplementaryColors().Length; i++) { // Create and assign new Button Button button = new Button() { Text = "", BackColor = ColorTriangle.ComplementaryColors()[i], FlatStyle = FlatStyle.Popup, Cursor = Cursors.Hand }; // Add Click event handler for all buttons, and add button to PictureBox button.Click += new EventHandler(buttonClickHandler); _complementaryButtons[i] = button; complementaryColorsPanel.Controls.Add(button); }}

private void buttonClickHandler(object sender, EventArgs e){ throw new NotImplementedException();}

private void MainForm_Load(object sender, EventArgs e){ // Load both color sets array Array.Copy(ColorTriangle.PrimaryColors(), 0, _bothColorSets, 0, ColorTriangle.PrimaryColors().Length); Array.Copy(ColorTriangle.ComplementaryColors(), 0, _bothColorSets, ColorTriangle.PrimaryColors().Length, ColorTriangle.ComplementaryColors().Length); // Create buttons createButtons();}...

Click to save your changes.

Page 168: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Let's discuss this code.

OBSERVE:

.

.

. private void createButtons(){ // Create primary and complementary buttons _primaryButtons = new Button[ColorTriangle.PrimaryColors().Length]; for (int i = 0; i < ColorTriangle.PrimaryColors().Length; i++) { // Create and assign new Button Button button = new Button() { Text = "", BackColor = ColorTriangle.PrimaryColors()[i], FlatStyle = FlatStyle.Popup, Cursor = Cursors.Hand }; // Add Click event handler for all buttons, and add button to PictureBox button.Click += new EventHandler(buttonClickHandler); _primaryButtons[i] = button; primaryColorsPanel.Controls.Add(button); } // Create complementary buttons _complementaryButtons = new Button[ColorTriangle.ComplementaryColors().Length]; for (int i = 0; i < ColorTriangle.ComplementaryColors().Length; i++) { // Create and assign new Button Button button = new Button() { Text = "", BackColor = ColorTriangle.ComplementaryColors()[i], FlatStyle = FlatStyle.Popup, Cursor = Cursors.Hand }; // Add Click event handler for all buttons, and add button to PictureBox button.Click += new EventHandler(buttonClickHandler); _complementaryButtons[i] = button; complementaryColorsPanel.Controls.Add(button); }}

private void buttonClickHandler(object sender, EventArgs e){ throw new NotImplementedException();}

private void MainForm_Load(object sender, EventArgs e){ // Load both color sets array Array.Copy(ColorTriangle.PrimaryColors(), 0, _bothColorSets, 0, ColorTriangle.PrimaryColors().Length); Array.Copy(ColorTriangle.ComplementaryColors(), 0, _bothColorSets, ColorTriangle.PrimaryColors().Length, ColorTriangle.ComplementaryColors().Length); // Create buttons createButtons();}...

Page 169: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

The creat eBut t o n method uses code we've used before. We call the Co lo rT riangle structPrimaryCo lo rs and Co mplement aryCo lo rs methods if we need a dimension (calling Lengt h as well), o rif we need to extract a co lor. We call creat eBut t o ns from the Form Load event handler, we use theArray.Co py method to populate the _bo t hCo lo rSet s array using methods o f the Co lo rT riangle struct asnecessary. We show the Button click event handler stub.

Although we've created the Button contro ls, we have yet to display them. Let's add the code to display ourButtons, and add a stub for a method to create the game co lors on the PictureBox contro l. We'll also add amethod stub for updating our StatusStrip. Then we add a method for resetting the game, update our o therForm event handlers, and update the Reset menu item event handler. It may seem like a lo t, but we changeonly one or two lines o f code in the event handlers code.

Note

Why so many stub methods? As you program, you'll think o f a method you'll need. Rather thanjust add the method to a To Do list, add the method as a stub—you can add comments aboutwhat the method will do once implemented. Create the stub method with the parameters andreturn type that you think you'll need so you can reference the stub method in your code, eventhough it's not yet implemented. If the method needs to return a value, provide a default returnstub until you can go back and implement it fully. You can add the line o f code t hro w newNo t Implement edExcept io n(); to your stub, but in our case, we want to refer to thesemethods, and want to be able to test the program without raising an exception.

Modify MainFo rm.cs as shown:

Page 170: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void updateDisplay(){ // Primary panel buttons int buttonWidth = primaryColorsPanel.ClientRectangle.Width; int buttonHeight = primaryColorsPanel.ClientRectangle.Height / _primaryButtons.Length; for (int i = 0; i < _primaryButtons.Length; i++) _primaryButtons[i].SetBounds(0, buttonHeight * i, buttonWidth, buttonHeight); // Complementary panel buttons buttonWidth = complementaryColorsPanel.ClientRectangle.Width; buttonHeight = complementaryColorsPanel.ClientRectangle.Height / _complementaryButtons.Length; for (int i = 0; i < _complementaryButtons.Length; i++) _complementaryButtons[i].SetBounds(0, buttonHeight * i, buttonWidth, buttonHeight); // Color panel - note PictureBox drawing done in Paint event // Should never use CreateGraphics() with PictureBox colorsPictureBox.Invalidate();}

private void setColors(){ // Stub}

private void updateStatusStrip(Match singleMatch, Match mixMatch){ // Stub}

private void resetGame(){ // Reset the game updateStatusStrip(Match.Reset, Match.Reset); setColors(); updateDisplay();}

private void MainForm_Shown(object sender, EventArgs e){ resetGame();}

private void MainForm_Resize(object sender, EventArgs e){ updateDisplay();}

private void resetToolStripMenuItem_Click(object sender, EventArgs e){ resetGame();}...

Click and to run the program. The Button contro ls will now display.

Page 171: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void updateDisplay(){ // Primary panel buttons int buttonWidth = primaryColorsPanel.ClientRectangle.Width; int buttonHeight = primaryColorsPanel.ClientRectangle.Height / _primaryButtons.Length; for (int i = 0; i < _primaryButtons.Length; i++) _primaryButtons[i].SetBounds(0, buttonHeight * i, buttonWidth, buttonHeight); // Complementary panel buttons buttonWidth = complementaryColorsPanel.ClientRectangle.Width; buttonHeight = complementaryColorsPanel.ClientRectangle.Height / _complementaryButtons.Length; for (int i = 0; i < _complementaryButtons.Length; i++) _complementaryButtons[i].SetBounds(0, buttonHeight * i, buttonWidth, buttonHeight); // Color panel - note PictureBox drawing done in Paint event // Should never use CreateGraphics() with PictureBox colorsPictureBox.Invalidate();}

private void setColors(){ // Stub}

private void updateStatusStrip(Match singleMatch, Match mixMatch){ // Stub}

private void resetGame(){ // Reset the game updateStatusStrip(Match.Reset, Match.Reset); setColors(); updateDisplay();}

private void MainForm_Shown(object sender, EventArgs e){ resetGame();}

private void MainForm_Resize(object sender, EventArgs e){ updateDisplay();}

private void resetToolStripMenuItem_Click(object sender, EventArgs e){ resetGame();}...

We added the updat eDisplay method to draw the Button contro ls and display our game co lors. We call theInvalidat e method o f the PictureBox contro l to redraw the PictureBox. In updat eDisplay, we draw theButtons, but we need to raise the Paint method o f the PictureBox because when drawing onto contro ls, we

Page 172: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

use the Paint method o f the contro l.

We added the two stub methods we'll need, set Co lo rs and updat eSt at usSt rip, and added calls to thesemethods in the event handlers, as well as calls to updat eDisplay. Spend some time considering why wecreated the methods we created, and why we updated the event handlers with the method calls that we did.

We implemented reset Game , including a call to updat eSt at usSt rip. We'll be using the enumeration wecreated earlier.

So what's left? We need to implement both o f the stub methods, implement the PictureBox Paint method, andimplement the Button event handler. Let's implement the set Co lo rs method and the PictureBox Paintmethod next so we can get the game co lors to display.

Modify MainFo rm.cs as shown:

Page 173: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void setColors(){ // Stub // Switch game state, and create our random generator Splat.Single = !Splat.Single; Random random = new Random(); if (Splat.Single) { // Single color game state - select random color Splat.FirstColor = _bothColorSets[random.Next(_bothColorSets.Length)]; } else { // Two color game state - select primary or complementary row, then // two random colors in that row Color[] workingSet = ColorTriangle.PrimaryColors(); int row = random.Next(2); if (row == 1) workingSet = ColorTriangle.ComplementaryColors(); int max = workingSet.Length; int firstIndex = random.Next(max); Splat.FirstColor = workingSet[firstIndex]; // Create an array containing allowed columns, then randomly select array // element and use value in allowed array as index into colors int[] allowedIndices = new int[workingSet.Length - 1]; int index = 0; for (int i = 0; i < workingSet.Length; i++) { if (i != firstIndex) allowedIndices[index++] = i; } int secondIndex = random.Next(allowedIndices.Length); Splat.SecondColor = workingSet[allowedIndices[secondIndex]]; }}

private void colorsPictureBox_Paint(object sender, PaintEventArgs e){ // Draw color splats on color panel if (Splat.Single) { // Single color game state, calculate maximum diameter and position and draw int diameter = Math.Min(colorsPictureBox.ClientRectangle.Width, colorsPictureBox.ClientRectangle.Height); int x = (colorsPictureBox.ClientRectangle.Width / 2) - (diameter / 2); int y = (colorsPictureBox.ClientRectangle.Height / 2) - (diameter / 2); e.Graphics.FillEllipse(new SolidBrush(Splat.FirstColor), x, y, diameter, diameter); } else { // Two color game state, calculate maximum diameter and position and draw int diameter = Math.Min(colorsPictureBox.ClientRectangle.Width, colorsPictureBox.ClientRectangle.Height / 2); int x = (colorsPictureBox.ClientRectangle.Width / 2) - (diameter / 2); int y = (colorsPictureBox.ClientRectangle.Height / 4) - (diameter / 2); e.Graphics.FillEllipse(new SolidBrush(Splat.FirstColor), x, y, diameter, diameter); y = colorsPictureBox.ClientRectangle.Height / 2; e.Graphics.FillEllipse(new SolidBrush(Splat.SecondColor), x, y, diameter

Page 174: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

, diameter); }}...

Click and to run the program. The game's co lors will now be displayed based on the game mode.The game mode will no t switch yet because we haven't implemented the Button Click event handler; if you doattempt to click on a Button, an exception will be raised.

Let's discuss this code.

Page 175: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void setColors(){ // Switch game state, and create our random generator Splat.Single = !Splat.Single; Random random = new Random(); if (Splat.Single) { // Single color game state - select random color Splat.FirstColor = _bothColorSets[random.Next(_bothColorSets.Length)]; } else { // Two color game state - select primary or complementary row, then // two random colors in that row Color[] workingSet = ColorTriangle.PrimaryColors(); int row = random.Next(2); if (row == 1) workingSet = ColorTriangle.ComplementaryColors(); int max = workingSet.Length; int firstIndex = random.Next(max); Splat.FirstColor = workingSet[firstIndex]; // Create an array containing allowed columns, then randomly select array // element and use value in allowed array as index into colors int[] allowedIndices = new int[workingSet.Length - 1]; int index = 0; for (int i = 0; i < workingSet.Length; i++) { if (i != firstIndex) allowedIndices[index++] = i; } int secondIndex = random.Next(allowedIndices.Length); Splat.SecondColor = workingSet[allowedIndices[secondIndex]]; }}

private void colorsPictureBox_Paint(object sender, PaintEventArgs e){ // Draw color splats on color panel if (Splat.Single) { // Single color game state, calculate maximum diameter and position and draw int diameter = Math.Min(colorsPictureBox.ClientRectangle.Width, colorsPictureBox.ClientRectangle.Height); int x = (colorsPictureBox.ClientRectangle.Width / 2) - (diameter / 2); int y = (colorsPictureBox.ClientRectangle.Height / 2) - (diameter / 2); e.Graphics.FillEllipse(new SolidBrush(Splat.FirstColor), x, y, diameter, diameter); } else { // Two color game state, calculate maximum diameter and position and draw int diameter = Math.Min(colorsPictureBox.ClientRectangle.Width, colorsPictureBox.ClientRectangle.Height / 2); int x = (colorsPictureBox.ClientRectangle.Width / 2) - (diameter / 2); int y = (colorsPictureBox.ClientRectangle.Height / 4) - (diameter / 2); e.Graphics.FillEllipse(new SolidBrush(Splat.FirstColor), x, y, diameter, diameter); y = colorsPictureBox.ClientRectangle.Height / 2; e.Graphics.FillEllipse(new SolidBrush(Splat.SecondColor), x, y, diameter, diameter);

Page 176: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

}}...

The set Co lo rs method works with the Splat struct to determine and update the game state. The PictureBoxPaint event uses the Splat struct when drawing the game co lors. First method toggles (flip) the game mode.Then, depending on the game mode, we determine which co lors to display. In single-co lor game mode, weonly need to set First Co lo r by selecting a random co lor from the _bo t hCo lo rSet s array.

In two-co lor game mode, determining the co lors is a bit more complicated because we have to select fromprimary or complementary co lors randomly from the ColorTriangle struct, then select two co lors randomlyfrom the array. To do this, we consider the PrimaryCo lo rs the default, setting a Color array wo rkingSet .Then we generate a random number, either 0 or 1. If 0 results, that's the default state, and we use the primaryco lors. If 1 results, then we switch to complementary co lors. Next we generate a first index number, and usethat index to grab the co lor from the wo rkingSet . To determine the second index number, we create aninteger array called allo wedIndices that will contain the indices in the wo rkingSet that are still free (in o therwords, we exclude the first index value). Then, we generate a random number that represents the position inthe allo wedIndices. The generated position allows us to extract the element out o f the allo wedIndicesinteger array that contains the index into the wo rkingSet , which we use to extract and set Seco ndCo lo r.

We used a different version o f the random Next method, where only a single parameter is used. With a singleparameter, the lower range is presumed to be zero .

We also implemented the PictureBox Paint event method, using the DrawEllipse method to draw our co lorcircles, depending on the game mode.

Now let's implement the Button Click event handler.

Modify MainFo rm.cs as shown:

Page 177: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void buttonClickHandler(object sender, EventArgs e){ throw new NotImplementedException(); Button button = sender as Button; if (Splat.Single) { // Single-color game state - test if user selection is correct if (button.BackColor == ColorTriangle.GetComplement(Splat.FirstColor)) updateStatusStrip(Match.Match, Match.Ignore); else updateStatusStrip(Match.Mismatch, Match.Ignore); } else { // Two-color game state - test if user selection is correct if (button.BackColor == ColorTriangle.GetMixResult(Splat.FirstColor, Splat.SecondColor)) updateStatusStrip(Match.Ignore, Match.Match); else updateStatusStrip(Match.Ignore, Match.Mismatch); } // Next game state and colors, then update display setColors(); updateDisplay();}

private void updateStatusStrip(Match singleMatch, Match mixMatch){ // Stub // Default state of status strip int singleMatches = 0; int mixMatches = 0; string match = ""; Color matchColor = this.BackColor; if (singleMatch != Match.Reset && mixMatch != Match.Reset) { // Not resetting, update based on enum values singleMatches = Splat.SingleMatches; mixMatches = Splat.MixMatches; if (singleMatch == Match.Match) singleMatches++; if (mixMatch == Match.Match) mixMatches++; if (singleMatch == Match.Match || mixMatch == Match.Match) { match = "Match!"; matchColor = Color.Green; } else { match = "Mismatch!"; matchColor = Color.Red; } }

// Update status strip and Splat matchToolStripStatusLabel.Text = match; matchToolStripStatusLabel.BackColor = matchColor; Splat.SingleMatches = singleMatches; Splat.MixMatches = mixMatches; singleMatchesToolStripStatusLabel.Text = "Single Matches: " + Splat.SingleMatches.ToString();

Page 178: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

mixMatchesToolStripStatusLabel.Text = "Mix Matches: " + Splat.MixMatches.ToString();}...

Click to save your changes and to run the program. The game is now fully functional, running insingle- and two-co lor mode alternately, and presenting primary or complementary co lors randomly wheneverits in two-co lor mode. If you click the correct co lor, "Match!" appears in the Status Strip and the appropriate(Single or Mix) match to tal is incremented.

Let's discuss this code.

Page 179: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void buttonClickHandler(object sender, EventArgs e){ Button button = sender as Button; if (Splat.Single) { // Single-color game state - test if user selection is correct if (button.BackColor == ColorTriangle.GetComplement(Splat.FirstColor)) updateStatusStrip(Match.Match, Match.Ignore); else updateStatusStrip(Match.Mismatch, Match.Ignore); } else { // Two-color game state - test if user selection is correct if (button.BackColor == ColorTriangle.GetMixResult(Splat.FirstColor, Splat.SecondColor)) updateStatusStrip(Match.Ignore, Match.Match); else updateStatusStrip(Match.Ignore, Match.Mismatch); } // Next game state and colors, then update display setColors(); updateDisplay();}

private void updateStatusStrip(Match singleMatch, Match mixMatch){ // Default state of status strip int singleMatches = 0; int mixMatches = 0; string match = ""; Color matchColor = this.BackColor; if (singleMatch != Match.Reset && mixMatch != Match.Reset) { // Not resetting, update based on enum values singleMatches = Splat.SingleMatches; mixMatches = Splat.MixMatches; if (singleMatch == Match.Match) singleMatches++; if (mixMatch == Match.Match) mixMatches++; if (singleMatch == Match.Match || mixMatch == Match.Match) { match = "Match!"; matchColor = Color.Green; } else { match = "Mismatch!"; matchColor = Color.Red; } } // Update status strip and Splat matchToolStripStatusLabel.Text = match; matchToolStripStatusLabel.BackColor = matchColor; Splat.SingleMatches = singleMatches; Splat.MixMatches = mixMatches; singleMatchesToolStripStatusLabel.Text = "Single Matches: " + Splat.SingleMatches.ToString(); mixMatchesToolStripStatusLabel.Text = "Mix Matches: " + Splat.MixMatches.ToString();

Page 180: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

}...

The Button click event handler detects the current game state, then determines whether the Color o f theselected Button is correct by calling Get Co mplement o r Get MixResult o f the ColorTriangle struct andcomparing the Button co lor to the value returned from either o f these methods. Based on the result, we callupdat eSt at usSt rip to update the StatusStrip, then call set Co lo rs to toggle the game mode and generate anew co lor cho ice, and then call updat eDisplay to update the contro ls. In updat eSt at usSt rip we usemethod parameters that are enumerations to determine how to adjust the StatusStrip. Theupdat eSt at usSt rip method includes the ability to reset the StatusStrip. If we aren't resetting, then we needto extract the current game state data from the Splat struct, and adjust accordingly, eventually updating theStatusStrip contro ls.

So, that completes the game! But still many questions remain. What about casting? Did you see any castingcode? No, you didn't. Why? Because this pro ject as designed didn't need any casting code; that's usually thecase. You will employ casting when your design requires it, o r if you see an opportunity to make your codingmore intuitive through casting. This pro ject didn't require casting, but are there opportunities where you couldhave made using your structs more intuitive?

Consider any elements you've created, how they're used, and how you might make them easier to use. Whenyou consider elements like structs and classes, look at their core purposes. Consider any methods you'vecreated, and whether providing casting or operator overload might improve them.

In our program, we added two structs. The ColorTriangle struct is a candidate for potential improvement. Weadded four methods to this struct. Do you think any o f them could be replaced or improved by casting oroperator overloading? The only method that might make sense would be GetComplement. Casting is appliedwhen you attempt to assign or convert one data type to another. Would it ever make sense to assign avariable to Co lorTriangle, or assign ColorTriangle to a variable? Consider the code below, extracted from theButton click event handler that has been modified:

OBSERVE:

if (Splat.Single){ // Single color game state - test if user selection is correct if (ColorTriangle == button.BackColor) //if (button.BackColor == ColorTriangle.GetComplement(Splat.FirstColor)) updateStatusStrip(Match.Match, Match.Ignore); else updateStatusStrip(Match.Mismatch, Match.Ignore);}

Does the new line o f code make intuitive sense? What might you be trying to accomplish? Studio won't let ususe this line. It produce an error indicating that we're trying to use ColorTriangle as a variable when it's a datatype. So even if we could make this code work, intuitively it doesn't help. So, we can conclude that initially,casting does not improve the ColorTriangle struct.

Often, casting is required when we start to add operator overloads. Might operator overloads help us makeusing the ColorTriangle struct more intuitive? These operators are o ften overloaded:

Unary o perat o rs: +, -, !, ~, ++, --, true, falseBinary o perat o rs: +, -, *, /, %, &, |, ^, <<, >>Co mpariso n o perat o rs: ==, !=, <, >, <=, >=

We aren't adding or removing anything from the class, so most o f the operators don't apply. We could comeup with a scenario to overload the == comparison operator, but we've seen how that comparison didn't reallymake sense. So, fo r this lesson, we'll just have to settle fo r an example and actually use casting in a laterlesson.

Before you move on to the next lesson, do your homework. Right-click in the window where this lesson text appears and selectBack. Then select Quiz fo r this lesson in the syllabus and answer the quiz questions. When you finish the quiz questions, clickHand it in at the bottom of that window. Then do the same with the Pro ject(s) fo r the lesson. Your instructor will grade yourquiz(zes) and pro ject(s) and provide guidance if needed.

Copyright © 1998-2014 O'Reilly Media, Inc.

Page 181: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 182: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Interfaces and Interface Casting

In this lesson we're go ing to continue to expand our knowledge o f object oriented programming (OOP) by introducinginterfaces!

This lesson includes these topics:

InterfacesShape Capture - A Coding Tutorial

Interfaces

What is an Interface?

We used interfaces earlier to enable array sorting. In order to enable array sorting within a class, that classhad to inherit from IComparable, which is an interface. By inheriting our class from IComparable, we wereforced to implement the CompareTo method. An interface looks like a class, but uses the int erf ace keywordin place o f class. It resembles an abstract class in that none o f the interface elements include implementation.That's right, none o f the interface elements contain implementations. Interfaces are intended to be inherited bystructs and classes, requiring that struct or class to implement all o f the elements defined by the interface.

We've discussed the logic o f abstract classes, and much o f that same logic and reasoning applies tointerfaces. When a struct or class inherits from an interface, that inheritance guarantees that the derived entitywill implement a specific set o f elements. Even though we will use the same notation o f C# inheritance wheninheriting an interface, an interface is really not inherited, but is said to "implement an interface." Implementingan interface enforces what is known as a co nt ract between the base interface and the struct or classimplementing that interface. As a contract requires certain demands to be met, an interface requires certainfunctionality to be implemented.

Multiple Inheritance

Struct and class inheritance in C# allow only a single inheritance hierarchy from base structs or classes, so , ifyou have a base class, a base struct, and an abstract class that you want to inherit from in a single child class,you must pick only one o f these base elements from which to inherit. Interfaces do not have this limitation. Aclass or struct can implement a single interface, or implement multiple interfaces simultaneously, with orwithout also inheriting from a single struct or class. So, interfaces are a designed way to provide the feature o fmultiple inheritance via interface implementation in the C# language.

NoteMultiple inheritance supported by C# Because o f the potential ambiguity and conflict that mightresult from multiple implementations o f the same element. For example, a method, when usinginterfaces we are guaranteed that elements within the inheritance chain, even with multipleparents, have not been implemented, so we avo id implementation ambiguity and conflict.

Interfaces Versus Abstract Classes

An abstract class where we've chosen to omit all implementations resembles an interface, so why not just doaway with interfaces, and enable multiple inheritance for abstract classes? There are programming languagesthat support multiple class inheritance hierarchy, so the it makes sense to consider the idea. Let's alsoconsider the intended use o f a class (including an abstract class) versus that o f an interface.

The purpose o f a class is to group or encapsulat e related functionality within a single definition. Thepurpose o f class inheritance is to link groups o f related functionality into a hierarchy that moves from generalto specific. The purpose o f an abstract class is to specify elements that are applied broadly and then becomeincreasingly specialized, and the elements are still related to the the functionality and purpose o f the classhierarchy. These concepts embody the OOP principle o f encapsulat io n.

An interface, however, is intended to provide a group o f related functionality that requires full implementationby all derived entities. You'll want to consider using interfaces in these scenarios:

Reusabilit y: Group related functionality that applies to multiple entities co llectively, but not asingle entity individually.Maint ainabilit y: Provide an enforceable contract that eliminates the possibility o f breakingderived code.

Page 183: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Mult iple inherit ance: Provide a mechanism to support multiple inheritance withoutimplementation conflict.

One good example o f reusabilit y and maint ainablit y is the IComparable interface we used with arrays.The array class is designed to provide functionality related to arrays. Sorting is an important feature o f anarray; it is also an important feature for o ther classes that probably have no relationship to arrays. Sorting is away o f comparing objects like elements o f an array or a class and is a prime candidate for an interface.Because any sorting so lution requires specific components to make it work, we use an interface to requireany class that wants to use a sorting interface to implement those methods necessary for sorting. That's howcome we have the IComparable interface.

As you continue to learn more OOP techniques using C#, you'll need to decide whether to create a class oran interface. Good software design incorporates a mixture o f classes and interfaces. You'll get better andbetter at determining the best time to use each too l the more you work with them, so keep practicing!

Creating an Interface

By convention, interfaces are prefixed with a capital I. Use a capital I with any interfaces you create, althoughyou may want to adopt another convention to prevent conflict with the hundreds o f .NET interfaces out there,such as ICustom.

Here's a summary o f rules for interfaces:

Interfaces can include methods, properties, events, indexers, or any combination o f these fourmember types.Interfaces canno t contain constants, fields, operators, instance constructors, destructors, ortypes.Interfaces canno t contain static members.All interface elements are automatically public and canno t include access modifiers.Interfaces can implement from other interfaces.Structs or classes can implement one or more interfaces.Any structs or classes derived from, or said to implement an interface, must implement allelements o f the interface, and those implementation signatures must match the signatures o f theinterface exactly.Modifying an interface changes the contract and requires all structs or classes that implement thatinterface to add implementation per the new contract.

This is just a partial list o f rules for interfaces, but let's stick with these for now.

Let's get to work on an example that demonstrates a parent abstract base class, interface, and a derived classthat inherits from the base class and implements the interface, implementing the required abstract andinterface elements, and overrides the optional virtual abstact base class method.

Create a test pro ject using File | New | Pro ject , and change the Name to Int erf aces. Click to saveyour changes.

Modify the Fo rm1.cs class code as shown:

Page 184: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); ClassesAndInterfaces();}

private void ClassesAndInterfaces(){ ChildClass childClass = new ChildClass();}...abstract class ParentClass{ public string ID = "ParentClass"; public void SomeClassMethod() { } // Implemented base class method public virtual void SomeClassVirtualMethod() { } // Implemented base class virtual method (allows override) public abstract void SomeClassAbstractMethod(); // Base class abstract method (not implemented by definition)}

interface ICustomSample{ string SomeInterfaceProperty { get; set; } // Interface property (not implemented by definition) int SomeInterfaceMethod(); // Interface method (not implemented by definition)}

class ChildClass : ParentClass, ICustomSample{ public string SomeInterfaceProperty { get; set; } // Implemented interface property as automatic property (required) public int SomeInterfaceMethod() { return 1; } // Implemented interface method (required) public override void SomeClassVirtualMethod() { } // Implemented override of base class method (optional) public override void SomeClassAbstractMethod() { } // Implemented override of base class abstract method (required)}...

Click to save your changes, and to run the program.

Note We place both o f the classes and the interface code in the Form1.cs file, after the Form1 class,but before the end o f the namespace.

Page 185: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public Form1(){ InitializeComponent(); ClassesAndInterfaces();}

private void ClassesAndInterfaces(){ ChildClass childClass = new ChildClass();}...abstract class ParentClass{ public string ID = "ParentClass"; public void SomeClassMethod() { } // Implemented base class method public virtual void SomeClassVirtualMethod() { } // Implemented base class virtual method (allows override) public abstract void SomeClassAbstractMethod(); // Base class abstract method (not implemented by definition)}

interface ICustomSample{ string SomeInterfaceProperty { get; set; } // Interface property (not implemented by definition) int SomeInterfaceMethod(); // Interface method (not implemented by definition)}

class ChildClass : ParentClass, ICustomSample{ public string SomeInterfaceProperty { get; set; } // Implemented interface property as automatic property (required) public int SomeInterfaceMethod() { return 1; } // Implemented interface method (required) public override void SomeClassVirtualMethod() { } // Implemented override of base class method (optional) public override void SomeClassAbstractMethod() { } // Implemented override of base class abstract method (required)}...

Let's work with this example a little more. Create a test pro ject, modifying it in various ways to see what worksand what doesn't. Create an instance o f the ChildClass and make sure your classes don't cause any errors.(We've highlighted which classes are implemented in the comments.)

Let's move on to a tutorial that uses interfaces!

Shape Capture - A Coding TutorialWe're go ing to create a game with real motion. The goal o f the game is to use the mouse to move a square on thescreen to capture moving circles. We could use code that's similar to the code we used to make o ther games, butwe're want to try out ournew too ls! Our game will use a combination o f interfaces and classes. We'll create a class thatcontro ls the game, so very little code will be in the Form, except fo r code to respond to user events. We'll also look atthe design o f the game from a class (and interface) perspective.

Page 186: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

User Interface Prototypes

Before we begin planning a class design, we need to identify the objects in our game. You may rememberworking with a TOE chart (Task, Object, Event) before. We won't create a TOE for this game, but we do want toidentify our objects so we can understand what they do, which will help us with the class design o f the game.Let's examine the user interface elements to identify the objects.

Design Edit o r UI

Init ial Runt ime UI

Page 187: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Object Identification and Discussion

Here are our objects:

Object Descript io n

Game o bject (Co llect o rGame) Single object that connects all o f the non-UI elements together.

Capt ure shape o bject(Capt ureShape) The individual shape objects that may be captured.

Co llect o r o bject (Co llect o r) Single object that may be moved by the user that co llectsCaptureShape objects.

These three objects seem intuitive enough, but how would we use classes and interfaces for theimplementation? Should we even try to do that? Even though it may not be obvious right now, usinginterfaces with this game would be useful, and, if we design our classes and interfaces right, using interfaceswill make coding more intuitive and easier to fo llow.

For our ShapeCapture game, we want to create a connection between the Collector object and CaptureShapeobjects, and in turn enable the CaptureGame to manipulate both o f these objects. When we create thisconnection, we want to establish methods and properties that must be implemented, and we want to make itpossible for the Collector object to notify an individual CaptureShape when it has been co llected. When aCaptureShape is captured, the individual CaptureShape should be able to respond to being captured. This isimportant because in our game we're go ing to have a CaptureShape reset its position once captured, but ifwe decide later that we want to change the behavior o f the game, we would change that behavior in theCaptureShape class, without impacting o ther components o f the game.

To enable this connection between Collector and CaptureShape, we're go ing to use three interfaces, as listedin this table:

Int erf ace Descript io n

ICo llector All properties and methods necessary for a Collector object, including the ability to co llectCaptureShape objects.

Page 188: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

IShape All properties and methods for fundamental shape objects.

ICaptureShape Inherited interface from IShape that adds additional properties and methods to IShapeunique to a CaptureShape.

Here are the interface design specifications from Studio Class Diagram:

Int erf aces

Take a look at the ICo llector interface Collect method and the ICaptureShape-OnCollected methods. Here arethe full signatures o f both o f these methods:

The ICo llector Co llect method includes an ICaptureShape object as a parameter. This connection enables anICollector object to co llect a specific ICaptureShape object. When an ICo llector object co llects anICaptureShape object, that ICaptureShape object will be notified via the OnCollected method.

Next, let's look at the relationship between IShape and ICaptureShape. We create an abstract class Shape thatimplements IShape methods and properties, except fo r the Draw method, which we leave abstract. We'rego ing to create additional derived classes from Shape with specific Draw implementations. If we only wanteda single CaptureShape object, then we could have stopped at the Shape class. We've illustrated theRectangularCaptureShape and EllipseCaptureShape classes derived from Shape, but they also implementthe ICaptureShape interface. Later, when we look at the code in detail, you'll see that this inheritance andimplementation simplifies our coding.

Tip Classes that implement an interface are displayed at the top o f the class object. You see a circleconnected to the class object, and the interface name to the right o f the circle.

IShape and ICapt ureShape int erf ace re lat io nships

Page 189: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Note the Collector class and CaptureGame classes below. The Collector class is identical to the ICo llectorinterface it implements, so why do we need the ICo llector interface at all? For our game design, we could skipICollector altogether, but consider the possibility o f more than one Collector in our game. By having aninterface, creating another Co llector would be fairly straightforward. So, why not have the ICo llector be anabstract class? Because we want any potential o ther Co llector objects to be forced to implement all o f theproperties and methods in ICo llector. We could accomplish that in a completely abstract class, but we use aninterface to enforce this contract instead.

What about the CaptureGame class? There will only be a single instance o f this class, so a normal class willwork. This class includes a private _co llectorShapes field. This field is an array o f ICaptureShapes that willcontain all o f the CaptureShape objects. The private _co llector field is the instance o f the Collector object.

Co llect o r and Capt ureGame Classes

Page 190: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Now that the overview is complete, let's create the game!

Creating the User Interface

Let's start creating our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to ShapeCapt ure and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to Shape

Capt ure . Click to save your changes.

Now let's add the contro ls we'll need.

Add a MenuStrip contro l by dragging the MenuStrip from the Too lbox and dropping it on the MainForm. TheMenuStrip docks to the top o f the Form beneath the Form Titlebar. Change the Name property o f theMenuStrip to mainMenuSt rip. Click on the MenuStrip, and in the text box that appears, click again, and enter

&File . Click to save your changes.

Add the Reset, Pause, horizontal separator, and Exit menu items under the File menu.

Under the File menu, add &Reset , &Pause , a - (fo r the horizontal separator), then E&xit .Click away from the

menu to close the editing o f the menu. Click to save your changes.

We'll keep the default names generated by Studio for each menu item.

Add the event handler C# code for the Reset, Pause, and Exit menu options, and for the Form Shown andForm Resize events.

Use the mouse to open the File menu, and double-click the Reset menu item, the Pause menu item, andthe Exit menu item, to generate event handlers for each o f these menu items. Select the MainForm by clickingon the Form Titlebar. Click the Properties Window Event s icon, and double-click on the Sho wn and Resizeevents to generate event handlers for each o f these Form events.

Add the code for the Exit event as shown:

Page 191: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Click to save your changes.

We'll again use a PictureBox contro l fo r our game surface, and we'll also need a Timer contro l to animate ourgame.

Drag a Pict ureBo x contro l onto the Form. Change the Name property to mainPict ureBo x, and theBorderStyle property to Fixed3D. Set the PictureBox to to be Do cked in Parent Co nt ainer. With thePictureBox contro l selected, click the Properties Window Event s icon, and double-click on the Mo useMo veand Paint events to generate the event handlers. Drag a T imer contro l onto the Form, and change the Nameproperty to gameT imer, the Interval property to 30 , and the Enabled property to T rue . With the Timer contro lselected, click the Properties Window Event s icon, and double-click the T ick property to generate the event

handler. Click to save your changes.

Adding the Code

Now that the UI is complete and we've created our event handlers, let's add the code. First, we'll add all o f theinterfaces and classes we need.

Use the table below to add an interface or class using Add Class. For interfaces, change the class keyword toint erf ace . Add a public access modifier to all o f the interfaces and classes. Also include the appropriateinheritance or implementation code using the co lon (:) operator after the interface or class name. When you

finish, click to save your changes.

NoteWhen coding an inheritance and an interface implementation to a class, the base class mustalways be placed first, as in public class Rect angleCapt ureShape : Shape, ICaptureShape,where Shape is the base class.

Class/Int erf ace Name T ype Inherit ance/Implement at io n

ICo llector Interface

IShape Interface

ICaptureShape Interface IShape

Shape Abstract class IShape

RectangularCaptureShape Class Shape, ICaptureShape

EllipseCaptureShape Class Shape, ICaptureShape

Collector Class ICollector

CaptureGame Class

Now we'll start coding our interfaces and classes.

Modify the code in the ICo llect o r.cs interface as shown:

Page 192: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public interface ICollector { Size Dimensions { get; set; } Point Location { get; set; } Color FillColor { get; set; } void Draw(Graphics graphics); int Collected { get; } int CollectedPoints { get; } void Collect(ICaptureShape collectorShape, Random random, Size boardSize); void Reset(); }}

Click to save your changes.

The ICo llector interface contains a number o f properties and a method. The interface also includes propertiesthat tabulate the number o f shapes that have been co llected, the to tal number o f po ints from items that havebeen co llected, a Collect method to enable the Collector to co llect a shape, and a method to reset theCollector. Let's add the IShape interface now.

Modify the code in the IShape.cs interface as shown:

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public interface IShape { Size Dimensions { get; set; } Point Location { get; set; } Color FillColor { get; set; } void Draw(Graphics graphics); bool Hit(Point location, Size dimensions); void Animate(Size boardSize); }}

Click to save your changes.

Again we see properties and a method for creating shapes, detecting a co llision, and a method to move oranimate a shape. Now let's add the ICaptureShape interface inherited from IShape.

NoteEven though a class implements an interface, when an interface inherits from a base interface,the derived interface is not implementing the base interface because, by definition, interfaceshave no implementation.

Modify the code in the ICapt ureShape.cs interface as shown:

Page 193: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public interface ICaptureShape : IShape { int Points { get; set; } void OnCollected(Random random, Size boardSize); }}

Click to save your changes.

The ICaptureShape interface that is inheriting from IShape, includes only a property indicating how manypoints the shape is worth, and a method to be called when the shape has been co llected. Now let's code theShape abstract class.

Modify the code in Shape.cs abstract class as shown:

Page 194: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public abstract class Shape : IShape { private enum StartingPositions { Top, Bottom, Left, Right } private StartingPositions _startPosition; private Point _location; public Point Location { get { return _location; } set { _location = value; } } private Size _dimensions; public Size Dimensions { get { return _dimensions; } set { _dimensions = value; } } private Color _fillColor; public Color FillColor { get { return _fillColor; } set { _fillColor = value; } } public Shape(Color color) { _fillColor = color; } public bool Hit(Point location, Size dimensions) { // Check rectangle 1 against rectangle 2 to detect a collision checking left, right, // top, then bottom to see if any way they collide, then negate result Rectangle r1 = new Rectangle(location, dimensions); Rectangle r2 = new Rectangle(_location, _dimensions); return !(r1.X + r1.Width < r2.X || r1.Y + r1.Height < r2.Y || r1.X > r2.X + r2.Width || r1.Y > r2.Y + r2.Height); } public void Animate(Size boardSize) { switch (_startPosition) { case Shape.StartingPositions.Top: _location.Y++; if (_location.Y >= boardSize.Height) _location.Y = 0 - _dimensions.Height; break; case Shape.StartingPositions.Bottom: _location.Y--; if (_location.Y + _dimensions.Height <= 0) _location.Y = boardSize.Height; break; case Shape.StartingPositions.Left: _location.X++; if (_location.X >= boardSize.Width) _location.X = 0 - _dimensions.Width; break; case Shape.StartingPositions.Right:

Page 195: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_location.X--; if (_location.X + _dimensions.Width <= 0) _location.X = boardSize.Width; break; } } public void Reset(Random random, Size boardSize) { // Generate random starting position var values = Enum.GetValues(typeof(StartingPositions)); int positionIndex = random.Next(values.Length); int counter = 0; foreach (var value in values) { if (counter++ == positionIndex) { _startPosition = (StartingPositions)value; break; } } // Based on starting position, generate actual random x or y switch (_startPosition) { case StartingPositions.Top: _location = new Point(random.Next(boardSize.Width - _dimensions.Width), 0); break; case StartingPositions.Bottom: _location = new Point(random.Next(boardSize.Width - _dimensions.Width), boardSize.Height); break; case StartingPositions.Left: _location = new Point(0, random.Next(boardSize.Height - _dimensions.Height)); break; case StartingPositions.Right: _location = new Point(boardSize.Width - _dimensions.Width, random.Next(boardSize.Height - _dimensions.Height)); break; } } public abstract void Draw(Graphics graphics); }}

Click to save your changes.

Let's discuss parts o f this code.

Page 196: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private enum StartingPositions { Top, Bottom, Left, Right }...public bool Hit(Point location, Size dimensions){ // Check rectangle 1 against rectangle 2 to detect a collision checking left, right, // top, then bottom to see if any way they collide, then negate result Rectangle r1 = new Rectangle(location, dimensions); Rectangle r2 = new Rectangle(_location, _dimensions); return !(r1.X + r1.Width < r2.X || r1.Y + r1.Height < r2.Y || r1.X > r2.X + r2.Width || r1.Y > r2.Y + r2.Height);}...public void Reset(Random random, Size boardSize){ // Generate random starting position var values = Enum.GetValues(typeof(StartingPositions)); int positionIndex = random.Next(values.Length); int counter = 0; foreach (var value in values) { if (counter++ == positionIndex) { _startPosition = (StartingPositions)value; break; } } // Based on starting position, generate actual random x or y switch (_startPosition) { case StartingPositions.Top: _location = new Point(random.Next(boardSize.Width - _dimensions.Width), 0); break; case StartingPositions.Bottom: _location = new Point(random.Next(boardSize.Width - _dimensions.Width), boardSize.Height); break; case StartingPositions.Left: _location = new Point(0, random.Next(boardSize.Height - _dimensions.Height)); break; case StartingPositions.Right: _location = new Point(boardSize.Width - _dimensions.Width, random.Next(boardSize.Height - _dimensions.Height)); break; }}...

In Shape.cs, we use an enum enumerated type St art ingPo sit io ns that contro ls both where a shape starts,and the direction the shape will move when animated. The Reset method randomly picks one the possibleSt art ingPo sit io ns, and then a random po int that co incides with the starting position. Reset includes asyntax we haven't seen before, var, instead o f a data type. The var keyword is used within methods to createa strongly typed variable with implicit type. The var syntax is convenient when you aren't sure what the datatype is. This syntax is particularly handy when you're working with enumerations. We're able to use the

Page 197: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Get Values method o f the Enum class that will generate an array o f St art ingPo sit io ns; we have all o f theenumerated constants in an array called values. Using this array, we can randomly select a starting position.

We also implement the Hit method. Previously, we were only concerned with detecting whether a po int waswithin a Rectangle, but now we want to know whether any po int within one rectangle is within anotherrectangle. There are a number o f ways to detect such an intersection (or co llision), but we're go ing to test tofind out whether any o f the sides o f one rectangle could possibly be within the o ther rectangle.

Let's code the RectangleCaptureShape and EllipseCapture shape classes next.

Modify the code in Rect angleCapt ureShape.cs class as shown:

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public class RectangleCaptureShape : Shape, ICaptureShape { public RectangleCaptureShape(Random random, Size dimensions, Size boardSize, Color color, int points) : base(color) { _points = points; base.Dimensions = dimensions; base.Reset(random, boardSize); } private int _points; public int Points { get { return _points; } set { _points = value; } } public override void Draw(Graphics graphics) { using (SolidBrush brush = new SolidBrush(base.FillColor)) graphics.FillRectangle(brush, new Rectangle(base.Location, base.Dimensions)); } public void OnCollected(Random random, Size boardSize) { base.Reset(random, boardSize); } }}

Click to save your changes.

Let's discuss this code.

Page 198: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public class RectangleCaptureShape : Shape, ICaptureShape { public RectangleCaptureShape(Random random, Size dimensions, Size boardSize, Color color, int points) : base(color) { _points = points; base.Dimensions = dimensions; base.Reset(random, boardSize); } private int _points; public int Points { get { return _points; } set { _points = value; } } public override void Draw(Graphics graphics) { using (SolidBrush brush = new SolidBrush(base.FillColor)) graphics.FillRectangle(brush, new Rectangle(base.Location, base.Dimensions)); } public void OnCollected(Random random, Size boardSize) { base.Reset(random, boardSize); } }}

In Rect angleCapt ureShape , we've highlighted the inheritance from Shape , and the implementation o fICapt ureShape . We've highlighted all o f the uses o f base , all referring to the Shape abstract class. TheDraw method is also from the Shape class, so it requires o verride keyword. The Po int s property andOnCo llect ed method are from the ICapt ureShape interface.

In this class (and in o ther classes) you'll see a Random parameter, and a boardSize parameter. We need toinclude the Random parameter because we will be using consecutive random numbers. The boardSizeparameter is the dimensions o f our drawing surface.

Note also the use o f the using keyword. This syntax is designed to create a block o f code with scope thatensures the proper disposal o f objects when they leave the scope. In our syntax, we're using a So lidBrushclass. If you look up the So lidBrush class, you'll see that this class includes a Dispose method, which isrequired by classes that implement the IDisposable interface. Whenever an object implements theIDisposable interface, use the using syntax to ensure the resources are properly disposed. Until we coveredinterfaces, we avo ided using this syntax except when dealing with files. We'll see using used in this tutorialfo r both So lidBrush and with Fo nt classes.

Next, we need to add the EllipseCaptureShape class. We won't discuss this class in detail, because it'salmost identical to the RectangleCaptureShape class.

Modify the code in EllipseCapt ureShape.cs class as shown:

Page 199: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public class EllipseCaptureShape : Shape, ICaptureShape { public EllipseCaptureShape(Random random, Size dimensions, Size boardSize, Color color, int points) : base(color) { _points = points; base.Dimensions = dimensions; base.Reset(random, boardSize); } private int _points; public int Points { get { return _points; } set { _points = value; } } public override void Draw(Graphics graphics) { using (SolidBrush brush = new SolidBrush(base.FillColor)) graphics.FillEllipse(brush, new Rectangle(base.Location, base.Dimensions)); } public void OnCollected(Random random, Size boardSize) { base.Reset(random, boardSize); } }}

Click to save your changes, and to run the program.

Next, let's add the Collector class.

Modify Co llect o r.cs as shown:

Page 200: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public class Collector : ICollector { private Point _location; public Point Location { get { return _location; } set { _location = value; } } private Size _dimensions; public Size Dimensions { get { return _dimensions; } set { _dimensions = value; } } private Color _fillColor; public Color FillColor { get { return _fillColor; } set { _fillColor = value; } } private int _collected; public int Collected { get { return _collected; } } private int _collectedPoints; public int CollectedPoints { get { return _collectedPoints; } } public Collector(Color color, Point location, Size dimensions) { _fillColor = color; _location = location; _dimensions = dimensions; } public void Collect(ICaptureShape collectorShape, Random random, Size boardSize) { _collected++; _collectedPoints += collectorShape.Points; collectorShape.OnCollected(random, boardSize); } public void Reset() { _collected = 0; _collectedPoints = 0; } public void Draw(Graphics graphics) { using (SolidBrush brush = new SolidBrush(_fillColor)) graphics.FillRectangle(brush, new Rectangle(_location, _dimensions)); } }}

Page 201: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click to save your changes, and to run the program.

You'll probably be able to determine the purpose o f most o f the properties and methods by their names. Let'slook at the Collect method:

OBSERVE:

.

.

.public void Collect(ICaptureShape collectorShape, Random random, Size boardSize){ _collected++; _collectedPoints += collectorShape.Points; collectorShape.OnCollected(random, boardSize);}...

The Co llect method is used to co llect shapes, first incrementing a few counters, then calling the OnCo llectmethod co llect o rShape , a ICapt ureShape object.

Next, let's implement our last class, the CaptureGame class.

Modify Capt ureGame.cs as shown:

Page 202: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public class CaptureGame { private Size _boardSize; public Size BoardSize { set { _boardSize = value; } } private Random _random = new Random(); private Collector _collector; private ICaptureShape[] _captureShapes; public int CollectorHits { get { return _collector.Collected; } } public int CollectorPoints { get { return _collector.CollectedPoints; } } public Point CollectorPosition { set { value.X -= _collector.Dimensions.Width; value.Y -= _collector.Dimensions.Height; _collector.Location = value; } } public CaptureGame(int collectorShapeCount, Size boardSize) { _boardSize = boardSize; _captureShapes = new ICaptureShape[collectorShapeCount]; _collector = new Collector(Color.Red, new Point(0,0), new Size(30, 30)); for (int i = 0; i < _captureShapes.Length; i++) _captureShapes[i] = new EllipseCaptureShape(_random, new Size(20, 20), _boardSize, Color.Green, 5); } public void DrawCollectorShapes(Graphics graphics) { // foreach in C# is read-only (immutable) foreach (ICaptureShape collectorShape in _captureShapes) collectorShape.Draw(graphics); } public void AnimateCollectorShapes() { for (int i = 0; i < _captureShapes.Length; i++) { _captureShapes[i].Animate(_boardSize); if (_captureShapes[i].Hit(_collector.Location, _collector.Dimensions)) _collector.Collect(_captureShapes[i], _random, _boardSize); } } public void DrawCollector(Graphics graphics) { _collector.Draw(graphics); } public void Reset() {

Page 203: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_collector.Reset(); } }}

Click to save your changes.

Let's discuss this code.

Page 204: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace ShapeCapture{ public class CaptureGame { private Size _boardSize; public Size BoardSize { set { _boardSize = value; } } private Random _random = new Random(); private Collector _collector; private ICaptureShape[] _captureShapes; public int CollectorHits { get { return _collector.Collected; } } public int CollectorPoints { get { return _collector.CollectedPoints; } } public Point CollectorPosition { set { value.X -= _collector.Dimensions.Width; value.Y -= _collector.Dimensions.Height; _collector.Location = value; } } public CaptureGame(int collectorShapeCount, Size boardSize) { _boardSize = boardSize; _captureShapes = new ICaptureShape[collectorShapeCount]; _collector = new Collector(Color.Red, new Point(0,0), new Size(30, 30)); for (int i = 0; i < _captureShapes.Length; i++) _captureShapes[i] = new EllipseCaptureShape(_random, new Size(20, 20), _boardSize, Color.Green, 5); } public void DrawCollectorShapes(Graphics graphics) { // foreach in C# is read-only (immutable) foreach (ICaptureShape collectorShape in _captureShapes) collectorShape.Draw(graphics); } public void AnimateCollectorShapes() { for (int i = 0; i < _captureShapes.Length; i++) { _captureShapes[i].Animate(_boardSize); if (_captureShapes[i].Hit(_collector.Location, _collector.Dimensions)) _collector.Collect(_captureShapes[i], _random, _boardSize); } } public void DrawCollector(Graphics graphics) { _collector.Draw(graphics); } public void Reset() {

Page 205: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_collector.Reset(); } }}

The Capt ureShape class includes a Co llect o r private instance variable _co llect o r that is usedthroughout this class, as the Collector object. We've created several methods in the Capt ureShape classthat facilitate interaction with the Co llect o r class. The Capt ureShape class also includes a private classarray variable _capt ureShapes o f ICapt ureShape objects. This array is unique as it is an array o f interfaceimplementations o f the ICapt ureShape interface, which means that any object we store in this array mustimplement the ICapt ureShape interface. Since this interface inherits from IShape, we're able to access notonly implementations o f the ICapt ureShape interface, but implementations o f the IShape interface as well.

There is a feature o f the Capt ureShape class, the f o reach looping structure located in theDrawCo llect o rShapes method that we'll compare to the f o r loop syntax in theAnimat eCo llect o rShapes method. In C#, the f o reach creates read-only instance variables o f the items inan array (or co llection), so when you need to modify the contents o f an array (or co llection), you must use analternative looping structure, such as the f o r loop.

Note When an object is read-only, like a string or a foreach instance variable, that object is said to beimmutable.

The Capt ureShape class encapsulates the complete functionality o f the Shape Capture game. This isimportant as we get ready to update the Form to complete the coding. In earlier examples we've had a lo t o fcode mixed into our Form methods (event handlers and private methods). Now the Form event handlersmake calls into Capt ureShape , which simplifies the Form code, and encapsulates the functionality o f ourgame from the events o f our Form even more efficiently. Now, if we need to make a change to the game, wecan probably make that change without having to modify any Form code. Let's add the Form code now.

Modify MainFo rm.cs as shown:

Page 206: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;

namespace ShapeCapture{ public partial class MainForm : Form { CaptureGame _captureGame; // Collector game object private bool _playGame = true; // Play/pause toggle private int _maxShapes = 10; // Default maximum number of shapes public MainForm() { InitializeComponent(); } private void mainPictureBox_MouseMove(object sender, MouseEventArgs e) { if (_playGame) _captureGame.CollectorPosition = new Point(e.X, e.Y); } private void mainPictureBox_Paint(object sender, PaintEventArgs e) { _captureGame.DrawCollector(e.Graphics); _captureGame.DrawCollectorShapes(e.Graphics); string gameStatus = "Hits: " + _captureGame.CollectorHits + " - Points: " + _captureGame.CollectorPoints; using (Font font = new Font("Arial", 12, FontStyle.Bold)) using (SolidBrush brush = new SolidBrush(Color.Black)) { e.Graphics.DrawString(gameStatus, font, brush, new Point(5, 5)); } gameTimer.Enabled = true; } private void pauseToolStripMenuItem_Click(object sender, EventArgs e) { _playGame = !_playGame; pauseToolStripMenuItem.Checked = !_playGame; } private void MainForm_Shown(object sender, EventArgs e) { _captureGame = new CaptureGame(_maxShapes, mainPictureBox.ClientSize); } private void gameTimer_Tick(object sender, EventArgs e) { if (_playGame) { gameTimer.Enabled = false; _captureGame.AnimateCollectorShapes(); mainPictureBox.Invalidate(); } } private void resetToolStripMenuItem_Click(object sender, EventArgs e) {

Page 207: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_captureGame.Reset(); } private void MainForm_Resize(object sender, EventArgs e) { _captureGame.BoardSize = mainPictureBox.ClientSize; } }}

and to run the program. The ShapeCapture game will now run, and you can capture shapes!

Let's discuss this code.

Page 208: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;

namespace ShapeCapture{ public partial class MainForm : Form { CaptureGame _captureGame; // Collector game object private bool _playGame = true; // Play/pause toggle private int _maxShapes = 10; // Default maximum number of shapes public MainForm() { InitializeComponent(); } private void mainPictureBox_MouseMove(object sender, MouseEventArgs e) { if (_playGame) _captureGame.CollectorPosition = new Point(e.X, e.Y); } private void mainPictureBox_Paint(object sender, PaintEventArgs e) { _captureGame.DrawCollector(e.Graphics); _captureGame.DrawCollectorShapes(e.Graphics); string gameStatus = "Hits: " + _captureGame.CollectorHits + " - Points: " + _captureGame.CollectorPoints; using (Font font = new Font("Arial", 12, FontStyle.Bold)) using (SolidBrush brush = new SolidBrush(Color.Black)) { e.Graphics.DrawString(gameStatus, font, brush, new Point(5, 5)); } gameTimer.Enabled = true; } private void pauseToolStripMenuItem_Click(object sender, EventArgs e) { _playGame = !_playGame; pauseToolStripMenuItem.Checked = !_playGame; } private void MainForm_Shown(object sender, EventArgs e) { _captureGame = new CaptureGame(_maxShapes, mainPictureBox.ClientSize); } private void gameTimer_Tick(object sender, EventArgs e) { if (_playGame) { gameTimer.Enabled = false; _captureGame.AnimateCollectorShapes(); mainPictureBox.Invalidate(); } } private void resetToolStripMenuItem_Click(object sender, EventArgs e) {

Page 209: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_captureGame.Reset(); } private void MainForm_Resize(object sender, EventArgs e) { _captureGame.BoardSize = mainPictureBox.ClientSize; } }}

It's pretty amazing how we can add just a few lines o f code to the Form, and get such significant results. Wecreate a Capt ureGame instance varaible _capt ureGame that we use throughout the Form to interact withthe game class. We instantiate the _capt ureGame game object in the Form Shown event, update theposition o f Contro ller object in the MouseMove event, then wait fo r Timer Tick events to animate the CaptureShape objects and call the PictureBox Invalidat e method to raise the Paint event. The PictureBox Paint eventredraws the Contro ller and Capture Shape objects, and draws the game score on the PictureBox contro l.

In the Timer Tick event handler, and in the PictureBox Paint event handler, we disable and re-enable the Timer.Why? Primarily to prevent the Timer from raising another Tick event when we're already handling a current Tickevent, and to make debugging the code easier. Another reason for the disabling and re-enabling o f the Timeris to introduce the possibility o f a conflict, while updating the Collector and Capture Shape objects, whichcould happen if our code was multi-threaded. The Timer contro l we're using runs within the process, orthread, o f the user interface. Because there is only a single thread, we don't have to worry about the possibilityo f executing the same code at the same time. Still, you'll want to consider tht possibility fo r the future; we'llcover multi-threading in a future lesson.

In the PictureBox Paint method, we've illustrated the preferred way to nest using statements. Typically, you'dexpect to see two sets o f curly brackets block operators with two using statements, but the method we'veillustrated is correct: stacking the using statements, and using a single set o f curly braces.

We use a boo lean variable _playGame which we toggle from the Pause menu item event handler todetermine whether the game is being played or paused. We are able to change the playing board game sizein the Form Resize event handler.

So, that was your first adventure with C# interfaces! I think you're really getting the hang o f this stuff. Goodwork! See you in the next lesson...

Before you move on to the next lesson, make sure to do any homework, pro jects, quizzes and such.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 210: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Delegates and Events

We've handled numerous events in previous lessons, but almost all o f them were provided by C# and .NET. In this lesson we'lllearn about delegates and how we can create our own custom events.

This lesson includes these topics:

DelegatesEventsBalloons - A Coding Tutorial

Delegates

What is a Delegate?

A C# delegate is a type that references a method. When we define a delegate, we're defining a type that maybe used to declare o ther variables. Once defined, a delegate variable may be used just as we would use anyother data type, such as an int, string, or a class. Also, because a delegate references a method, delegatevariables may be used just as we would use a method. When defining a delegate type, we declare thedelegate and specify a return type and any parameters, effectively creating a type that references a methodsignature. Let's take a look at an example.

OBSERVE:

// Define a delegate with signature: no parameters, void return private delegate void SimpleDelegate();

Examining this definition o f delegate type, you may notice that if we remove the delegat e keyword, thedefinition looks exactly like a method definition. Creating a delegate requires a method signature. For ourdelegate, our signature has no parameters, and no return type (or you could state that the return type is vo id).The new type-safe delegate type is SimpleDelegat e . Why type-safe? Because we created theSimpleDelegat e delegate type to reference methods that have a signature o f no parameters and a vo idreturn type, SimpleDelegat e may only be used with methods that have this signature. Any attempt to useSimpleDelegat e with methods with a different signature will result in a compiler error. We would useSimpleDelegat e just as we would any o ther type. Let's look at an example o f using this delegate.

Tip

A delegate may be defined outside a class, effectively creating a new global delegate type just aswe would create a class, or it may be defined within a class, creating a new delegate type that istied to the class, but may be used according to the access modifier applied to the delegate typedefinition. In our code, our delegate definition is defined within the Form1 class, marked private. Ifwe want to create the delegate definition outside o f a class, the access modifier may not be privateor pro tected.

Working with Delegates

Select File | New | Pro ject and change the Name of the pro ject to Delegat es. Modify Fo rm1.cs as shown:

Page 211: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Define a delegate with signature: no parameters, void return private delegate void SimpleDelegate();

// Define a delegate with signature: int parameter, int returnprivate delegate void MulticastDelegate(int aNumber); // Private class variableprivate int _total;

public Form1(){ InitializeComponent(); Delegates();}

// Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = new SimpleDelegate(SimpleMethod);}

private void SimpleMethod(){ _total++;}...

Click to save your changes, and to run the program.

Page 212: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

// Define a delegate with signature: no parameters, void return private delegate void SimpleDelegate();

// Define a delegate with signature: int parameter, int returnprivate delegate void MulticastDelegate(int aNumber); // Private class variableprivate int _total; public Form1(){ InitializeComponent(); Delegates();} // Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = new SimpleDelegate(SimpleMethod);} private void SimpleMethod(){ _total++;}...

We use our delegate type SimpleDelegat e to create an instance o f the delegate,simpleMet ho dDelegat e , using the new keyword, and passing a compatible method name, in this caseSimpleMet ho d, as a parameter to the delegate constructor. Now we have an immutable reference to theSimpleMet ho d method that we can reference using the simpleMet ho dDelegat e delegate referencevariable. Why immutable? Delegate reference instance variables, such as simpleMet ho dDelegat e , refer tothe method used when creating them, and no o ther method. Let's next see how we would use a delegateinstance variable.

Modify the Fo rm1.cs class code in the lesson test pro ject as shown:

CODE TO TYPE:

.

.

.// Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = new SimpleDelegate(SimpleMethod); // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1}...

Page 213: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click to save your changes, and to run the program.

OBSERVE:

.

.

. // Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = new SimpleDelegate(SimpleMethod); // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1}...

You can see that in our code we've treated the simpleMet ho dDelegat e variable as a method, adding theparantheses. The backing or reference method SimpleMet ho d is called, adding one to the class variable_t o t al. We could have called the method directly instead o f using a delegate, but I want you to get used tousing delegates as well.

Creating a delegate instance is so common that C# o ffers a shortcut method, illustrated in the next bit o f codewhere we replace the original syntax with the shortcut syntax.

Modify Fo rm1.cs as shown:

CODE TO TYPE:

.

.

.// Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = new SimpleDelegate(SimpleMethod); SimpleDelegate simpleMethodDelegate = SimpleMethod; // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1}...

Click to save your changes, and to run the program.

Page 214: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

// Delegate test method... private void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = SimpleMethod; // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1}...

Using the shortcut syntax we can omit the new keyword and the delegate type, and simply assign a methodname to a delegate instance variable. We'll use this syntax throughout this lesson when creating delegateinstance variables. Let's expand our code to define another delegate instance variable.

Modify Fo rm1.cs as shown:

CODE TO TYPE:

.

.

.// Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = SimpleMethod; // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1 // Create another instance of SimpleDelegate referencing AnotherSimpleMethod() SimpleDelegate anotherSimpleMethodDelegate = AnotherSimpleMethod; // Use delegate instance to effectively call AnotherSimpleMethod() anotherSimpleMethodDelegate(); Console.WriteLine(_total); // Output: 0}

private void AnotherSimpleMethod(){ _total--;}...

Click to save your changes, and to run the program.

Page 215: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. // Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = SimpleMethod; // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1

// Create another instance of SimpleDelegate referencing AnotherSimpleMethod() SimpleDelegate anotherSimpleMethodDelegate = AnotherSimpleMethod; // Use delegate instance to effectively call AnotherSimpleMethod() anotherSimpleMethodDelegate(); Console.WriteLine(_total); // Output: 0}

private void AnotherSimpleMethod(){ _total--;}...

We've added a second SimpleDelegat e instance variable ano t herSimpleMet ho dDelegat e that refersto a second method Ano t herSimpleMet ho d. Let's complete our code by creating two new delegates—onethat references methods with a signature with no parameters, but an int return type, and another thatreferences methods methods with a single int parameter and an int return type.

Modify Fo rm1.cs as shown:

Page 216: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Define a delegate with signature: int parameter, void returnpublic delegate void SimpleDelegateWithParameter(int number);

// Define a delegate with signature: int parameter, void returnpublic delegate int SimpleDelegateWithParameterWithReturn(int number);

// Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = SimpleMethod; // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1 // Create another instance of SimpleDelegate referencing AnotherSimpleMethod() SimpleDelegate anotherSimpleMethodDelegate = AnotherSimpleMethod; // Use delegate instance to effectively call AnotherSimpleMethod() anotherSimpleMethodDelegate(); Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegateWithParameter referencing ParameterMethod() SimpleDelegateWithParameter simpleDelegateWithParameter = ParameterMethod; // Use delegate instance to effectively call ParameterMethod() simpleDelegateWithParameter(4); Console.WriteLine(_total); // Output: 4 // Create an instance of SimpleDelegateWithParameter referencing ParameterMethod() SimpleDelegateWithParameterWithReturn simpleDelegateWithParameterWithReturn = ParameterMethodWithReturn; // Use delegate instance to effectively call ParameterMethodWithReturn() int result = simpleDelegateWithParameterWithReturn(4); Console.WriteLine(_total); // Output: 0}

private void ParameterMethod(int aNumber){ _total += aNumber;} private int ParameterMethodWithReturn(int aNumber){ return _total -= aNumber;}...

Click to save your changes, and to run the program.

Let's discuss this code.

Page 217: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. // Define a delegate with signature: int parameter, void returnpublic delegate void SimpleDelegateWithParameter(int number);

// Define a delegate with signature: int parameter, void returnpublic delegate int SimpleDelegateWithParameterWithReturn(int number);

// Delegate test methodprivate void Delegates(){ Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegate referencing SimpleMethod() SimpleDelegate simpleMethodDelegate = SimpleMethod; // Use delegate instance to effectively call SimpleMethod() simpleMethodDelegate(); Console.WriteLine(_total); // Output: 1 // Create another instance of SimpleDelegate referencing AnotherSimpleMethod() SimpleDelegate anotherSimpleMethodDelegate = AnotherSimpleMethod; // Use delegate instance to effectively call AnotherSimpleMethod() anotherSimpleMethodDelegate(); Console.WriteLine(_total); // Output: 0 // Create an instance of SimpleDelegateWithParameter referencing ParameterMethod() SimpleDelegateWithParameter simpleDelegateWithParameter = ParameterMethod; // Use delegate instance to effectively call ParameterMethod() simpleDelegateWithParameter(4); Console.WriteLine(_total); // Output: 4 // Create an instance of SimpleDelegateWithParameter referencing ParameterMethod() SimpleDelegateWithParameterWithReturn simpleDelegateWithParameterWithReturn = ParameterMethodWithReturn; // Use delegate instance to effectively call ParameterMethodWithReturn() int result = simpleDelegateWithParameterWithReturn(4); Console.WriteLine(_total); // Output: 0}

private void ParameterMethod(int aNumber){ _total += aNumber;}

private int ParameterMethodWithReturn(int aNumber){ return _total -= aNumber;}...

We added two more delegate definitions, SimpleDelegat eWit hParamet er andSimpleDelegat eWit hParamet erWit hRet urn, that add delegate types to reference two additional methodsignatures: one with an int parameter and vo id return type, and a second with an int parameter and intreturn type. Then we fo llow the same pattern and create delegate instance variables, and use those instancevariables to call the associated methods.

Page 218: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Anonymous Delegate Methods

When creating a delegate instance, rather than use an existing method, we can create a method on-the-fly,using an anonymous delegate method. Let's re-use some of our code to create and use an anonymousdelegate method.

Modify the Fo rm1.cs class code in the lesson test pro ject as shown:

CODE TO TYPE:

.

.

.// Define an anonymous delegate methodprivate SimpleDelegateWithParameterWithReturn anonymousMethodDelegate = delegate(int aNumber){ return aNumber * 2;};

public Form1(){ InitializeComponent(); Delegates(); AnonymousDelegates();}

private void AnonymousDelegates(){ // Use delegate instance to effectively call anonymous method Console.WriteLine(anonymousMethodDelegate(6)); // Output: 12}...

Click to save your changes, and to run the program.

Let's discuss this code.

Page 219: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. // Define an anonymous delegate methodprivate SimpleDelegateWithParameterWithReturn anonymousMethodDelegate = delegate(int aNumber){ return aNumber * 2;};

public Form1(){ InitializeComponent(); Delegates(); AnonymousDelegates();}

private void AnonymousDelegates(){ // Use delegate instance to effectively call anonymous method Console.WriteLine(anonymousMethodDelegate(6)); // Output: 12}...

We're using the delegate definition SimpleDelegat eWit hParamet erWit hRet urn (single int parameter,int return type) to create a delegate class level variable ano nymo usMet ho dDelegat e .ano nymo usMet ho dDelegat e is a delegate instance variable that defines an anonymous method ratherthan using an existing method using the delegat e keyword. When defining an anonymous delegate method,we include the parameters that must match the delegate type, in this caseSimpleDelegat eWit hParamet erWit hRet urn. This delegate type also requires the method to return anint (which our anonymous method does). Now, we can use ano nymo usMet ho dDelegat e just like amethod.

Multicast Delegate Instances

As we've created delegate instances, we've assigned to them a single method. All delegates supportreferencing multiple methods. When a delegate instance references more than a single method, that delegateinstance is referred to as a mult icast delegate. When called, a multicast delegate instance will call all o f thereferenced methods. Let's see an example.

Modify the Fo rm1.cs class code in the lesson test pro ject as shown:

Page 220: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Multicast delegateprivate MulticastDelegate multicastDelegate;

public Form1(){ InitializeComponent(); Delegates(); AnonymousDelegates(); MulticastDelegates();}

private void MulticastDelegates(){ // Add methods to multicast delegate _total = 5; multicastDelegate += Add; multicastDelegate += Multiply; // Call multicast delegate, effectively calling all referenced methods multicastDelegate(2); Console.WriteLine(_total); // Output: 14 // Remove a referenced method and call multicast delegate _total = 5; multicastDelegate -= Multiply; multicastDelegate(2); Console.WriteLine(_total); // Output: 7}

private void Add(int aNumber){ _total += aNumber;}

private void Multiply(int aNumber){ _total *= aNumber;}...

Click to save your changes, and to run the program.

Let's discuss this code.

Page 221: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

// Multicast delegateprivate MulticastDelegate multicastDelegate;

public Form1(){ InitializeComponent(); Delegates(); AnonymousDelegates(); MulticastDelegates();}

private void MulticastDelegates(){ // Add methods to multicast delegate _total = 5; multicastDelegate += Add; multicastDelegate += Multiply; // Call multicast delegate, effectively calling all referenced methods multicastDelegate(2); Console.WriteLine(_total); // Output: 14 // Remove a referenced method and call multicast delegate _total = 5; multicastDelegate -= Multiply; multicastDelegate(2); Console.WriteLine(_total); // Output: 7}

private void Add(int aNumber){ _total += aNumber;}

private void Multiply(int aNumber){ _total *= aNumber;}

We define the delegate Mult icast Delegat e (single int parameter, vo id return type), then create a class leveldelegate instance o f Mult icast Delegat e called mult icast Delegat e . Then we use the += additionassignment operator to add references to the Add and Mult iply methods to the mult icast Delegat edelegate instance. When we call mult icast Delegat e , we effectively call each method that is referencedusing mult icast Delegat e , passing 2 as the parameter. Then we demonstrate that we can use the -+subtraction assignment operator to remove a method reference from mult icast Delegat e . When we callmult icast Delegat e again, we see that the Mult iply method is no longer called.

We used a delegate without a return type in the signatureintentionally . You can use delegates with a returntype as part o f the referenced method signatures, but do not use any returned value from a multicast delegate.When a multicast delegate calls each method, the order is indeterminate (inconsistent), so you would notknow which method returned the value.

Why Delegates?

The ability to create a delegate that references a method signature cana be really useful. When you're able tocreate a reference to a method, you're providing the ability fo r so ftware to register a callback, o r a methodthat should be called under specific circumstances. Callbacks are the backbone o f events, that's how comedelegates are used with C# events. Multicast delegates also allow us to chain methods to be called underspecific circumstances. The inherent ability o f a delegate to reference a method with a specific signature notonly provides type-safety, it facilitates writing different so ftware components that can agree upon a requiredmethod signature, without knowing anything more about the calling program, or the called method. This inturn assists in further decoupling software objects and prevention o f interdependence among softwareelements. Software element interdependence leads to code that is difficult to maintain.

Page 222: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

So, let's move on to events.

Events

Events are delegates

We've used the Properties Window Event s icon to create a method that handles Form and contro l events. Insome of our recent lessons, we also needed a way to enable a single event method to be called frommultiple contro ls. Do you remember the format we used? Here's an example:

OBSERVE:

button.Click += new EventHandler(buttonClickHandler);

This syntax represents adding a callback method to a multicast delegate using the addition assignmentoperator. In the code, we're adding the method but t o nClickHandler to handle the Click event fo r a Buttoncontro l. Event Handler must be a delegate type definition. Studio provides a wizard that helps generate eventhandlers using delegates, prompting us to press the T ab key to auto-generate both the delegate instanceand the referenced event callback method. but t o nClickHandler is one o f many defined delegate types usedwith events.

NoteEven though the auto-generated event uses the longer version for creating delegate instancevariables, we can also use the shortcut syntax. Although one nice feature o f the longer versionis that you can hover the mouse over the delegate type and Studio Intellisense will pop up thedelegate type signature.

.NET Events

Do you remember the signature o f the but t o nClickHandler method that was auto-generated by Studio?

OBSERVE:

private void buttonClickHandler(object sender, EventArgs e)

The but t o nClickHandler method signature has a return type o f vo id, which makes sense for a multicastdelegate and two parameters: o bject and Event Args. We've seen the o bject class before, and we knowthat it is the foundation o f all classes and the root o f the type hierarchy. By using this most fundamental o fclasses, .NET events are capable o f supporting any class derived from o bject . Event Args is a class that isassociated with the Event Handler event delegate. This delegate is used when there is no event dataassociated with an event. Events with event data are associated with different contro ls, use different delegatesand event data classes to ensure type-safe implementation, and provide event data necessary for the eventbeing handled. For example, a MouseClick event requires the MouseEventHandler delegate and uses theMouseEventArgs for event data.

In this lesson's coding tutorial, we'll use both delegates and .NET event delegates. We'll also create our ownevent data class to be used with our own event. Because we'll be deriving our custom event from .NETevents, we'll need to use signatures that are compatible with .NET events.

Subscriber and Publisher

Before we move on to the coding tutorial, consider this concept related to events: subscriber and publisher.Contro ls we add that generate events are called publishers: they publish or broadcast events. Rather than justbroadcast events to anyone and everyone, .NET broadcasts to event subscribers: if you want to be notified o fan event, you must subscribe. When we use a delegate as a multicast delegate and add methods to thedelegate instance, we are essentially adding subscribers. As you learn to work with delegates and events,consider what is publishing and what is subscribing; it can help you keep track o f which side o f thesubscriber-publisher "fence" you're occupying.

Okay, on to the coding tutorial!

Balloons - A Coding TutorialIn this coding tutorial, we'll create a program that displays animated (moving) balloons. At first a single balloon will bedisplayed, and as the balloon rises, it expands, just as a real balloon does in our atmosphere. Eventually, the balloon

Page 223: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

displayed, and as the balloon rises, it expands, just as a real balloon does in our atmosphere. Eventually, the balloonwill rise too high, and pop. When a balloon pops, the number o f balloons is incremented by one, and then thoseballoons begin to rise. Each balloon that's added is given a random lift rate and expansion rate. Each balloon isclickable so you can view information about the selected balloon.

In addition to learning about delegates and events, we're also go ing to learn more about how to contro l the speed o fan animated program independent o f a Timer contro l.

User Interface Prototypes

Below are images o f the Balloons program interface elements, including the UI in the Design Editor (withmenus), and the running program UI with two balloons. The red balloon in selected, and the green balloon isunselected:

Design Edit o r UI

Design Edit o r UI - File Menu

Page 224: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Design Edit o r UI - T ime Int erval Menu

Design Edit o r UI - Game FPS Menu

Runt ime UI Wit h Ballo o n Select ed

Page 225: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating the User Interface

Let's start creating our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to Ballo o ns and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to Ballo o ns.

Also , change the StartPosition property to Cent erScreen. Click to save your changes.

Now let's add the contro ls we'll need.

Use this table to add and name the main contro ls:

Co nt ro l T ype Co nt ro l Name

MenuStrip mainMenuStrip

StatusStrip mainStatusStrip

PictureBox mainPictureBox

Timer gameTimer

Set the PictureBox to Do ck in Parent Co nt ainer, and the BorderStyle to Fixed3D. Set the Timer properties

Enabled to T rue and the Interval to 5 milliseconds. Click .

Next, let's add three Too lStripStatusLabels to the StatusStrip, and format the labels to enhance the visibility o feach label.

Use the dropdown menu on the StatusStrip to add three StatusLabel contro ls. Name the contro ls, in orderfrom left to right: t imerFpsT o o lSt ripSt at usLabel, ballo o nCo unt T o o lSt ripSt at usLabel, andselect edBallo o nT o o lSt ripSt at usLabel. Set the Text property o f the first two StatusLabel contro ls to ...,the BorderSides property to Right , and the BorderStyle property to Et ched. Set the Text property o f the last

StatusLabel to No ballo o n select ed. Click to save your changes.

Next, let's add the menu items.

Use the table below to create the menu items displayed in the Design Editor UI. Click to save yourchanges:

Parent Menu Menu T ext Name Pro pert y

&File fileToo lStripMenuItem

&Settings settingsToolStripMenuItem

File E&xit exitToo lStripMenuItem

Settings &Timer Interval intervalToo lStripMenuItem

Settings &Game FPS gameFPSToolStripMenuItem

Timer Interval 5 interval5ToolStripMenuItem

Timer Interval 10 interval10ToolStripMenuItem

Timer Interval 15 interval15ToolStripMenuItem

Timer Interval 20 interval20ToolStripMenuItem

Timer Interval 30 interval30ToolStripMenuItem

Timer Interval 45 interval45ToolStripMenuItem

Timer Interval 60 interval60ToolStripMenuItem

Game FPS 15 fps15ToolStripMenuItem

Game FPS 20 fps20ToolStripMenuItem

Game FPS 30 fps30ToolStripMenuItem

Game FPS 45 fps45ToolStripMenuItem

Page 226: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Game FPS 60 fps160ToolStripMenuItem

Now, we'll add the event handler C# code for the Exit menu option. We'll add event handlers for the o thermenu items dynamically using code later.

Use the mouse to open the File menu, and double-click the Exit menu item to generate the Exit eventhandler fo r this menu item. Add the code shown below to the Exit event handler:

CODE TO TYPE:

.

.

. private void exitToolStripMenuItem_Click(object sender, EventArgs e){ // Exit the application this.Close();}...

Click to save your changes.

Let's add a few other event handlers we'll need.

Create Form and contro l event handlers listed in the table below using the Properties Window Event icon:

Fo rm/Co nt ro l Event Event Handler

MainForm Shown MainForm_Shown

MainForm Resize MainForm_Resize

mainPictureBox MouseUp mainPictureBox_MouseUp

mainPictureBox Paint mainPictureBox_Paint

gameFPSToolStripMenuItem Click gameFPSToolStripMenuItem_Click

intervalToo lStripMenuItem Click intervalToo lStripMenuItem_Click

gameTimer Tick gameTimer_Tick

Click to save your changes.

Adding the Code

Now that we have all o f the UI elements added, we can start coding. Let's start by creating a Balloon class thatwill represent the moving balloon objects.

Right-click Ballo o ns in the So lution Explorer, select Add | Class, enter Ballo o n in the Name textbox, andclick Add. Modify the Ballo o n.cs class code as shown:

Page 227: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Balloons{ // Balloon info struct used for custom event arguments public struct BalloonInfo { public int GrowthRate; public int LiftSpeed; public int MaxSize; public Point Location; public Size Dimensions; public Color FillColor; }

public class Balloon { // Private class properties private int _growthCount = 0; private int _growthRate; private int _maxSize; private int _liftSpeed; private int _tailLength; private Point _location; private Size _dimensions; private Color _fillColor; // Public properties public Color FillColor { set { _fillColor = value; } } // Constructor public Balloon(Point location, Size dimensions, Color fillColor, int growthRate, int liftSpeed) { _location = location; _dimensions = dimensions; _fillColor = fillColor; _maxSize = dimensions.Height * 2; _growthRate = growthRate; _liftSpeed = liftSpeed; _tailLength = _dimensions.Height * 2; } // Public methods public void DrawAndAnimate(bool animate, Size boardSize, Graphics graphics) { if (animate) { // Move and enlarge balloon based on lift and growth rates _location.Y -= _liftSpeed; _growthCount++; if (_growthCount % _growthRate == 0) { _dimensions.Height++; _dimensions.Width++; } } if (_dimensions.Height >= _maxSize) { // Balloon will pop // TODO Handle balloon pop event

Page 228: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} else { // Move balloon if (_location.Y + _dimensions.Height <= 0) _location.Y = boardSize.Height; // Draw balloon and balloon tail using (SolidBrush brush = new SolidBrush(_fillColor)) graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); Point tailStart = new Point(_location.X + _dimensions.Width / 2, _location.Y + _dimensions.Height); Point tailEnd = new Point(tailStart.X, tailStart.Y + _tailLength); using (Pen pen = new Pen(_fillColor)) graphics.DrawLine(pen, tailStart, tailEnd); } } public BalloonInfo Info() { return new BalloonInfo() { GrowthRate = _growthRate, LiftSpeed = _liftSpeed, MaxSize = _maxSize, Location = _location, Dimensions = _dimensions, FillColor = _fillColor }; } public bool Hit(Point location) { return new Rectangle(_location, _dimensions).Contains(location); } }}

Click to save your changes.

Let's discuss this code.

Page 229: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Balloons{ // Balloon info struct used for custom event arguments public struct BalloonInfo { public int GrowthRate; public int LiftSpeed; public int MaxSize; public Point Location; public Size Dimensions; public Color FillColor; }

public class Balloon { // Private class properties private int _growthCount = 0; private int _growthRate; private int _maxSize; private int _liftSpeed; private int _tailLength; private Point _location; private Size _dimensions; private Color _fillColor; // Public properties public Color FillColor { set { _fillColor = value; } } // Constructor public Balloon(Point location, Size dimensions, Color fillColor, int growthRate, int liftSpeed) { _location = location; _dimensions = dimensions; _fillColor = fillColor; _maxSize = dimensions.Height * 2; _growthRate = growthRate; _liftSpeed = liftSpeed; _tailLength = _dimensions.Height * 2; } // Public methods public void DrawAndAnimate(bool animate, Size boardSize, Graphics graphics) { if (animate) { // Move and enlarge balloon based on lift and growth rates _location.Y -= _liftSpeed; _growthCount++; if (_growthCount % _growthRate == 0) { _dimensions.Height++; _dimensions.Width++; } } if (_dimensions.Height >= _maxSize) { // Balloon will pop // TODO Handle balloon pop event

Page 230: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} else { // Move balloon if (_location.Y + _dimensions.Height <= 0) _location.Y = boardSize.Height; // Draw balloon and balloon tail using (SolidBrush brush = new SolidBrush(_fillColor)) graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); Point tailStart = new Point(_location.X + _dimensions.Width / 2, _location.Y + _dimensions.Height); Point tailEnd = new Point(tailStart.X, tailStart.Y + _tailLength); using (Pen pen = new Pen(_fillColor)) graphics.DrawLine(pen, tailStart, tailEnd); } } public BalloonInfo Info() { return new BalloonInfo() { GrowthRate = _growthRate, LiftSpeed = _liftSpeed, MaxSize = _maxSize, Location = _location, Dimensions = _dimensions, FillColor = _fillColor }; } public bool Hit(Point location) { return new Rectangle(_location, _dimensions).Contains(location); } }}

We have a number o f private class variables, but only _f illCo lo r is used with a property; it uses theFillCo lo r accessor as a write-only property. We limit the access to our classes through mechanisms thatgive us contro l o f how anyone might use our classes. The only property o f the balloon we allow to be setindependently is the co lor; we do that using the constructor. If we need information about the balloon, we'vegot the Ballo o nInf o struct that we can use now. Using a struct conso lidates information into a singlemethod call, and will also be useful when we implement an event tied to the balloon later.

NoteIn C# parlance, a private class variable that is used with a public accessor is said to "back up"the accessor and is referred to as a "backing variable." In our code, _f illCo lo r is a backingvariable to the FillCo lo r accessor.

Also note that we conso lidated the animation and movement o f the balloon into a single method,DrawAndAnimat e . This conso lidation will prove useful when we use a delegate. We added a comment, //T ODO Handle ballo o n po p event , that marks where we'll need to add code later to account fo r thepopping o f our balloon once it reaches its maximum size.

We also included a standard method we've used before to determine hit o r select detection. We'll use it laterwhen selecting a balloon.

Page 231: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Tip

Studio provides a number o f useful methods for keeping track o f tasks you need to finish. Wecould use the NotImplementedException, but when we test our program and a balloon reaches amaximum size, the exception would be raised. An better method to use invo lves having Studiounderstand the // T ODO Handle ballo o n po p event as a task that we need to finish. When youuse the T ODO token, Studio creates an entry in the Task List window automatically. You can viewthe Task List by go ing to View | T ask List , o r by using the Ct rl+W, Ct rl+T shortcut. Be sure toselect Co mment s from the drop-down menu. Feel free to experiment with the Task List; it's veryuseful!

Let's next add a class to manage our game.

Right-click Ballo o ns in the So lution Explorer, select Add | Class, enter Ballo o nGame in the Name textbox,and click Add. Modify Ballo o nGame.cs as shown:

Page 232: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;using System.Collections;

namespace Balloons{ public class BalloonGame { // Private class properties private ArrayList _balloons = new ArrayList(); private int _desiredFrameRate = 20; private Random _random = new Random(); private int _lastTickCount; private Color _defaultColor = Color.Green; private Color _hitColor = Color.Red; // Private class backing variables private Size _boardSize; private int _maxBalloons; // Public properties public Size BoardSize { set { _boardSize = value; } } public int MaxBalloons { get { return _maxBalloons; } } public int DesiredFrameRate { get { return _desiredFrameRate; } set { _desiredFrameRate = value; } } // Constructor public BalloonGame(int maximumBalloonCount, Size boardSize) { // Initialize game settings and create first balloon _boardSize = boardSize; _maxBalloons = maximumBalloonCount; if (_maxBalloons <= 0) _maxBalloons = 5; _balloons.Add(CreateBalloon()); } // Private methods private Balloon CreateBalloon() { // Randomly set growth and lift rates, and create a balloon int growthRate = _random.Next(10, 41); int liftRate = _random.Next(1, 6); Balloon balloon = new Balloon(new Point(_random.Next(_boardSize.Width - 20), _boardSize.Height), new Size(20, 20), _defaultColor, growthRate, liftRate); return balloon; } private bool TimeToAnimate() { // Determine if time to animate game based on desired game frame rate bool result = false; result = (((System.Environment.TickCount & Int32.MaxValue) - _lastTickCount) >= (1000 / _desiredFrameRate)); if (result) _lastTickCount = System.Environment.TickCount; return result; } // Public methods and event handlers

Page 233: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

public void Update(Graphics graphics) { // Check if time to animate objects bool timeToAnimate = TimeToAnimate(); // Animate, if time, and draw each balloon for (int i = 0; i < _balloons.Count; i++) ((Balloon)_balloons[i]).DrawAndAnimate(timeToAnimate, _boardSize, graphics); } }}

Click to save your changes.

Let's discuss this code.

Page 234: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;using System.Collections;

namespace Balloons{ public class BalloonGame { // Private class properties private ArrayList _balloons = new ArrayList(); private int _desiredFrameRate = 20; private Random _random = new Random(); private int _lastTickCount; private Color _defaultColor = Color.Green; private Color _hitColor = Color.Red; // Private class backing variables private Size _boardSize; private int _maxBalloons; // Public properties public Size BoardSize { set { _boardSize = value; } } public int MaxBalloons { get { return _maxBalloons; } } public int DesiredFrameRate { get { return _desiredFrameRate; } set { _desiredFrameRate = value; } } // Constructor public BalloonGame(int maximumBalloonCount, Size boardSize) { // Initialize game settings and create first balloon _boardSize = boardSize; _maxBalloons = maximumBalloonCount; if (_maxBalloons <= 0) _maxBalloons = 5; _balloons.Add(CreateBalloon()); } // Private methods private Balloon CreateBalloon() { // Randomly set growth and lift rates, and create a balloon int growthRate = _random.Next(10, 41); int liftRate = _random.Next(1, 6); Balloon balloon = new Balloon(new Point(_random.Next(_boardSize.Width - 20), _boardSize.Height), new Size(20, 20), _defaultColor, growthRate, liftRate); return balloon; } private bool TimeToAnimate() { // Determine if time to animate game based on desired game frame rate bool result = false; result = ((System.Environment.TickCount - _lastTickCount) >= (1000 / _desiredFrameRate)); if (result) _lastTickCount = System.Environment.TickCount; return result; } // Public methods and event handlers

Page 235: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

public void Update(Graphics graphics) { // Check if time to animate objects bool timeToAnimate = TimeToAnimate(); // Animate, if time, and draw each balloon for (int i = 0; i < _balloons.Count; i++) ((Balloon)_balloons[i]).DrawAndAnimate(timeToAnimate, _boardSize, graphics); } }}

The Ballo o nGame class serves as the game object; we'll use it to make our game begin to work. In thisclass, we've switched back to using an ArrayList to ho ld our balloons in the _ballo o ns variable. Sincewe're using an ArrayList, we had to add the appropriate using statement. Most o f the code is probablyfamiliar to you. We did use an explicit cast when we looped through the _ballo o ns ArrayList. In a futurelesson we'll go over ways to code so we don't have to cast in this situation. We could have used a foreachloop here as well, but later when we begin to add additional balloons, our fo reach looping o f the ArrayListwould have run into problems. We have used a new method name called Updat e that will be usedencapsulate the concept o f updating the state o f our game.

We have added another new concept to our game: frame rate. Previously, we've used a Timer contro l todetermine when we animated objects in our game. Although we didn't mention it, the speed o f the animationof our game was directly tied to how frequently the Timer Tick event was raised. In o ther words, the shorter theTimer Interval property, the faster our game would run. What we really need is a way to contro l the speed o fobjects without worrying about the Timer Interval, so long as the interval value is set so that we get calls toupdate our game freqently enough.

To accomplish this animation speed independent o f Timer interval frequency, we've introduced two privateclass variables into our Ballo o nGame class: _desiredFrameRat e and _last T ickCo unt . Whenever theUpdat e method is called, we call the T imeT o Animat e method, and determine if sufficient time has elapsedsince the last time we checked to create a certain frame per second (FPS) value. If that FPS value is greaterthan the _desiredFrameRat e , we know it's time to animate our objects. In the context o f our balloon game,animate means to change the position o f the balloons, and to increase the size o f the balloon. Even if it's nottime to animate the balloon, we still draw the balloon. We'll discuss why when we move on to adding code tothe Form to call the Updat e method. We'll revisit changing the Timer contro l Interval value and the desiredFPS for the game, and learn more about how these two concepts impact each o ther later as well.

Let's update our Form code so that we can get an animated balloon. (Note that we do not limit the size o f theballoon (it doesn't pop) yet, so the balloon will continue to grow and grow.

Modify MainFo rm.cs as shown:

Page 236: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private BalloonGame _game; // Balloon game object...private void MainForm_Shown(object sender, EventArgs e){ // Create balloon game object, defaulting max number of balloons to 10 _game = new BalloonGame(10, mainPictureBox.ClientSize);}

private void MainForm_Resize(object sender, EventArgs e){ // Respond to Form resize _game.BoardSize = mainPictureBox.ClientSize;}

private void mainPictureBox_Paint(object sender, PaintEventArgs e){ // Draw the balloons _game.Update(e.Graphics);}

private void gameTimer_Tick(object sender, EventArgs e){ // Force PictureBox Paint event to be raised mainPictureBox.Invalidate();}...

and to run the program. A balloon will be drawn, move upwards, and get larger. You may also adjustthe size o f the game.

Let's discuss this code.

Page 237: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private BalloonGame _game; // Balloon game object...private void MainForm_Shown(object sender, EventArgs e){ // Create balloon game object, defaulting max number of balloons to 10 _game = new BalloonGame(10, mainPictureBox.ClientSize);}

private void MainForm_Resize(object sender, EventArgs e){ // Respond to Form resize _game.BoardSize = mainPictureBox.ClientSize;}

private void mainPictureBox_Paint(object sender, PaintEventArgs e){ // Draw the balloons _game.Update(e.Graphics);}

private void gameTimer_Tick(object sender, EventArgs e){ // Force PictureBox Paint event to be raised mainPictureBox.Invalidate();}...

We added a private class variable, _game , that will be the instance o f our Ballo o nGame . We instantiate thisinstance in the Form Shown event. We Invalidat e the PictureBox contro l each time the Timer Tick event israised, which raises the PictureBox Paint event. In the PictureBox Paint event, we call the _game Updat emethod.

Remember, the Updat e method is checking to determine whether the balloon needs to be animated eachtime it is called; it's also drawing the balloon each time, even if no thing changed. Why? It's important tounderstand that the PictureBox contro l uses a technique called do uble buf f ering fo r drawing, which is moreefficient than drawing directly to the contro l being displayed. Double buffering works by having two buffers thatho ld what's displayed, and while one buffer is displayed, the o ther buffer is used for any updates. Each timewe draw to the PictureBox, we're drawing to this invisible or "non-displayed," buffer; once the Paint eventterminates, the PictureBox contro l flips the display to this previously invisible buffer. So, each time you call thePictureBox Paint event, you must rewrite everything that needs to be displayed. If you don't, you'll get a flickereffect, because the invisible buffer is displayed, but it's empty. Although it may seem easier not to rewrite eachtime, double buffering is a good method for animation. Not all contro ls have double buffering, but thePictureBox contro l does, so we use it.

We've also added code to the Form Resize event to accomodate a change in the game.

Now that we've gotten a balloon displayed and animated, let's add code to use events and delegates. Firstlet's work on raising an event when the balloon pops.

Tip You can use the Studio Task List to jump to lines o f code that you've marked by double-clickingon the entry in the Task List.

Modify Ballo o n.cs as shown:

Page 238: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Public event delegatepublic event EventHandler Popped;

// Public event handlerpublic virtual void OnPopped(EventArgs e){ if (Popped != null) Popped(this, e);}

// Public methodspublic void DrawAndAnimate(bool animate, Size boardSize, Graphics graphics){ if (animate) { // Move and enlarge balloon based on lift and growth rates _location.Y -= _liftSpeed; _growthCount++; if (_growthCount % _growthRate == 0) { _dimensions.Height++; _dimensions.Width++; } } if (_dimensions.Height >= _maxSize) { // Balloon will pop // TODO Handle balloon pop event OnPopped(EventArgs.Empty); } else { // Draw balloon and balloon tail if (_location.Y + _dimensions.Height <= 0) _location.Y = boardSize.Height; using (SolidBrush brush = new SolidBrush(_fillColor)) graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); Point tailStart = new Point(_location.X + _dimensions.Width / 2, _location.Y + _dimensions.Height); Point tailEnd = new Point(tailStart.X, tailStart.Y + _tailLength); using (Pen pen = new Pen(_fillColor)) graphics.DrawLine(pen, tailStart, tailEnd); }}...

Click to save your changes.

Let's discuss this code.

Page 239: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Public event delegatepublic event EventHandler Popped;

// Public event handlerpublic virtual void OnPopped(EventArgs e){ if (Popped != null) Popped(this, e);}

// Public methodspublic void DrawAndAnimate(bool animate, Size boardSize, Graphics graphics){ if (animate) { // Move and enlarge balloon based on lift and growth rates _location.Y -= _liftSpeed; _growthCount++; if (_growthCount % _growthRate == 0) { _dimensions.Height++; _dimensions.Width++; } }

if (_dimensions.Height >= _maxSize) { // Balloon will pop OnPopped(EventArgs.Empty); } else { // Draw balloon and balloon tail if (_location.Y + _dimensions.Height <= 0) _location.Y = boardSize.Height; using (SolidBrush brush = new SolidBrush(_fillColor)) graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); Point tailStart = new Point(_location.X + _dimensions.Width / 2, _location.Y + _dimensions.Height); Point tailEnd = new Point(tailStart.X, tailStart.Y + _tailLength); using (Pen pen = new Pen(_fillColor)) graphics.DrawLine(pen, tailStart, tailEnd); }}...

We added an event delegate to the Balloon class using the event keyword, fo llowed by the type o f event wewant to use, in this case, Event Handler. We discussed this kind o f .NET event type that is used whenever wewant a .NET compatible event that has no event arguments. The event delegate that we've created is calledPo pped; subscribers use it to gain notification when a balloon is popped.

Next, we declare a public, virt ual method called OnPo pped. This method is used internally by the Balloonclass to raise a Po pped event. By convention, internal class event handlers that derive or use .NET eventsare declared as virt ual so that derived classes may override the behavior. And, because these methods arevirtual, they must be public. A private, virtual method could not be overridden, so why make it virtual?

The OnPo pped method checks to make sure that Po pped is not null, which is a test to make sure there aresubscribers. If there are subscribers, we raise the Po pped event, passing the t his self instance reference asthe first parameter, and the Event Args e event argument. The t his reference becomes the object senderparameter we've seen so o ften in event handler methods, and the Event Args e are the event-specificarguments that change depending on the event being raised.

Page 240: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Lastly, we've replaced our T ODO task with a call to OnPo pped to notify any subscribers that a balloon haspopped. Because Event Handler events send no event information, we specify Event Args.Empt y as theevent argument when we call OnPo pped.

So, let's update our BalloonGame class to handle this event.

Modify Ballo o nGame.cs as shown:

CODE TO TYPE:

.

.

.// Private methodsprivate Balloon CreateBalloon(){ // Randomly set growth and lift rates, and create a balloon int growthRate = _random.Next(10, 41); int liftRate = _random.Next(1, 6); Balloon balloon = new Balloon(new Point(_random.Next(_boardSize.Width - 20), _boardSize.Height), new Size(20, 20), _defaultColor, growthRate, liftRate); // Add an event handler to Popped event for each ballon balloon.Popped += new EventHandler(PoppedEventHandler); return balloon;}

private void RemoveBalloon(Balloon balloon){ // Remove event delegate handler for Popped event for this balloon balloon.Popped -= this.PoppedEventHandler;}

// Private methods and event handlersprivate void PoppedEventHandler(object sender, EventArgs e){ // Remove popped balloon and add new balloon, then conditionally add new balloon // if max balloon count not reached Balloon balloon = sender as Balloon; if (balloon != null) RemoveBalloon(balloon); _balloons.Add(CreateBalloon()); if (_balloons.Count < _maxBalloons) _balloons.Add(CreateBalloon());}...

and to run the program. Now, as each balloon reaches its maximum size, it will pop, and additionalballoons will appear.

Let's discuss this code.

Page 241: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Private methodsprivate Balloon CreateBalloon(){ // Randomly set growth and lift rates, and create a balloon int growthRate = _random.Next(10, 41); int liftRate = _random.Next(1, 6); Balloon balloon = new Balloon(new Point(_random.Next(_boardSize.Width - 20), _boardSize.Height), new Size(20, 20), _defaultColor, growthRate, liftRate); // Add an event handler to Popped event for each ballon balloon.Popped += new EventHandler(PoppedEventHandler); return balloon;}

private void RemoveBalloon(Balloon balloon){ // Remove event delegate handler for Popped event for this balloon balloon.Popped -= this.PoppedEventHandler;}

// Private methods and event handlersprivate void PoppedEventHandler(object sender, EventArgs e){ // Remove popped balloon and add new balloon, then conditionally add new balloon // if max balloon count not reached Balloon balloon = sender as Balloon; if (balloon != null) RemoveBalloon(balloon); _balloons.Add(CreateBalloon()); if (_balloons.Count < _maxBalloons) _balloons.Add(CreateBalloon());}...

With these changes, the balloon game will now handle the popping o f a balloon by removing that balloon,and creating more balloons. When we create a balloon, we use the addition assignment operator (+-) tosubscribe to the Po pped event o f the new balloon. When a balloon is popped, we use the subtractionassignment operator (-+) to unsubscribe from the Po pped event fo r that balloon. We added an event handlermethod, Po ppedEvent Handler, to handle the Po pped event fo r all o f the balloons. In this event handler,the sender parameter is the balloon that was popped.

While we've added one event, we can use the concept o f delegates to make our code a bit more efficient, anddemonstrate the power o f delegates.

Modify Ballo o n.cs as shown:

Page 242: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Public event delegatepublic event EventHandler Popped;public delegate void BalloonDrawAnimateDelegate(bool animate, Size boardSize, Graphics graphics);...// Public methodspublic void DrawAndAnimate(bool animate, Size boardSize, Graphics graphics){...}...

Click to save your changes.

Let's discuss this code.

OBSERVE:

.

.

.// Public event delegatepublic event EventHandler Popped;public delegate void BalloonDrawAnimateDelegate(bool animate, Size boardSize, Graphics graphics);...// Public methodspublic void DrawAndAnimate(bool animate, Size boardSize, Graphics graphics){...}

This new line o f code declares a delegat e type definition called Ballo o nDrawAnimat eDelegat e with aparameter signature that matches the DrawAndAnimat e method fingerprint. Let's see how we can use thisdelegate type definition.

Modify the code in Ballo o nGame.cs class code as shown below.

Page 243: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Private delegatesprivate Balloon.BalloonDrawAnimateDelegate _ballonDrawAnimate;

// Private methodsprivate Balloon CreateBalloon(){ // Randomly set growth and lift rates, and create a balloon int growthRate = _random.Next(10, 41); int liftRate = _random.Next(1, 6); Balloon balloon = new Balloon(new Point(_random.Next(_boardSize.Width - 20), _boardSize.Height), new Size(20, 20), _defaultColor, growthRate, liftRate); // Add an event handler to Popped event for each ballon balloon.Popped += new EventHandler(PoppedEventHandler); // Add balloon drawing and animation method to our delegate list _ballonDrawAnimate += balloon.DrawAndAnimate; return balloon;}

private void RemoveBalloon(Balloon balloon){ // Remove event delegate handler for Popped event for this balloon balloon.Popped -= this.PoppedEventHandler; // Remove draw and animate delegate from our delegate list _ballonDrawAnimate -= balloon.DrawAndAnimate;}...// Public methods and event handlerspublic void Update(Graphics graphics){ // Check if time to animate objects bool timeToAnimate = TimeToAnimate(); // Animate, if time, and draw each balloon for (int i = 0; i < _balloons.Count; i++) ((Balloon)_balloons[i]).DrawAndAnimate(timeToAnimate, _boardSize, graphics); // Call delegate to animate and redraw all current balloons _ballonDrawAnimate(timeToAnimate, _boardSize, graphics);}...

and to run the program. The program runs the same as it did before.

Let's discuss this code.

Page 244: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Private delegatesprivate Balloon.BalloonDrawAnimateDelegate _ballonDrawAnimate;

// Private methodsprivate Balloon CreateBalloon(){ // Randomly set growth and lift rates, and create a balloon int growthRate = _random.Next(10, 41); int liftRate = _random.Next(1, 6); Balloon balloon = new Balloon(new Point(_random.Next(_boardSize.Width - 20), _boardSize.Height), new Size(20, 20), _defaultColor, growthRate, liftRate); // Add an event handler to Popped event for each ballon balloon.Popped += new EventHandler(PoppedEventHandler); // Add balloon drawing and animation method to our delegate list _ballonDrawAnimate += balloon.DrawAndAnimate; return balloon;}

private void RemoveBalloon(Balloon balloon){ // Remove event delegate handler for Popped event for this balloon balloon.Popped -= this.PoppedEventHandler; // Remove draw and animate delegate from our delegate list _ballonDrawAnimate -= balloon.DrawAndAnimate;}...// Public methods and event handlerspublic void Update(Graphics graphics){ // Check if time to animate objects bool timeToAnimate = TimeToAnimate(); // Animate, if time, and draw each balloon // Call delegate to animate and redraw all current balloons _ballonDrawAnimate(timeToAnimate, _boardSize, graphics);}...

We added a delegate instance variable from the delegate Balloon type Ballo o nDrawAnimat eDelegat e wejust declared in the Ballo o n class called _ballo nDrawAnimat e . This delegate will ho ld all o f the draw andanimate methods for each o f the balloons that are created. We've modified the code when we create andremove a balloon to either add each Ballo o n DrawAndAnimat e method or remove it. Then, whenever theUpdat e method is called, we call the _ballo nDrawAnimat e delegate instance variable. When this variableis called, it acts just like a function call, but because it contains delegates, it executes the DrawAndAnimat efo r each balloon, passing the same parameters to each method.

Have you noticed that with our latest changes, we were able to modify the Balloon and BalloonGame classeswithout modifying the Form code? The more we can encapsulate our code within well defined andunderstood objects, the easier it is to modify those objects without disturbing o ther components in ourprogram.

Next, let's add the ability to select a balloon, and get some information about the balloon. We'll be using theBalloonInfo struct we created earlier, returning this information as part o f the event arguments. To accomplishthis task, we'll need to create our own event arguments class.

Page 245: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Right-click Ballo o ns in the So lution Explorer, select Add | Class, enter Ballo o nInf o Args in the Nametextbox, and click Add. Modify Ballo o nInf o Args.cs as shown:

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace Balloons{ public class BalloonInfoArgs : EventArgs { private BalloonInfo _balloonInfo; public BalloonInfo Info { get { return _balloonInfo; } } public BalloonInfoArgs(Balloon balloon) { if (balloon != null) _balloonInfo = balloon.Info(); } }}

Click to save your changes.

Let's discuss this code.

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace Balloons{ public class BalloonInfoArgs : EventArgs { private BalloonInfo _balloonInfo; public BalloonInfo Info { get { return _balloonInfo; } } public BalloonInfoArgs(Balloon balloon) { if (balloon != null) _balloonInfo = balloon.Info(); } }}

The Ballo o nInf o Args class, derived from the .NET Event Args class, provides a single accessor that willreturn the Ballo o nInf o struct we created earlier. Why did we derive our event from Event Args, typicallyassociated with no event data? Even though the EventArgs class does not provide event data, it is the baseclass for all o ther .NET classes, providing basic event methods which may be useful fo r our own customevent arguments. We've gained several basic event methods, and then extended the EventArgs class byadding our own event information to create Ballo o nInf o Args.

Let's see how we can use this custom event class. We'll need to implement selecting a balloon, and creatingevents that publish information about the currently selected balloon.

Modify Ballo o nGame.cs as shown:

Page 246: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Public delegates and eventspublic delegate void InfoHandler(object sender, BalloonInfoArgs e);public event InfoHandler OnInfo;public event EventHandler OnNoInfo;

// Private class propertiesprivate Balloon _activeBalloon = null;

private void RemoveBalloon(Balloon balloon){ // Remove event delegate handler for Popped event for this balloon balloon.Popped -= this.PoppedEventHandler; // Remove draw and animate delegate from our delegate list _ballonDrawAnimate -= balloon.DrawAndAnimate; // Find balloon in ArrayList for removal and reset of active balloon int index = _balloons.IndexOf(balloon); if (index >= 0) { if (_balloons[index] == _activeBalloon) _activeBalloon = null; _balloons.RemoveAt(index); // Raise OnNoInfo event OnNoInfo(balloon, EventArgs.Empty); }} // Public methods and event handlerspublic void Update(Graphics graphics){ // Check if time to animate objects bool timeToAnimate = TimeToAnimate(); // Animate, if time, and draw each balloon // Call delegate to animate and redraw all current balloons _ballonDrawAnimate(timeToAnimate, _boardSize, graphics); // Raise OnInfo event if we have an active (selected) balloon if (_activeBalloon != null) OnInfo(_activeBalloon, new BalloonInfoArgs(_activeBalloon));}

public void Select(Point location){ // Loop through each balloon in ArrayList to see which was selected foreach (Balloon balloon in _balloons) { if (balloon.Hit(location)) { // Reset active balloon color if (_activeBalloon != null && _activeBalloon != balloon) _activeBalloon.FillColor = _defaultColor; _activeBalloon = balloon; _activeBalloon.FillColor = _hitColor; // Raise OnInfo event with custom arguments OnInfo(_activeBalloon, new BalloonInfoArgs(_activeBalloon)); break; } }}.

Page 247: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

.

.

Click to save your changes.

Let's discuss this code.

Page 248: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Public delegates and eventspublic delegate void InfoHandler(object sender, BalloonInfoArgs e);public event InfoHandler OnInfo;public event EventHandler OnNoInfo;

// Private class propertiesprivate Balloon _activeBalloon = null;

private void RemoveBalloon(Balloon balloon){ // Remove event delegate handler for Popped event for this balloon balloon.Popped -= this.PoppedEventHandler; // Remove draw and animate delegate from our delegate list _ballonDrawAnimate -= balloon.DrawAndAnimate; // Find balloon in ArrayList for removal and reset of active balloon int index = _balloons.IndexOf(balloon); if (index >= 0) { if (_balloons[index] == _activeBalloon) _activeBalloon = null; _balloons.RemoveAt(index); // Raise OnNoInfo event OnNoInfo(balloon, EventArgs.Empty); }}

// Public methods and event handlerspublic void Update(Graphics graphics){ // Check if time to animate objects bool timeToAnimate = TimeToAnimate(); // Animate, if time, and draw each balloon // Call delegate to animate and redraw all current balloons _ballonDrawAnimate(timeToAnimate, _boardSize, graphics); // Raise OnInfo event if we have an active (selected) balloon if (_activeBalloon != null) OnInfo(_activeBalloon, new BalloonInfoArgs(_activeBalloon));}

public void Select(Point location){ // Loop through each balloon in ArrayList to see which was selected foreach (Balloon balloon in _balloons) { if (balloon.Hit(location)) { // Reset active balloon color if (_activeBalloon != null && _activeBalloon != balloon) _activeBalloon.FillColor = _defaultColor; _activeBalloon = balloon; _activeBalloon.FillColor = _hitColor; // Raise OnInfo event with custom arguments OnInfo(balloon, new BalloonInfoArgs(_activeBalloon)); break; } }}.

Page 249: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

.

.

We added a new delegat e type definition, Inf o Handler, that we'll be using to handle balloon informationevents. Because a balloon may either be selected or not selected, we have created two events: OnInf o o four custom event delegate Inf o Handler, and OnNo Inf o o f the .NET standard Event Handler (whichdoesn't need any event information). We've modified the code in the Select and Update routines to raise theOnInf o event, publishing the necessary information about the currently selected balloon. We track whichballoon is selected using a new class variable _act iveBallo o n. In order to determine which balloon isselected, we've added a new method Select that uses the Hit method o f each balloon to determine whichballoon is selected. Notice that in the Select method we use break to exit the foreach loop once we've foundthe correct balloon.

When a balloon is removed, we need to account fo r the possibility that the removed balloon was the selectedballoon, and respond accordingly by nulling _act iveBallo o n and raising the OnNo Inf o event.

Let's update our Form code to enable the select detection, and display information about the selectedballoon.

Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.private void MainForm_Shown(object sender, EventArgs e){ // Create balloon game object, defaulting max number of balloons to 10 _game = new BalloonGame(10, mainPictureBox.ClientSize); // Add event handler for balloon info event from game object using custom event type _game.OnInfo += new BalloonGame.InfoHandler(OnInfoEventHandler); // Add event handler for no balloon info event from game object using standard no arguments // event type _game.OnNoInfo += new EventHandler(OnNoInfoEventHandler);}

private void mainPictureBox_MouseUp(object sender, MouseEventArgs e){ // Directly call game object selection method _game.Select(e.Location);}...protected virtual void OnNoInfoEventHandler(object sender, EventArgs e){ // Respond to NoInfo event from game object selectedBalloonToolStripStatusLabel.Text = "No balloon selected";}

protected virtual void OnInfoEventHandler(object sender, BalloonInfoArgs e){ // Respond to Info event from game object string s = "Balloon - "; s += "Size: " + e.Info.Dimensions.Width; s += "; Lift: " + e.Info.LiftSpeed; selectedBalloonToolStripStatusLabel.Text = s;}...

Page 250: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

and to run the program. You can now select a balloon and see information about the balloon on theStatusStrip.

OBSERVE:

.

.

.private void MainForm_Shown(object sender, EventArgs e){ // Create balloon game object, defaulting max number of balloons to 10 _game = new BalloonGame(10, mainPictureBox.ClientSize); // Add event handler for balloon info event from game object using custom event type _game.OnInfo += new BalloonGame.InfoHandler(OnInfoEventHandler); // Add event handler for no balloon info event from game object using standard no arguments // event type _game.OnNoInfo += new EventHandler(OnNoInfoEventHandler);}

private void mainPictureBox_MouseUp(object sender, MouseEventArgs e){ // Directly call game object selection method _game.Select(e.Location);}...protected virtual void OnNoInfoEventHandler(object sender, EventArgs e){ // Respond to NoInfo event from game object selectedBalloonToolStripStatusLabel.Text = "No balloon selected";}

protected virtual void OnInfoEventHandler(object sender, BalloonInfoArgs e){ // Respond to Info event from game object string s = "Balloon - "; s += "Size: " + e.Info.Dimensions.Width; s += "; Lift: " + e.Info.LiftSpeed; selectedBalloonToolStripStatusLabel.Text = s;}...

We modified the Form Shown event handler to add a subscriber to the OnInf o event and the OnNo Inf oevent. When subscribing, compatible event handler methods were passed as parameters. Each event handlermethod implemented whatever was necessary to handle the event: either updating the StatusStrip to indicatethat no balloon was selected, or retrieving information from the Ballo o nInf o Args e event argument torequest the balloon info struct using the Inf o method.

We've covered a whole lo t o f material about delegates and events here. Let's review the frames per secondconcepts we touched on earlier. We added a number o f menu items we haven't used, and we still have twoStatusLabel contro ls on the StatusStrip we haven't updated. Let's add code to handle the menu item events,and calculate and display what the frames per second would be if we were using the Timer Interval propertyfor our game frames per second. We'll also make it possible to update the desired game frames per second,and display that value as well. First, let's add a method to the game that will return a count o f the number o fballoons.

Modify Ballo o nGame.cs as shown:

Page 251: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public int BalloonCount{ get { return (_ballonDrawAnimate != null ? _ballonDrawAnimate.GetInvocationList().Count() : 0); }}...

Click to save your changes.

Let's discuss this code.

OBSERVE:

.

.

.public int BalloonCount{ get { return (_ballonDrawAnimate != null ? _ballonDrawAnimate.GetInvocationList().Count() : 0); }}...

We could have returned the ArrayList count, but instead we opted to use a ternary operator and the_ballo nDrawAnimat e delegate instance variable we added earlier. Using the delegate instance, we call theGet Invo cat io nList method that will return an array o f all o f the delegates. We don't need the list—just thecount o f the list—but showing the Get Invo cat io nList demonstrates just one o f the many methods that isavailable when working with delegates.

Let's update the Form code next.

Modify MainFo rm.cs as shown:

Page 252: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private static int _currentFrameRate; // Timer control frame per second (FPS) variablesprivate static int _lastFrameRate;private static int _lastTickCount;

private static int getFrameRate(){ // Calculate frame rate using elapsed millisecond counter if (System.Environment.TickCount - _lastTickCount >= 1000) { _lastFrameRate = _currentFrameRate; _currentFrameRate = 0; _lastTickCount = System.Environment.TickCount; } _currentFrameRate++; return _lastFrameRate;}

private void MainForm_Shown(object sender, EventArgs e){ // Add event handlers using delegate + operator fps15ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps20ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps30ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps45ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps60ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); interval5ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval10ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval15ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval20ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval30ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval45ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval60ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); // Simulate clicking 15 interval menu item to set timer interval interval15ToolStripMenuItem.PerformClick(); // Create balloon game object, defaulting max number of balloons to 10 _game = new BalloonGame(10, mainPictureBox.ClientSize); // Simulate clicking 20 game FPS menu item for the desired game FPS fps20ToolStripMenuItem.PerformClick(); // Add event handler for balloon info event from game object using custom event type _game.OnInfo += new BalloonGame.InfoHandler(OnInfoEventHandler); // Add event handler for no balloon info event from game object using standard no arguments // event type _game.OnNoInfo += new EventHandler(OnNoInfoEventHandler);

Page 253: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

}

void gameFPSToolStripMenuItem_Click(object sender, EventArgs e){ // Adjust game FPS rate, checking and unchecking menu items ToolStripMenuItem menuItem = sender as ToolStripMenuItem; if (menuItem != null) { fps15ToolStripMenuItem.Checked = false; fps20ToolStripMenuItem.Checked = false; fps30ToolStripMenuItem.Checked = false; fps45ToolStripMenuItem.Checked = false; fps60ToolStripMenuItem.Checked = false; menuItem.Checked = true; _game.DesiredFrameRate = Convert.ToInt32(menuItem.Text); }}

void intervalToolStripMenuItem_Click(object sender, EventArgs e){ // Adjust timer interval, checking and unchecking menu items ToolStripMenuItem menuItem = sender as ToolStripMenuItem; if (menuItem != null) { interval5ToolStripMenuItem.Checked = false; interval10ToolStripMenuItem.Checked = false; interval15ToolStripMenuItem.Checked = false; interval20ToolStripMenuItem.Checked = false; interval30ToolStripMenuItem.Checked = false; interval45ToolStripMenuItem.Checked = false; interval60ToolStripMenuItem.Checked = false; menuItem.Checked = true; gameTimer.Interval = Convert.ToInt32(menuItem.Text); }}

private void mainPictureBox_Paint(object sender, PaintEventArgs e){ // Draw the balloons _game.Update(e.Graphics); // Update status strip labels timerFpsToolStripStatusLabel.Text = "Timer FPS: " + getFrameRate() + ", Game FPS: " + _game.DesiredFrameRate; balloonCountToolStripStatusLabel.Text = "Balloon count: " + _game.BalloonCount + " of " + _game.MaxBalloons;}...

and to run the program. The game displays the current theoretical FPS based on the Timer Interval,along with the actual desired FPS for the game. Select a different Game FPS, and see the balloon animationincrease or decrease depending on the value you selected. Select different Timer Interval values, and noticethat the speed o f the game does not change so long as the Timer FPS is greater than the Game FPS.

Let's discuss this code.

Page 254: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private static int _currentFrameRate; // Timer control frame per second (FPS) variablesprivate static int _lastFrameRate;private static int _lastTickCount;

private static int getFrameRate(){ // Calculate frame rate using elapsed millisecond counter if (System.Environment.TickCount - _lastTickCount >= 1000) { _lastFrameRate = _currentFrameRate; _currentFrameRate = 0; _lastTickCount = System.Environment.TickCount; } _currentFrameRate++; return _lastFrameRate;}

private void MainForm_Shown(object sender, EventArgs e){ // Add event handlers using delegate + operator fps15ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps20ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps30ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps45ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); fps60ToolStripMenuItem.Click += new EventHandler(gameFPSToolStripMenuItem_Click); interval5ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval10ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval15ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval20ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval30ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval45ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); interval60ToolStripMenuItem.Click += new EventHandler(intervalToolStripMenuItem_Click); // Simulate clicking 15 interval menu item to set timer interval interval15ToolStripMenuItem.PerformClick(); // Create balloon game object, defaulting max number of balloons to 10 _game = new BalloonGame(10, mainPictureBox.ClientSize); // Simulate clicking 20 game FPS menu item for the desired game FPS fps20ToolStripMenuItem.PerformClick(); // Add event handler for balloon info event from game object using custom event type _game.OnInfo += new BalloonGame.InfoHandler(OnInfoEventHandler); // Add event handler for no balloon info event from game object using standard no arguments // event type _game.OnNoInfo += new EventHandler(OnNoInfoEventHandler);

Page 255: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

}

void gameFPSToolStripMenuItem_Click(object sender, EventArgs e){ // Adjust game FPS rate, checking and unchecking menu items ToolStripMenuItem menuItem = sender as ToolStripMenuItem; if (menuItem != null) { fps15ToolStripMenuItem.Checked = false; fps20ToolStripMenuItem.Checked = false; fps30ToolStripMenuItem.Checked = false; fps45ToolStripMenuItem.Checked = false; fps60ToolStripMenuItem.Checked = false; menuItem.Checked = true; _game.DesiredFrameRate = Convert.ToInt32(menuItem.Text); }}

void intervalToolStripMenuItem_Click(object sender, EventArgs e){ // Adjust timer interval, checking and unchecking menu items ToolStripMenuItem menuItem = sender as ToolStripMenuItem; if (menuItem != null) { interval5ToolStripMenuItem.Checked = false; interval10ToolStripMenuItem.Checked = false; interval15ToolStripMenuItem.Checked = false; interval20ToolStripMenuItem.Checked = false; interval30ToolStripMenuItem.Checked = false; interval45ToolStripMenuItem.Checked = false; interval60ToolStripMenuItem.Checked = false; menuItem.Checked = true; gameTimer.Interval = Convert.ToInt32(menuItem.Text); }}

private void mainPictureBox_Paint(object sender, PaintEventArgs e){ // Draw the balloons _game.Update(e.Graphics); // Update status strip labels timerFpsToolStripStatusLabel.Text = "Timer FPS: " + getFrameRate() + ", Game FPS: " + _game.DesiredFrameRate; balloonCountToolStripStatusLabel.Text = "Balloon count: " + _game.BalloonCount + " of " + _game.MaxBalloons;}...

These final changes dynamically added event handlers for the Timer Interval menu items and Game FPSmenu items, using delegate/event operations. The related event handlers also set all o f the menu itemChecked properties to false and remove any check marks next to the menu items, then set the selectedT o o lSt ripMenuIt em menu item to true, and change either the desired game FPS or the Timer Interval.

We updated the PictureBox Paint method to update the StatusStrip StatusLabels with the FPS information, andthe count o f the balloons.

That's it, you've made it through a lesson on delegates and events! That was not easy, you should feel prettygreat about your progress right now.

Before you move on to the next lesson, do your homework, take any quizzes, and complete lesson pro jects assigned. Youknow the drill!

Copyright © 1998-2014 O'Reilly Media, Inc.

Page 256: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 257: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Lambda Expressions

In this lesson we'll introduce lambda statements and expressions. In later lessons we'll expand on this topic and introducegeneric co llections and LINQ.

This lesson explores:

Lamba ExpressionsTargets - A Coding Tutorial

Lamba Expressions

What is a Lambda Expression?

We've already learned about delegates, a technique we use to reference methods. During our discussion o fdelegates, we also introduced the concept o f the anonymous method, which is essentially a means o fcreating an inline set o f instructions for one-time use. Next, we'll introduce lambda expressions. Lambdaexpressions are a concise yet versatile syntax for expressing anonymous functions. In addition, lambdaexpressions o ffer o ther features essential to more generic, versatile, object-oriented programming. Thesefeatures are used frequently with more advanced C# programming constructs, such as LINQ.

NoteWe'll LINQ discuss in greater detail in a later lesson. LINQ stands for Language-INtegratedQuery. It's a set o f features and techno logy added to .NET languages that integrates querycapabilities directly into programming languages such as C#.

Lambda Expression Syntax

As we define the syntax o f a lambda expression, we'll progress through a typical method, a delegate, ananonymous method, and finally a lambda expression, all to accomplish the same task. The sample code willtake a list o f scores and return those scores that exceed a minimum value. Let's get started using a methodcall.

Select File | New | Pro ject , and change the Name of the pro ject to Lambda. Modify Fo rm1.cs as shown:

Page 258: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Collections;

namespace Lambda{ public partial class Form1 : Form { public Form1() { InitializeComponent(); Lambda(); } private struct Result { public string ID; public float Score; } private void Lambda() { ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score); } private bool checkValue(float value, float minimumValue) { return (value >= minimumValue); } }}

Click to save your changes and to run your program. You get this output:

Page 259: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Let's discuss this code.

OBSERVE:

private struct Result{ public string ID; public float Score;}

private void Lambda(){ ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score);}

private bool checkValue(float value, float minimumValue){ return (value >= minimumValue);}

You're already familiar with all o f this code, including creating and using the Result st ruct , and calling thecheckValue method. Now let's next use a delegate to accomplish the same task.

Modify Fo rm1.cs as shown:

Page 260: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private delegate bool ResultCheck(float value, float minimumValue); private void Lambda(){ ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score); // Using delegate Console.WriteLine("Delegate results"); ArrayList delegateResults = findResults(originalScores, 80.0F, checkValue); foreach (Result result in delegateResults) Console.WriteLine(result.ID + ": " + result.Score);}

private ArrayList findResults(ArrayList results, float minimumValue, ResultCheck resultCheck){ ArrayList newResults = new ArrayList(results); foreach (Result result in results) if (!resultCheck(result.Score, minimumValue)) newResults.Remove(result); return newResults;}

private bool checkValue(float value, float minimumValue){ return (value >= minimumValue);}...

Click to save your changes and to run your program. You see this output:

Let's discuss that code.

Page 261: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private delegate bool ResultCheck(float value, float minimumValue);

private void Lambda(){ ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score); // Using delegate Console.WriteLine("Delegate results"); ArrayList delegateResults = findResults(originalScores, 80.0F, checkValue); foreach (Result result in delegateResults) Console.WriteLine(result.ID + ": " + result.Score);}

private ArrayList findResults(ArrayList results, float minimumValue, ResultCheck resultCheck){ ArrayList newResults = new ArrayList(results); foreach (Result result in results) if (!resultCheck(result.Score, minimumValue)) newResults.Remove(result); return newResults;}

private bool checkValue(float value, float minimumValue){ return (value >= minimumValue);}...

We added code to find the results using the delegat e Result Check. The delegate Result Check is used asthe data type to the third parameter result Check to a new method called f indResult sAno nymo us. We'reindicating which method to use when determining the results, so when we call f indResult sAno nymo us, wepass the checkValue method name as a parameter.

In the f indResult sAno nymo us method, we create a new ArrayList, newResult s, from the original ArrayListthat was passed in as the result s parameter by passing result s as a parameter to the ArrayList constructor.The newResult s ArrayList is a completely new ArrayList, although it was created using shallow copy, whichmeans each element in newResult s references the original ArrayList elements. We remove only theelements that are referenced from the newResult s ArrayList, and do not change the original result sArrayList.

Let's get started coding the anonymous method version now. Keep in mind that the construction o f theanonymous method is dependent on the parameters and return type o f the delegate we're replacing. We'rereplacing the two float parameters fingerprint with a boo lean return type. Let's add the anonymous methodversion.

Modify Fo rm1.cs as shown:

Page 262: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void Lambda(){ ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score); // Using delegate Console.WriteLine("Delegate results"); ArrayList delegateResults = findResults(originalScores, 80.0F, checkValue); foreach (Result result in delegateResults) Console.WriteLine(result.ID + ": " + result.Score); // Using anonymous method Console.WriteLine("Anonymous method results"); ArrayList anonymousResults = findResults(originalScores, 80.0F, delegate(float value, float minimumValue) { return (value >= minimumValue); }); foreach (Result result in anonymousResults) Console.WriteLine(result.ID + ": " + result.Score);}...

Click to save your changes and to run your program. You'll see this output:

Let's discuss this code.

Page 263: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void Lambda(){ ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score); // Using delegate Console.WriteLine("Delegate results"); ArrayList delegateResults = findResults(originalScores, 80.0F, checkValue); foreach (Result result in delegateResults) Console.WriteLine(result.ID + ": " + result.Score);

// Using anonymous method Console.WriteLine("Anonymous method results"); ArrayList anonymousResults = findResults(originalScores, 80.0F, delegate (float value, float minimumValue) { return (value >= minimumValue); }); foreach (Result result in anonymousResults) Console.WriteLine(result.ID + ": " + result.Score);}...

We replaced the delegate reference we used before with an anonymous method, using the delegat ekeyword to indicate that we're defining an anonymous method. When we use this anonymous method, wedon't call the checkValue method, because we've defined the functionality inline.

Now let's try a lambda expression. Lambda expressions may be expressed in a few different fo rmats,depending on whether we have zero , one, or more than one parameter, whether we need to explicitly definethe parameter data types, and whether we're constructing an expression or a statement. Let's look at thegeneric syntax, as well as each variation:

OBSERVE:

// Generic syntax(input parameters) => expression

// No parameters() => expression

// One parameterparameterOne => expression

// Two (or more) parameters(parameterOne, parameterTwo) => expression

// Two (or more) parameters with explicit data types(float parameterOne, float parameterTwo) => expression

// Lambda statement() => { statement; }

Page 264: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Lambda expressions use the => operator, which is read as "goes to ." Typically, lamba expressions do notspecify the data types o f any input paramet ers. Lambda expressions infer the data type based on thedelegate they replace, and the content o f the expressio n. Although a return type is not indicated, theexpressio n must reso lve to a compatible data type that matches the delegate.

When using only a single parameter, we don't need parentheses around the input paramet ers. If you thinkthe .NET language may have a problem inferring the parameters, you can specify the parameters as part o fthe input parameters. Also, lambda expressions may be expressions or statements, where the statementformat uses curly bracket scope operators and supports most C# coding elements (excluding those withanonymous functions).

If limits are part o f anonymous functions, are those same limits in place with lambda functions? Primarily,variables external to the lambda expression (known as outer scope variables) cannot be accessed if they areref or out variables. When you create a lambda expression (or an anonymous method), you're creating areference to code that may persist beyond the scope o f the enveloping block. Any variables referenced withinlambda expression are captured for the lifetime o f that lambda expression. Additionally, you cannot havejump statements (break, goto , continue) within lambda expressions.

Let's return to our code now and add an appropriate lambda expression.

Modify Fo rm1.cs as shown:

CODE TO TYPE:

.

.

.private void Lambda(){ ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score); // Using delegate Console.WriteLine("Delegate results"); ArrayList delegateResults = findResults(originalScores, 80.0F, checkValue); foreach (Result result in delegateResults) Console.WriteLine(result.ID + ": " + result.Score); // Using anonymous method Console.WriteLine("Anonymous method results"); ArrayList anonymousResults = findResults(originalScores, 80.0F, delegate(float value, float minimumValue) { return (value >= minimumValue); }); foreach (Result result in anonymousResults) Console.WriteLine(result.ID + ": " + result.Score); // Using lambda method Console.WriteLine("Lambda results"); ArrayList lambdaResults = findResults(originalScores, 80.0F, (value, minimumValue) => value >= minimumValue); foreach (Result result in lambdaResults) Console.WriteLine(result.ID + ": " + result.Score);}...

Page 265: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click to save your changes and to run your program. You see this output:

Let's discuss this code.

Page 266: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void Lambda(){ ArrayList originalScores = new ArrayList(); originalScores.Add(new Result() { ID = "1", Score = 75.5F }); originalScores.Add(new Result() { ID = "2", Score = 63.0F }); originalScores.Add(new Result() { ID = "3", Score = 82.5F }); originalScores.Add(new Result() { ID = "4", Score = 95.0F }); originalScores.Add(new Result() { ID = "5", Score = 100.0F }); // Using standard method Console.WriteLine("Method results"); foreach(Result result in originalScores) if (checkValue(result.Score, 80.0F)) Console.WriteLine(result.ID + ": " + result.Score); // Using delegate Console.WriteLine("Delegate results"); ArrayList delegateResults = findResults(originalScores, 80.0F, checkValue); foreach (Result result in delegateResults) Console.WriteLine(result.ID + ": " + result.Score); // Using anonymous method Console.WriteLine("Anonymous method results"); ArrayList anonymousResults = findResults(originalScores, 80.0F, delegate (float value, float minimumValue) value >= minimumValue); foreach (Result result in anonymousResults) Console.WriteLine(result.ID + ": " + result.Score); // Using lambda method Console.WriteLine("Lambda results"); ArrayList lambdaResults = findResults(originalScores, 80.0F, (value, minimumValue) => value >= minimumValue); foreach (Result result in lambdaResults) Console.WriteLine(result.ID + ": " + result.Score);}...

The lambda so lution is even more concise than the anonymous method. How would we read the lambdastatement we added? A common phrasing would be "value, minimum value goes to value is greater than orequal to minimum value." Another possible phrasing is "input value and minimum value returns value isgreater than or equal to minimum value."

Lambda Statements and Expressions

In our sample code we used a lambda expression, but we could have used a lambda statement. In our earlierexamples, we learned that lambda statements include curly bracket block elements. Here's how we'd convertour lambda expression to a lambda statement:

Page 267: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// ArrayList lambdaResults = findResults(originalScores, 80.0F, (value, minimumValue) => value >= minimumValue);ArrayList lambdaResults = findResults(originalScores, 80.0F, (value, minimumValue) => { return value >= minimumValue; });...

In our conversion, we added the curly brackets, a semico lon to end the statement, and the ret urn keyword toindicate that we want to return the result.

Lambda Expressions and Event Handlers

You can use lambda expressions with event handlers. We know that event handlers are delegates, soreplacing an event handler with an anonymous function or a lambda expression is a natural progression.When using an anonymous function or lambda expression with an event handler delegate, you do not have areference to the handler code. Previously when we added event handlers using delegates, we passed themethod name as a reference, enabling us to remove that method from the delegate list later. When you createan anonymous method or lambda expression, you do not have a reference to remove from the delegate listlater. So, if you're adding a method to an event handler that you'll need to remove later, continue to use themethod you've learned before. In the tutorial, we'll see the syntax for using a lambda expression with an eventhandler.

Usage

You can use a lambda expression anywhere you might use an anonymous method. Anonymous andlambda methods are both convenient fo r short, one-use-only methods. In later lessons, we'll learn morepowerful uses o f lambda expressions with generics and co llections, as well as with LINQ.

Lambda Expressions as Algorithms

Although we won't be focusing on the concept o f lambda expresions as algorithms in depth, take a minute toconsider the purpose o f anonymous methods and lambda expressions. When we use them, essentiallywe're wrapping functionality. This functionality represents a so lution, or algorithm, so when you use a lambdaexpression, you're creating an instance o f a portable algorithm. If you need to reuse this algorithm, you maynot want an inline so lution such as a lambda expression or anonymous method.

Lambda Expression Trees

A lambda expression tree is a data structure created by .NET that contains each o f the lambda expressionsrepresented in a tree-like structure. We won't cover lambda expression trees in detail during this lesson, butwe will later when we go over o ther uses o f lambda expressions.

Targets - A Coding TutorialIn this tutorial, we'll create a program that displays targets on which to click. The program will include levels where youhave to click on all o f the targets for that level four times before moving on to the next level. The number o f targetsincreases with each subsequent level, starting with one target at Level 1. Each target displays a countdown timer thatindicates when that target will disappear. If you fail to click on all o f the targets before one o f the target's countdownexpires, you have to restart that level.

In addition to learning about lambda expressions and statements, you will also learn about Named and Optionalmethod parameters, centering text on a drawn shape, and using lambda expressions with events.

User Interface Prototypes

Below are images o f the Targets program interface elements, including the UI in the Design Editor with the Filemenu, and the running program UI at Level 3:

Design Edit o r UI Wit h File Menu

Page 268: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Runt ime UI - Level 3

Page 269: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating the User Interface

Select File | New | Pro ject . Change the Name of the pro ject to T arget s and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to T arget s.

Also , change the StartPosition property to Cent erScreen. Click to save your changes.

Now let's add the contro ls we'll need.

Use this table to add and name the main contro ls:

Co nt ro l T ype Co nt ro l Name

MenuStrip mainMenuStrip

StatusStrip mainStatusStrip

PictureBox mainPictureBox

Timer gameTimer

Set the PictureBox properties to Do ck in Parent Co nt ainer, and the BorderStyle to Fixed3D. Set the Timer

properties Enabled to T rue and the Interval to 5 milliseconds. Click .

Next, let's add three Too lStripStatusLabels to the StatusStrip, and format the labels to enhance their visibility.

Use the drop-down menu on the StatusStrip to add a StatusLabel contro l. Name the contro l

hit sT o o lSt ripSt at usLabel. Set the Text property to .... Click to save your changes.

Next, let's add the menu items.

Use the table below to create the menu items displayed in the Design Editor UI. Click to save your

Page 270: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

changes.

Parent Menu Menu T ext Name Pro pert y

&File fileToo lStripMenuItem

File &Reset resetToo lStripMenuItem

File E&xit exitToo lStripMenuItem

Although we'll be adding almost all o f our event handlers using code, we need to add a handler fo r the FormShown event.

Select the Form, and add an event handler fo r the Form Shown event by selecting the Properties Event s icon,

and double-clicking on the Sho wn event. Click to save your changes.

Adding the Code

Now that we have all o f the UI elements in place, let's start coding! Start by creating a Target class that willrepresent each target object.

Right-click T arget s in the So lution Explorer, select Add | Class, enter T arget in the Name textbox, and clickAdd. Modify T arget .cs as shown:

Page 271: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Targets{ public class Target { // Public events and delegate public event EventHandler CountExpired; public delegate void TargetDrawDelegate(Size boardSize, Graphics graphics); // Private class variables private Point _location; private Size _dimensions; private Color _fillColor; private Color _textColor; private int _countDown; private int _lastTickCount; // Constructor public Target(Point location, Size dimensions, Color fillColor, Color textColor, int startingCountDown = 10) { _location = location; _dimensions = dimensions; _fillColor = fillColor; _textColor = textColor; _countDown = startingCountDown; _lastTickCount = System.Environment.TickCount; } // Public methods public void Draw(Size boardSize, Graphics graphics) { // See if countdown has expired if (_countDown > 0) { if (System.Environment.TickCount - _lastTickCount > 1000) { _countDown--; _lastTickCount = System.Environment.TickCount; } // Draw target using (SolidBrush brush = new SolidBrush(_fillColor)) graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); // Draw countdown RectangleF boundingRectangle = new RectangleF(_location.X, _location.Y, _dimensions.Width, _dimensions.Height); using (Font font = new Font("Arial", 12, FontStyle.Bold)) using (StringFormat stringFormat = new StringFormat()) using (SolidBrush brush = new SolidBrush(_textColor)) { // Align text horizontally and vertically stringFormat.Alignment = StringAlignment.Center; stringFormat.LineAlignment = StringAlignment.Center; graphics.DrawString(_countDown.ToString(), font, brush, boundingRectangle, stringFormat); } }

Page 272: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

else // Raise event OnCountExpired(EventArgs.Empty); } public bool Hit(Point location) { return new Rectangle(_location, _dimensions).Contains(location); } // Protected event handlers protected virtual void OnCountExpired(EventArgs e) { if (CountExpired != null) CountExpired(this, e); } }}

Click to save your changes.

Let's discuss this code.

Page 273: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Targets{ public class Target { // Public events and delegate public event EventHandler CountExpired; public delegate void TargetDrawDelegate(Size boardSize, Graphics graphics); // Private class variables private Point _location; private Size _dimensions; private Color _fillColor; private Color _textColor; private int _countDown; private int _lastTickCount; // Constructor public Target(Point location, Size dimensions, Color fillColor, Color textColor, int startingCountDown = 10) { _location = location; _dimensions = dimensions; _fillColor = fillColor; _textColor = textColor; _countDown = startingCountDown; _lastTickCount = System.Environment.TickCount; } // Public methods public void Draw(Size boardSize, Graphics graphics) { // See if countdown has expired if (_countDown > 0) { if (System.Environment.TickCount - _lastTickCount > 1000) { _countDown--; _lastTickCount = System.Environment.TickCount; } // Draw target using (SolidBrush brush = new SolidBrush(_fillColor)) graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); // Draw countdown RectangleF boundingRectangle = new RectangleF(_location.X, _location.Y, _dimensions.Width, _dimensions.Height); using (Font font = new Font("Arial", 12, FontStyle.Bold)) using (StringFormat stringFormat = new StringFormat()) using (SolidBrush brush = new SolidBrush(_textColor)) { // Align text horizontally and vertically stringFormat.Alignment = StringAlignment.Center; stringFormat.LineAlignment = StringAlignment.Center; graphics.DrawString(_countDown.ToString(), font, brush, boundingRectangle, stringFormat); } }

Page 274: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

else // Raise event OnCountExpired(EventArgs.Empty); } public bool Hit(Point location) { return new Rectangle(_location, _dimensions).Contains(location); } // Protected event handlers protected virtual void OnCountExpired(EventArgs e) { if (CountExpired != null) CountExpired(this, e); } }}

We reused some code and used techniques that we learned earlier; we've highlighted code that is new orcontains elements we want to discuss. We created Co unt Expired as an event that may be raised when thetarget countdown expires. In drawing the countdown value, we create the variable st ringFo rmat that's aSt ringFo rmat class that allows us to set both the Alignment fo r horizontal alignment, andLineAlignment fo r vertical alignment within a bounding rectangle. We stack our using statements to ensureproper disposal o f objects we created for drawing.

In drawing the countdown value, we use the class variable _co unt Do wn. We determine when we need todecrement _co unt Do wn using the T ickCo unt value.

Note the way we've used the st art ingCo unt Do wn parameter in the class constructor, adding = 10 after theparameter. By adding the equals sign and a value, we convert the st art ingCo unt Do wn into an optionalparameter. If the class constructor is called without a value for st art ingCo unt Do wn, then this parameter willbe set to the default value, 10 . Optional parameters can be useful. When you declare one or more optionalparameters, they must fo llow any required parameters (parameters without a default value). Once a value issupplied for an optional parameter, all optional parameters that precede that parameter in the parameter listmust also be supplied.

Next, let's add the class that manages the game, the TargetGame class.

Right-click T arget s in the So lution Explorer, select Add | Class, enter T arget Game in the Name textbox,and click Add. Modify T arget Game.cs as shown:

Page 275: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Collections;using System.Drawing;

namespace Targets{ public class TargetGame { // Private delegates private Target.TargetDrawDelegate _targetDraw; // Private class variables private ArrayList _targets = new ArrayList(); private Random _random = new Random(); private int _defaultTargetSize = 40; // Private class backing properties private Size _boardSize; private int _numberOfTargets = 0; // Public properties public Size BoardSize { set { _boardSize = value; } } public int NumberOfTargets { get { return _numberOfTargets; } set { _numberOfTargets = value; } } // Constructor public TargetGame(Size boardSize, int numberOfTargets = 1) { // Initialize game settings _boardSize = boardSize; _numberOfTargets = numberOfTargets; } // Private methods private void AddTarget() { // Create target Target target = new Target( location: new Point(_random.Next(_boardSize.Width - _defaultTargetSize), _random.Next(_boardSize.Height - _defaultTargetSize)), dimensions: new Size(_defaultTargetSize, _defaultTargetSize), fillColor: Color.Blue, textColor: Color.White); // Add target drawing method to our delegate list using shortcut syntax _targetDraw += target.Draw; // Add target _targets.Add(target); } // Public methods public void Update(Graphics graphics) { // Add targets if (_targets.Count == 0) while (_targets.Count < _numberOfTargets) AddTarget(); // Call delegate to redraw all current targets _targetDraw(_boardSize, graphics);

Page 276: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} }}

Click to save your changes.

Let's discuss this code.

Page 277: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Collections;using System.Drawing;

namespace Targets{ public class TargetGame { // Private delegates private Target.TargetDrawDelegate _targetDraw; // Private class variables private ArrayList _targets = new ArrayList(); private Random _random = new Random(); private int _defaultTargetSize = 40; // Private class backing properties private Size _boardSize; private int _numberOfTargets = 0; // Public properties public Size BoardSize { set { _boardSize = value; } } public int NumberOfTargets { get { return _numberOfTargets; } set { _numberOfTargets = value; } } // Constructor public TargetGame(Size boardSize, int numberOfTargets = 1) { // Initialize game settings _boardSize = boardSize; _numberOfTargets = numberOfTargets; } // Private methods private void AddTarget() { // Create target Target target = new Target( location: new Point(_random.Next(_boardSize.Width - _defaultTargetSize), _random.Next(_boardSize.Height - _defaultTargetSize)), dimensions: new Size(_defaultTargetSize, _defaultTargetSize), fillColor: Color.Blue, textColor: Color.White); // Add target drawing method to our delegate list using shortcut syntax _targetDraw += target.Draw; // Add target _targets.Add(target); } // Public methods public void Update(Graphics graphics) { // Add targets if (_targets.Count == 0) while (_targets.Count < _numberOfTargets) AddTarget(); // Call delegate to redraw all current targets _targetDraw(_boardSize, graphics);

Page 278: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} }}

Let's go over a couple o f new elements found in the TargetGame class code. We use another optionalparameter, numberOf T arget s, with the class constructor. We also have the introduction o f Namedarguments when creating the Target. As part o f calling the Target constructor, we've added parameter namesbefore each parameter: lo cat io n, dimensio ns, f illCo lo r and t ext Co lo r, fo llowed by a co lon. Although wekept the original parameter order, it's not required. Named parameter arguments allow us to specify any orderwe want fo r the parameters, that way we don't have to remember the exact order.

NoteYou could get really creative with parameters and specify default values for all o f the parametersto a method, which would enable you to use any o f the parameters you want, in any order, o r noparameters at all. Of course, you should have a reason for creating a method with suchversatility.

Next, let's modify the Form to start the target game.

Modify MainFo rm.cs as shown:

Page 279: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;

namespace Targets{ public partial class MainForm : Form { private TargetGame _targetGame; private int _hits = 0; private int _level = 1; public MainForm() { InitializeComponent(); } private void MainForm_Shown(object sender, EventArgs e) { // Set up status updateHitsStatus(); // Set cursor mainPictureBox.Cursor = Cursors.Cross; // Exit event event lambda expressions exitToolStripMenuItem.Click += (from, ea) => this.Close(); // Create target game object _targetGame = new TargetGame(boardSize: mainPictureBox.ClientSize); // Set up Picturebox Paint event mainPictureBox.Paint += (from, ea) => _targetGame.Update(ea.Graphics); // Set up Timer Tick event gameTimer.Tick += (from, ea) => mainPictureBox.Invalidate(); gameTimer.Start(); } private void updateHitsStatus() { // Update hits status hitsToolStripStatusLabel.Text = "Level: " + _level.ToString() + " - Hits: " + _hits.ToString(); } }}

and to run the program. A target is drawn, and disappears when the countdown reaches zero .

Let's discuss this code.

Page 280: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;

namespace Targets{ public partial class MainForm : Form { private TargetGame _targetGame; private int _hits = 0; private int _level = 1; public MainForm() { InitializeComponent(); } private void MainForm_Shown(object sender, EventArgs e) { // Set up status updateHitsStatus(); // Set cursor mainPictureBox.Cursor = Cursors.Cross; // Exit event event lambda expressions exitToolStripMenuItem.Click += (from, ea) => this.Close(); // Create target game object _targetGame = new TargetGame(boardSize: mainPictureBox.ClientSize); // Set up Picturebox Paint event mainPictureBox.Paint += (from, ea) => _targetGame.Update(ea.Graphics); // Set up Timer Tick event gameTimer.Tick += (from, ea) => mainPictureBox.Invalidate(); gameTimer.Start(); } private void updateHitsStatus() { // Update hits status hitsToolStripStatusLabel.Text = "Level: " + _level.ToString() + " - Hits: " + _hits.ToString(); } }}

Finally, some lambda expressions! We've used different co lors to highlight each o f the lambda expressions.In each case, we're creating a lambda expression for an event (Click, Paint , and T ick), adding the lambdaexpression to the event delegate list. The delegate determines the parameters and return type, so for each o fthese lambda expressions we've specified parameters (from, ea) to represent the send object and the eventarguments. For the Click event from the Exit menu item, we use a lambda expression to call the Close()method. For the Paint PictureBox event, we'll call the Update event o f the TargetGame class instance. For theT ick event o f the timer contro l, we call the PictureBox Invalidate method.

The order o f the event delegate code is important. For the Paint event, we reference the TargetGame classinstance, so it must be created before we can execute this lambda expresion.

We've also highlighted the use o f a named argument, bo ardSize , in the call to the TargetGame constructor.

Page 281: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

So far, our game doesn't do much. Let's modify the game to draw the next target after the countdown expires.

Modify T arget Game.cs as shown:

CODE TO TYPE:

.

.

.private void AddTarget(){ // Create target Target target = new Target( location: new Point(_random.Next(_boardSize.Width - _defaultTargetSize), _random.Next(_boardSize.Height - _defaultTargetSize)), dimensions: new Size(_defaultTargetSize, _defaultTargetSize), fillColor: Color.Blue, textColor: Color.White); // Add an event handler to CountExpired event for each target target.CountExpired += new EventHandler(CountExpiredEventHandler); // Add target drawing method to our delegate list using shortcut syntax _targetDraw += target.Draw; // Add target _targets.Add(target);}

private void RemoveTarget(Target target){ if (target != null) { // Remove event delegate handler for CountExpired event for this target target.CountExpired -= this.CountExpiredEventHandler; // Remove draw delegate from our delegate list _targetDraw -= target.Draw; // Remove target from ArrayList _targets.Remove(target); }}

// Protected event handlersprotected virtual void CountExpiredEventHandler(object sender, EventArgs e){ // Remove target Target target = sender as Target; if (target != null) RemoveTarget(target);}...

and to run the program. Now, when a target countdown reaches zero , a new target is drawn.

Let's discuss this code.

Page 282: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void AddTarget(){ // Create target Target target = new Target( location: new Point(_random.Next(_boardSize.Width - _defaultTargetSize), _random.Next(_boardSize.Height - _defaultTargetSize)), dimensions: new Size(_defaultTargetSize, _defaultTargetSize), fillColor: Color.Blue, textColor: Color.White); // Add an event handler to CountExpired event for each target target.CountExpired += new EventHandler(CountExpiredEventHandler); // Add target drawing method to our delegate list using shortcut syntax _targetDraw += target.Draw; // Add target _targets.Add(target);}

private void RemoveTarget(Target target){ if (target != null) { // Remove event delegate handler for CountExpired event for this target target.CountExpired -= this.CountExpiredEventHandler; // Remove draw delegate from our delegate list _targetDraw -= target.Draw; // Remove target from ArrayList _targets.Remove(target); }}

// Protected event handlersprotected virtual void CountExpiredEventHandler(object sender, EventArgs e){ // Remove target Target target = sender as Target; if (target != null) RemoveTarget(target);}...

We add the Co unt ExpiredEvent Handler and then remove the Co unt ExpiredEvent Handler from theCo unt Expired event. We also added code that performs the task o f removing the target from the ArrayList.

Now let's add the code to respond to selecting a target and resetting the game. We'll have to modify both theTargetGame class and the Form before we can respond to the target hits.

Modify T arget Game.cs as shown:

Page 283: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Public events and delegatepublic event EventHandler Hit;

public void Select(Point location){ // Loop through each balloon in ArrayList to see which was selected foreach (Target target in _targets) { if (target.Hit(location)) { OnHit(EventArgs.Empty); RemoveTarget(target); break; } }}

public void Reset(int numberOfTargets = 1){ // Reset targets _numberOfTargets = numberOfTargets; while (_targets.Count > 0) RemoveTarget(_targets[0] as Target);}

// Protected event handlersprotected virtual void OnHit(EventArgs e){ if (Hit != null) Hit(this, e);}...

Click to save your changes.

Let's discuss this code.

Page 284: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Public events and delegatepublic event EventHandler Hit;

public void Select(Point location){ // Loop through each balloon in ArrayList to see which was selected foreach (Target target in _targets) { if (target.Hit(location)) { OnHit(EventArgs.Empty); RemoveTarget(target); break; } }}

public void Reset(int numberOfTargets = 1){ // Reset targets _numberOfTargets = numberOfTargets; while (_targets.Count > 0) RemoveTarget(_targets[0] as Target);}

// Protected event handlersprotected virtual void OnHit(EventArgs e){ if (Hit != null) Hit(this, e);}...

We use the Hit event delegate in the OnHit event handler when we call the event from the Select method.

Also notice t his in the delegate call to Hit . Do you remember how this method works? We created anEvent Handler delegate that subscribers can use to be notified o f a target hit event. We initiate the process bycalling the OnHit method. We pass an empty argument that we have access to because we created the eventdelegate from Event Handler. But where does the "self-reference" t his variable come from? Whenever youcall a method, the t his instance variable is implicitly passed. But, what instance object does t his represent?And, if we have the wrong instance reference, will it impact our code? How can we find out? If we have thewrong instance variable, how should we change the code?

To answer these questions, consider the purpose o f the OnHit method: to raise an event, effectively callingany method registered with the delegate so that the subscriber can handle this event. The Hit event issupposed to be tied to a target, so we can answer one o f our questions: which instance should we bereferring to? The target object that raised the Hit event. When we look at the code in the Select method, whatdo you think the t his instance variable is referencing? Well, what class are we in? The TargetGame class.How did the Select method get called? By referencing the instance o f the TargetGame class. So, t his isreferring to the TargetGame class instance. You can verify this by setting a break po int in the OnHit methodand inspecting the t his variable.

So, we need to change the code.

Modify T arget Game.cs as shown:

Page 285: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public void Select(Point location){ // Loop through each balloon in ArrayList to see which was selected foreach (Target target in _targets) { if (target.Hit(location)) { OnHit(target, EventArgs.Empty); RemoveTarget(target); break; } }}

// Protected event handlersprotected virtual void OnHit(object sender, EventArgs e){ if (Hit != null) Hit(thissender, e);}...

Click to save your changes.

Let's discuss this code.

OBSERVE:

.

.

.// Public events and delegatepublic event EventHandler Hit;

public void Select(Point location){ // Loop through each balloon in ArrayList to see which was selected foreach (Target target in _targets) { if (target.Hit(location)) { OnHit(target, EventArgs.Empty); RemoveTarget(target); break; } }}

// Protected event handlersprotected virtual void OnHit(object sender, EventArgs e){ if (Hit != null) Hit(sender, e);}...

Now, we pass the correct instance variable, t arget when we call OnHit , and pass the correct instance object,sender, when we raise the Hit event.

Page 286: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Let's make the final modifications to MainFo rm.cs:

CODE TO TYPE:

.

.

.private int _hitsPerLevel = 4;

private void MainForm_Shown(object sender, EventArgs e){ // Set up status updateHitsStatus(); // Set cursor mainPictureBox.Cursor = Cursors.Cross; // Lambda statement for game reset resetToolStripMenuItem.Click += (from, ea) => { _hits = 0; _level = 1; _targetGame.NumberOfTargets = 1; updateHitsStatus(); _targetGame.Reset(); }; // Exit event lambda expressions exitToolStripMenuItem.Click += (from, ea) => this.Close(); // PictureBox Mouseup lambda expression mainPictureBox.MouseUp += (from, ea) => _targetGame.Select(ea.Location); // Create target game object _targetGame = new TargetGame(boardSize: mainPictureBox.ClientSize); // Lambda delegates for Hit _targetGame.Hit += (from, ea) => ++_hits; _targetGame.Hit += (from, ea) => { if (_hits == _hitsPerLevel * _targetGame.NumberOfTargets) { _hits = 0; _level++; _targetGame.NumberOfTargets++; } }; _targetGame.Hit += (from, ea) => updateHitsStatus(); // Set up Picturebox Paint event mainPictureBox.Paint += (from, ea) => _targetGame.Update(ea.Graphics); // Set up Timer Tick event gameTimer.Tick += (from, ea) => mainPictureBox.Invalidate(); gameTimer.Start(); }...

Click and to run the program. With these changes, the Target game is completed!

Let's discuss this code.

Page 287: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private int _hitsPerLevel = 4;

private void MainForm_Shown(object sender, EventArgs e){ // Setup status updateHitsStatus(); // Set cursor mainPictureBox.Cursor = Cursors.Cross; // Lambda statement for game reset resetToolStripMenuItem.Click += (from, ea) => { _hits = 0; _level = 1; _targetGame.NumberOfTargets = 1; updateHitsStatus(); _targetGame.Reset(); }; // Exit event lambda expressions exitToolStripMenuItem.Click += (from, ea) => this.Close(); // PictureBox Mouseup lambda expression mainPictureBox.MouseUp += (from, ea) => _targetGame.Select(ea.Location); // Create target game object _targetGame = new TargetGame(boardSize: mainPictureBox.ClientSize); // Lambda delegates for Hit _targetGame.Hit += (from, ea) => ++_hits; _targetGame.Hit += (from, ea) => { if (_hits == _hitsPerLevel * _targetGame.NumberOfTargets) { _hits = 0; _level++; _targetGame.NumberOfTargets++; } }; _targetGame.Hit += (from, ea) => updateHitsStatus(); // Setup Picturebox Paint event mainPictureBox.Paint += (from, ea) => _targetGame.Update(ea.Graphics); // Setup Timer Tick event gameTimer.Tick += (from, ea) => mainPictureBox.Invalidate(); gameTimer.Start();}...

We added the _hit sPerLevel class variable to set the number o f hits per level, and added a number o flambda expression "blocks." The first expression is a lambda statement that handles the Click event fo r theReset menu option. This statement illustrates accessing local variables, calling instance variable methods,and calling a private method. Next, we handle the Mo useUp event, adding a lambda expression to determineif any o f the targets were selected. The last set o f lambda expressions deal with the Hit event, illustrating thatwe can add multiple entries to our delegate event, by incrementing a local variable, calling a local method, anda rather large block o f code in a lambda statement to determine when the user may progress to the next level.

As you can see, lambda expressions add a level o f functionality that can be really versatile! Good work thislesson. See you in the next one...

Page 288: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

lesson. See you in the next one...

(Before you move on to the next lesson though, do your homework and pro jects!)

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 289: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Exceptions and Exception Handling

In this lesson we'll learn how C# handles errors (generally referred to as exceptions) that occur in your code. You'll learn how todetect and manage such exceptions, as well as have your code generate error conditions.

This lesson includes these topics:

IntroductionCreating an ExceptionTry/Catch/Finally BlockThrowing an ExceptionNested ExceptionsExtending ExceptionsCustom ExceptionsTo Catch or NotExceptions and the Using SyntaxCoding Tutorial

IntroductionWhen you're coding, both anticipated and unexpected error conditions will occur. We've already talked about the threegeneral levels o f errors: compilation errors, logic errors, and run-time exceptions. This lesson deals specifically withrun-time exceptions.

A run-time exception may occur fo r various reasons. Some exceptions are unexpected—such as a full hard disk orbroken network connection—while o thers may be anticipated, such as invalid user-entered data. In general, you wantto create code that anticipates and handles typical issues that may arise from using your so ftware, and use theso lutions you learn about in this lesson to detect and handle the unexpected errors. However, many classes stillconsider the unexpected exception to be something that can be anticipated when performing certain tasks. We'llexplore this and o ther considerations as we work through the various ways C# deals with exceptions.

Creating an ExceptionLet's code a contrived exception to demonstrate a run-time exception in C#. We'll then adapt this code to handle theexception.

Select File | New | Pro ject , and change the Name of the pro ject to Except io ns. Modify Fo rm1.cs as shown below.

Page 290: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;

namespace Exceptions{ public partial class Form1 : Form { public Form1() { InitializeComponent(); Exceptions(); } private void Exceptions() { int numerator = 10; int denominator = 0; int result; result = numerator / denominator; } }}

Click and to run the program.

Studio displays up an unhandled exception dialog box similar to the one below. The exception is a result o f attemptingto divide by zero , as the deno minat o r variable is zero .

Would you say this exception was anticipated, or unexpected? As the developer, you knew you were go ing to performdivision, so perhaps you could have tested to ensure that the denominator was not zero . Although this exception ishandled with a single line o f code, we'll use this fo r our example because it's fairly easy to generate and trap. First, let'sdiscuss how C# deals with exceptions.

Try/Catch/Finally BlockThe C# language uses exception-handling syntax similar to o ther object-oriented languages—it invo lves a t ry, acat ch, and f inally a block:

Page 291: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

try{}catch{}finally{}

How does this syntax work? Any code that you want to execute and trap for errors is placed within the t ry block.Should an error occur, execution is transferred to the cat ch block immediately. Whether an error occurs or not, thef inally block will always execute. If no error occurs, the f inally block is executed fo llowing the last line in the t ryblock. If an error occurs, the f inally block executes after the last line in the cat ch block.

When using the try/catch/finally block, we call errors that occur exceptions. Such exceptions are typically said to beraised, as they may occur in your code, or within objects used by your code. Later we'll learn that you can raise yourown exceptions, which we refer to as throwing exceptions.

When using this exception-handling syntax, the t ry block is required, but may be fo llowed by either a cat ch block, af inally block, or both blocks as we illustrated above. If you omit the cat ch block, you'll have trapped the exception, butyou will no t be able to handle it. If you omit the f inally block, you will trap the exception, but you will no t be able toperform any post-exception code. It's common to omit the f inally block if no post-exception clean-up is necessary.You must have only a single t ry block, and if included, only a single f inally block. You may have multiple cat chblocks.

Let's adjust our code to see if we can trap for this exception.

Modify Fo rm1.cs as shown:

CODE TO TYPE:

.

.

.private void Exceptions(){ int numerator = 10; int denominator = 0; int result; try { result = numerator / denominator; } catch { MessageBox.Show("An error occurred."); }}...

Click and to run the program.

Let's discuss this code.

Page 292: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void Exceptions(){ int numerator = 10; int denominator = 0; int result; try { result = numerator / denominator; } catch { MessageBox.Show("An error occurred."); }}...

When you run this code, you get a MessageBo x with the specified message, so we know the cat ch block trapped theexception.

There are several potential exception conditions you may want to handle. The above syntax only gives one specificerror message; you might want something a little more versatile. Edit Fo rm1.cs as shown:

CODE TO TYPE:

.

.

.private void Exceptions(){ int numerator = 10; int denominator = 0; int result; try { } catch (Exception e) { MessageBox.Show("An error occurred."e.Message); } finally { }}...

Click and to run the program.

Let's discuss this code.

Page 293: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

try{}catch (Exception e){ MessageBox.Show(e.Message);}finally{}

In that example, we modify the cat ch block to include an Except io n parameter e . This syntax allows our code to trapfor a specific type o f exception, and handle that specific exception. The Except io n class is the base .NET class for allexceptions, and because it is the base class, it will trap all .NET errors, or any custom errors you may create derivedfrom this base class. .NET includes dozens o f exceptions derived from the Except io n class that you can handlespecifically. The e Except io n parameter gives us access to information about the exception, such as the exceptionmessage we just saw in our example. The sample below illustrates the use o f multiple catch blocks to handle specificexceptions:

OBSERVE:

try{}catch (System.IO.IOException e){ MessageBox.Show(e.Message);}catch (DivideByZeroException e){ MessageBox.Show(e.Message);}catch (Exception e){ MessageBox.Show(e.Message);}finally{}

In the above syntax, we see how we can include as many cat ch blocks as we want. The order is important, as theexception is caught by the first matching catch. Since the Except io n class will handle all derived exceptions, if youinclude this catch block you must always place it at the end. We've illustrated a catch block for a file input/output (IO)exception, as well as a divide by zero exception. Let's modify our code again to see what happens.

Modify Fo rm1.cs as shown:

Page 294: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. try { result = numerator / denominator; } catch (Exception eSystem.IO.IOException ex) { MessageBox.Show(e.Message"IOException: " + ex.Message); } catch (DivideByZeroException ex) { MessageBox.Show("DivideByZeroException: " + ex.Message); } catch (StackOverflowException ex) { MessageBox.Show("StackOverflowException: " + ex.Message); } catch (Exception ex) { MessageBox.Show("Exception: " + ex.Message); } finally { MessageBox.Show("Finally block"); }...

Click and to run the program.

Let's discuss this code:

Page 295: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. try { result = numerator / denominator; } catch (System.IO.IOException ex) { MessageBox.Show("IOException: " + ex.Message); } catch (DivideByZeroException ex) { MessageBox.Show("DivideByZeroException: " + ex.Message); } catch (StackOverflowException ex) { MessageBox.Show("StackOverflowException: " + ex.Message); } catch (Exception ex) { MessageBox.Show("Exception: " + ex.Message); } finally { MessageBox.Show("Finally block); }...

When we run this code, we see a MessageBox indicating that the DivideByZ ero Except io n exception was handled inthe corresponding cat ch block:

Then we see by a MessagBox indicating that the f inally block executed.

Handling exceptions is pretty straightforward in C#. Go ahead and experiment with this code by commenting out thedivide by zero catch block, and confirming that the Exception catch block now handles the exception. See what o therinformation you can access from the Exception object, such as StackTrace, or Source.

Throwing an ExceptionRather than creating an exception, you can also throw an exception by using the C# t hro w keyword and creating aninstance o f an exception object. You may recall that we used the event handler delegate wizard when adding an eventhandler using the addition assignment (+=) operator; the generated event handler method included t hro w newNo t Implement edExcept io n() code. Let's change our code to throw an exception.

Modify the Fo rm1.cs class code as shown:

NoteYou should see an "Unreachable code detected" warning in your code that indicates the result calculationline o f code is effectively unreachable because o f the unconditional throw statement. Ignore that warningfor now.

Page 296: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void Exceptions(){ int numerator = 10; int denominator = 0; int result; try { throw new Exception("Let's throw an exception"); result = numerator / denominator; } catch (System.IO.IOException ex) { MessageBox.Show("IOException: " + ex.Message); } catch (DivideByZeroException ex) { MessageBox.Show("DivideByZeroException: " + ex.Message); } catch (StackOverflowException ex) { MessageBox.Show("StackOverflowException: " + ex.Message); } catch (Exception ex) { MessageBox.Show("Exception: " + ex.Message); } finally { MessageBox.Show("Finally block); }}...

Click and to run the program.

You see this MessageBox:

Let's discuss this code:

Page 297: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void Exceptions(){ int numerator = 10; int denominator = 0; int result; try { throw new Exception("Let's throw an exception"); result = numerator / denominator; } catch (System.IO.IOException ex) { MessageBox.Show("IOException: " + ex.Message); } catch (DivideByZeroException ex) { MessageBox.Show("DivideByZeroException: " + ex.Message); } catch (StackOverflowException ex) { MessageBox.Show("StackOverflowException: " + ex.Message); } catch (Exception ex) { MessageBox.Show("Exception: " + ex.Message); } finally { MessageBox.Show("Finally block); }}...

We can create an Except io n object "on-the-fly" to initiate the exception-handling code. Although you should try tohandle anticipated exceptions in your code as efficiently as possible, throwing an exception is a standard syntax fordealing with exceptions, anticpated or unexpected, that are more challenging to handle and represent an exceptioncondition.

Nested ExceptionsThe exception handling syntax supports nesting, like this:

Page 298: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

try{ try { } catch (DivideByZeroException ex) { } finally { }} catch (Exception ex){} finally{}

In this sample code, the inner nested exception handling block would attempt to handle any exceptions. If fo r somereason the inner nested exception block fails to handle the exception, the inner finally block would still execute, butthen the outer exception handling block would attempt to handle the exception. So long as the exception was onederived from the .NET Exception class, the exception would be handled. Exception handling for all C# programmingproceeds in a similar manner, where local scope is searched to determine whether an exception handling block orblocks are present to handle the exception. If the exception is not handled, the call chain is stepped through one levelat a time, continuing to search for any compatible exception handling. If no exception handler is found, .NET itselfwraps every program with an exception handling block, which is how we received the unhandled exception dialog atthe beginning o f this lesson. Unhandled exceptions aren't really unhandled, as .NET handles them, but they areconsidered unhandled because a running program (not debugged within Studio) would exhibit the unhandledexception dialog box and then terminate immediately.

Each block within the exception handling system creates a local scope for variables. In the last code example, we usethe same variable, ex, fo r the exceptions, without conflict from .NET because each ex parameter is contained within itsown local scope. From our divide by zero coding sample, we defined numerator and denominator outside theexception handling blocks, giving us access to these variables should an exception occur, o r if we have no errors, butwant to use the variables later in our code.

TipWhile we used the example o f throwing an exception using the base Exception class, it's a C# best practicenot to use the base Exception class when throwing a new exception. Instead, you should create your ownexceptions.

Extending ExceptionsOne property o f the Exception class is the Data object. This property effectively allows us to add custom information toan exception that we throw. Let's modify our test program to provide information about the numerator value.

Modify Fo rm1.cs as shown:

Page 299: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void Exceptions(){ int numerator = 10; int denominator = 0; int result; try { throw new Exception("Let's throw an exception"); if (denominator == 0) { DivideByZeroException exception = new DivideByZeroException("Invalid denominator (0)"); exception.Data.Add("Numerator", numerator); throw exception; } else result = numerator / denominator; } catch (System.IO.IOException ex) { MessageBox.Show("IOException: " + ex.Message); } catch (DivideByZeroException ex) { if (ex.Data != null && ex.Data["Numerator"] != null) MessageBox.Show(ex.Message + ", numerator: " + ex.Data["Numerator"].ToString()); else MessageBox.Show("DivideByZeroException: " + ex.Message); } catch (StackOverflowException ex) { MessageBox.Show("StackOverflowException: " + ex.Message); } catch (Exception ex) { MessageBox.Show("Exception: " + ex.Message); } finally { MessageBox.Show("Finally block); }}...

Click and to run the program.

Let's discuss this code:

Page 300: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void Exceptions(){ int numerator = 10; int denominator = 0; int result; try { if (denominator == 0) { DivideByZeroException exception = new DivideByZeroException("Invalid denominator (0)"); exception.Data.Add("Numerator", numerator); throw exception; } else result = numerator / denominator; } catch (System.IO.IOException ex) { MessageBox.Show("IOException: " + ex.Message); } catch (DivideByZeroException e) { if (e.Data != null && e.Data["Numerator"] != null) MessageBox.Show(e.Message + ", numerator: " + e.Data["Numerator"].ToString()); else MessageBox.Show("DivideByZeroException: " + e.Message); } catch (StackOverflowException ex) { MessageBox.Show("StackOverflowException: " + ex.Message); } catch (Exception ex) { MessageBox.Show("Exception: " + ex.Message); } finally { MessageBox.Show("Finally block); }}...

We created an instance o f the DivideByZ ero Except io n exception class, used the Add method o f the Dat a propertyto add a custom item named Numerat o r, then set the value o f this new item to the numerat o r value. When theexception is thrown, we confirm in the cat ch block, that Dat a is not null, that the Numerat o r item exists, and thenoutput the value as part o f our exception MessageBox. You may use this method to add custom information to anyexceptions you raise as well.

Custom ExceptionsAlthough you can extend any Exception-derived exception using the Data property, you may want to create your ownexceptions. Why? You may want to make your code easier to fo llow by having catch blocks with your custom errordescriptions. Or, you may want to provide custom behavior when an exception occurs. Let's modify our test code toinclude a custom exception, and create two new methods to throw and handle this custom exception.

Modify Fo rm1.cs as shown:

Page 301: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public Form1(){ InitializeComponent(); // Exceptions(); CustomExceptions();}

private void CustomExceptions(){ try { TestCustomExceptions(); } catch (MyException ex) { MessageBox.Show(ex.Message); }}

private void TestCustomExceptions(){ throw new MyException("Test custom exception");}

class MyException : ApplicationException{ public MyException(string exceptionText) : base(exceptionText) { } public MyException(string exceptionText, Exception innerException) : base(exceptionText, innerException) { }}...

Click and to run the program.

Let's discuss this code:

Page 302: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void CustomExceptions(){ try { TestCustomExceptions(); } catch (MyException ex) { MessageBox.Show(ex.Message); }}

private void TestCustomExceptions(){ throw new MyException("Test custom exception");}

class MyException : ApplicationException{ public MyException(string exceptionText) : base(exceptionText) { } public MyException(string exceptionText, Exception innerException) : base(exceptionText, innerException) { }}...

In this example, we created a new class, MyExcept io n, derived from the .NET class Applicat io nExcept io n. TheApplicat io nExcept io n class ultimately derives from the .NET Exception class. In our new Exception class weincluded the typical constructors that you would implement when creating your own exception class. The firstconstructor only accepts a string parameter, except io nT ext , and calls the base class constructor. The secondconstructor also requires a string parameter, but additionally accepts an Exception object as the innerExcept io nparameter. Another property o f the Exception class is the InnerException property. If an exception was raised as part o fa chain o f exceptions, the InnerException will contain the outer exception prio r to the current exception. We provide thissecond constructor to support this mechanism.

With our custom exception class, all that remains is to throw our MyExcept io n exception.

TipWhen creating your own custom exception classes, you will want to create exceptions that are very specific,or "shallow," so that the thrown exception identifies exactly which type o f exception your program hasencountered.

To Catch or NotUltimately, you'll want to know when to trap and when not to trap for exceptions. We'll go over a few best practicesguidelines.

Avo id the temptation to use try/catch exception handling for program contro l. For example, if you're go ing to attempt toopen a file, you could wrap the file open attempt inside a try/catch block, and when the open fails, respond accordingly.A better approach whould be to test to determine whether the file exists rather than trap the error. When programmingrelated to files, think about what could happen to a file, and then code to respond to these possibilities. Even with all o fyour anticipation, an unexpected exception is still possible. Consider the possibility that you're attempting to write to afile over a network, and you've successfully opened the file, and even written to the file, when the network connection isbroken. Subsequent attempts to write to the file will result in an unexpected exception. Even though the broken networkconnection is unexpected, such unexpected events might happen when dealing with a file, so wrapping your file read orwrite code with a try/catch block does make sense, but such a block should be used as it is meant to be used, that is, inresponse to an unexpected event.

For some programming languages, the mere inclusion o f the try/catch block impacts the performance o f the code. InC#, you do not have such a heavy burden, although there may be instances where the .NET JIT (Just In Time) compiler

Page 303: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

may be prevented from completely optimizing the final executing code. Still, you should use try/catch/finally blockswhen you need them to trap the unexpected, or raise conditions that you consider exceptions, that you could notmanage efficiently without using exception handling, and no readily available programming so lution works. As thedeveloper, you'll understand the intended function o f your code, test your code for likely error conditions, and providetry/catch/finally blocks to trap the unexpected. You may consider wrapping any entry po int code with exception handlingto manage possible exceptions without having to resort to the .NET provided handler that eventually terminates theapplication.

Exceptions and the Using SyntaxWe've already learned to use the using syntax when dealing with a class that implements the IDisposable interface.We also learned to identify such objects by determining whether the object contains a Dispose method. Effectively, theusing syntax implements a f inally block, ensuring the call o f the Dispose method to properly destroy the object.When you work with exception handling blocks, you want to consider the best way to implement the try/catch/finallyblock. In the code below, assume that the variables represent data related to the names o f the variables:

OBSERVE:

using (SolidBrush brush = new SolidBrush(_fillColor)){ graphics.FillEllipse(brush, new Rectangle(_location, _dimensions));}

In this example, we wrapped the creation and usage o f a So lidBrush class with the using syntax. This syntaxensures that once it leaves the scope o f the using block, the Dispose method o f the brush object will be called. Now,let's consider using an exception-handling block with this code where we can either wrap our using block with anexception-handling block, or we can include our exception-handling block within the using block.

OBSERVE:

// Wrapping usingtry{ using (SolidBrush brush = new SolidBrush(_fillColor)) { graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); }}catch (Exception ex){}finally{}

// Wrapping exceptionusing (SolidBrush brush = new SolidBrush(_fillColor)){ try { graphics.FillEllipse(brush, new Rectangle(_location, _dimensions)); } catch (Exception ex) { } finally { }}

You may elect to use either fo rmat in your code, or to simplify the nesting by using the code below instead. (In thiscode, we do not use the using syntax):

Page 304: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

// Explicitly calling DisposeSolidBrush brush = null;try{ brush = new SolidBrush(_fillColor); graphics.FillEllipse(brush, new Rectangle(_location, _dimensions));}catch (Exception ex){}finally{ if (brush != null) brush.Dispose();}

Coding TutorialSurprise—no coding tutorial! We've worked through these exception handling blocks with a real example. Now we'llbegin to add them to our tutorials, so we're not go ing to create a separate tutorial fo r this lesson. You'll also have achance to use exception handling in the lesson pro ject.

Before you move on to the next lesson, do your homework and all o f that good stuff. See you in the next lesson...

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 305: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Collections and Generics

In this lesson we'll cover co llections, specifically, we'll introduce "generics," the recommended version o f co llections. We'lldiscuss the benefits o f generics over traditional co llections, as well as how to use them.

This lesson includes these topics:

Collections and GenericsCoins - A Coding Tutorial

Collections and Generics

Introduction

So far we've used the ArrayList co llection class from the System.Collections assembly. Each time we usedthis co llection class we had to add the appropriate using statement. .NET introduced a new version o fco llection classes, but until now we've avo ided using them. You might have noticed that whenever we createda new pro ject, the default Form1.cs class included the System.Collections.Generic assembly. Whenever weadded a class, System.Collections.Generic assembly was also included by default. This assembly definesthe generics classes that we prefer over o lder co llection classes. The generics classes are used asco llections, or to create our own co llection classes. Generics allow us to maximize the reuse o f code,provide type safety, and have significantly improved performance over the o lder co llection classes. Let's takea closer look at generics.

ArrayList vs. List Generic

To use generics, we need to introduce a generic t ype paramet er indicated by using the angle brackets: <and >. We may use the generic type parameter in a number o f scenarios. For example, we can use any o f thegeneric classes as co llection classes. We may also use generics with class and struct definitions and withmethods. We'll cover creating our own objects with generics a bit later, but first let's take a look at thereplacement generic co llection classes.

Primarily, we've used the ArrayList class for co llections. With generics, we would use the List generic classinstead. Let's create a sample co llection using an ArrayList, and the equivalent code using the generic Listclass.

For this lesson, create a test pro ject using File | New | Pro ject , and change the Name of the pro ject toCo llect io ns. Modify Fo rm1.cs as shown:

Page 306: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Collections;

namespace Collections{ public partial class Form1 : Form { public Form1() { InitializeComponent(); Generics(); } private void Generics() { ArrayList myArrayList = new ArrayList(); myArrayList.Add("Tom"); myArrayList.Add("Mary"); myArrayList.Add("Joanne"); List<string> myGenericList = new List<string>(); myGenericList.Add("Tom"); myGenericList.Add("Mary"); myGenericList.Add("Joanne"); foreach (string name in myGenericList) Console.WriteLine("Name: " + name); } }}

Click and to run the program. You see this in the Output window:

Let's discuss this code:

Page 307: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Collections;

namespace CSharp3_Lesson11{ public partial class Form1 : Form { public Form1() { InitializeComponent(); Generics(); } private void Generics() { ArrayList myArrayList = new ArrayList(); myArrayList.Add("Tom"); myArrayList.Add("Mary"); myArrayList.Add("Joanne"); List<string> myGenericList = new List<string>(); myGenericList.Add("Tom"); myGenericList.Add("Mary"); myGenericList.Add("Joanne"); foreach (string name in myGenericList) Console.WriteLine("Name: " + name);

} }}

We use List similarly to the way we use ArrayList, except that we've specified a generic type parameter,st ring, within the <> angle brackets. The general fo rmat o f a generic class is illustrated below, where Trepresents a data type.

OBSERVE:

GenericClass<T>

The ability to specify a data type for our generic co llections is a fundamental difference from the o lderco llections. With the ArrayList, we could add any data type to the co llection, but with generic co llections, wespecified a data type when we created the co llection, limiting the co llection so that it could contain only itemsthat are o f that data type, or that may be cast from a compatible data type.

You're probably thinking, "So, why would we want to use generic co llections when the ArrayList is so muchmore versatile?" We can perform the same types o f operations on generic co llections as with the o lderco llections, such as add, delete, search, or sort, but the generic co llections are limited to the data type o f theco llection. We could also specify a data type o f object with our generic co llection to once again have theability to add any data type derived from object, so where's the benefit in specifying a data type with genericco llections? The ability to require a data type prevents the need to cast to and from data types constantly, andensures that we know the data type o f the item within our co llection. This fundamental capability ensures type-safety at compile time.

In addition to compile time type-safety, generics are also much more efficient. Why? Since an ArrayList willsupport any data type, when we add data that is inherently value data type, we need to treat this data as anobject. When we add value data type values to an ArrayList, .NET wraps the value data type to create an

Page 308: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

object. As we've already discussed, this process is called bo xing. Unwrapping the value data type from theobject is called unbo xing. This boxing and unboxing process comes at a price. By using generic co llections,we avo id the boxing and unboxing process completely.

Other Generic Collection Classes

The List class is not the only generic co llection class. Here's a list o f generic classes that you may find useful:

List : Represents a strongly typed list o f objects that can be accessed by indexDict io nary: Represents a co llection o f keys and valuesSo rt edDict io nary: Represents a co llection o f keys and values sorted on the keyLinkedList : Represents a doubly linked listQueue : Represents a first- in, first-out (FIFO) co llectionSt ack: Represents a variable size last- in, first-out (LIFO) co llectionHashSet : Represents an optimized co llection o f unique objects that performs faster than List

We'll be using the List class in the tutorial fo r this lesson.

Creating Generic Classes

In addition to using the provided generic classes, we can create our own generic classes. Let's add our ownclass, and create a new method that we can call to use this new generic class.

Modify the Fo rm1.cs class code as shown:

Page 309: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public partial class Form1 : Form{ public Form1() { InitializeComponent(); Generics(); ClassGenerics(); } private void ClassGenerics() { // Create a string instance of the generic class, default constructor MyList<string> myStringList = new MyList<string>(); myStringList.Add("Rabbit"); myStringList.Add("Squirrel"); Console.WriteLine("Count: " + myStringList.Count); Console.WriteLine("Element 1: " + myStringList[1]); } . . .}

public class MyList<T>{ // Internal generic list private List<T> _myList = new List<T>();

// Indexer public T this[int index] { get { if (_myList.Count > 0 && index < _myList.Count) return _myList[index]; else return default(T); } }

// Accessors public int Count { get { return _myList.Count; } } // Public methods public void Add(T tData) { _myList.Add(tData); }}

Click and to run the program. You see the fo llowing in the Output window:

Page 310: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Let's discuss this code:

Page 311: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public partial class Form1 : Form{ public Form1() { InitializeComponent(); Generics(); ClassGenerics(); } private void ClassGenerics() { // Create a string instance of the generic class, default constructor MyList<string> myStringList = new MyList<string>(); myStringList.Add("Rabbit"); myStringList.Add("Squirrel"); Console.WriteLine("Count: " + myStringList.Count); Console.WriteLine("Element 1: " + myStringList[1]); } . . .}

public class MyList<T>{ // Internal generic list private List<T> _myList = new List<T>(); // Indexer public T this[int index] { get { if (_myList.Count > 0 && index < _myList.Count) return _myList[index]; else return default(T); } } // Accessors public int Count { get { return _myList.Count; } } // Public methods public void Add(T tData) { _myList.Add(tData); }}

We define a class called MyList , fo llowed by the generic type parameter angle brackets. As part o f the generictype parameter, we specify a generic parameter placeho lder T . The T indicates a data type. We create ast ring instance, mySt ringList , o f the MyList generic class in the ClassGenerics method. We call the Addclass method to add data o f the correct data type to the class generic List _myList . The Add method usesthe T generic place ho lder, indicating that whatever type we use when we instantiate our generic class, theAdd method requires a parameter o f the same type.

Next, the Co unt method is called, fo llowed by a call to the indexer t his method. This method returns anelement o f the _myList List class variable, which specifies a T return type.

Page 312: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

element o f the _myList List class variable, which specifies a T return type.

Let's extend this generic class a bit further.

Modify the Fo rm1.cs class code as shown:

Page 313: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

public partial class Form1 : Form{ . . . private void ClassGenerics() { // Create a string instance of the generic class, default constructor MyList<string> myStringList = new MyList<string>(); myStringList.Add("Rabbit"); myStringList.Add("Squirrel"); Console.WriteLine("Count: " + myStringList.Count); Console.WriteLine("Element 1: " + myStringList[1]); // Create an int instance of the generic class, default constructor MyList<int> myIntListOne = new MyList<int>(); Console.WriteLine("Default: " + myIntListOne.TData); // Create another int instance, single parameter constructor MyList<int> myIntListTwo = new MyList<int>(45); Console.WriteLine("Single: " + myIntListTwo.TData); myIntListTwo.Add(10); myIntListTwo.Add(50); myIntListTwo.Add(76); foreach (int i in myIntListTwo) Console.WriteLine("I: " + i); } . . .}

public class MyList<T>{ // Internal generic list private List<T> _myList = new List<T>(); // Private backing class variables private T _tData; // Indexer public T this[int index] { get { if (_myList.Count > 0 && index < _myList.Count) return _myList[index]; else return default(T); } }

// Accessors public int Count { get { return _myList.Count; } } public T TData { get { return _tData; } } // Constructors public MyList() { _tData = default(T); } public MyList(T tData) {

Page 314: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_tData = tData; } // Iterator (foreach) public IEnumerator<T> GetEnumerator() { return _myList.GetEnumerator(); } // Public methods public void Add(T tData) { _myList.Add(tData); }}

Click and to run the program. You see the fo llowing in the Output window:

Let's discuss this code:

Page 315: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

public partial class Form1 : Form{ . . . private void ClassGenerics() { // Create a string instance of the generic class, default constructor MyList<string> myStringList = new MyList<string>(); myStringList.Add("Rabbit"); myStringList.Add("Squirrel"); Console.WriteLine("Count: " + myStringList.Count); Console.WriteLine("Element 1: " + myStringList[1]); // Create an int instance of the generic class, default constructor MyList<int> myIntListOne = new MyList<int>(); Console.WriteLine("Default: " + myIntListOne.TData); // Create another int instance, single parameter constructor MyList<int> myIntListTwo = new MyList<int>(45); Console.WriteLine("Single: " + myIntListTwo.TData); myIntListTwo.Add(10); myIntListTwo.Add(50); myIntListTwo.Add(76); foreach (int i in myIntListTwo) Console.WriteLine("I: " + i); } . . .}

public class MyList<T>{ // Internal generic list private List<T> _myList = new List<T>(); // Private backing class variables private T _tData; // Indexer public T this[int index] { get { if (_myList.Count > 0 && index < _myList.Count) return _myList[index]; else return default(T); } } // Accessors public int Count { get { return _myList.Count; } } public T TData { get { return _tData; } } // Constructors public MyList() { _tData = default(T); } public MyList(T tData) {

Page 316: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

_tData = tData; } // Iterator (foreach) public IEnumerator<T> GetEnumerator() { return _myList.GetEnumerator(); } // Public methods public void Add(T tData) { _myList.Add(tData); }}

We added two constructors to the MyList class to illustrate that generic classes use the generic parameter T .We included a private class variable _t Dat a, also o f type T , to illustrate that the generic class also has theability to use the generic type for class variables, and a method to deal with null. We've learned that referencedata types are nullable (may be set to null), but value data types are not. With a generic class, a consumer o four class may use either a value data type or a reference type. C# deals with this issue by introducing a newkeyword: def ault . We use this keyword in one o f our constructors, assigning def ault (T ) to _t Dat a,effectively letting .NET determine at runtime whether the data type is a value (in which case we assign 0), o r if itis a reference data type (in which case we assign null). We added a class property, T Dat a, to get access tothe _t Dat a value.

The last addition to the MyList class is the Get Enumerat o r method. This method allows us to use thef o reach syntax with the MyList class. This method returns the generic interface IEnumerat o r specificallyfor type T , by calling the Get Enumerat o r method o f the _myList generic List instance.

Before we move on to the tutorial, we want to introduce one more feature o f generic classes: constraints.We'll be running into contraints in the tutorial. When we declare a generic class, we can apply a constraint towhich data types may be used to instantiate an instance o f the generic class, using the where keyword. Thiscode illustrates this feature:

OBSERVE:

public class MyList<T> where T : constraint{...}

The constraint can be one o f these different constraint types:

where T : structwhere T : classwhere T : new()where T : <base class name>where T : <interface name>where T : U

In our tutorial, we'll use the "where T : <base class name>" constraint, which means that the generic classmay only be used with data types that have to be o f a specific base class or classes derived from that baseclass. We won't be go ing over all constraints, but you can learn more at Constraints on Type Parameters.

Let's move on to the tutorial...

Coins - A Coding TutorialIn this coding tutorial, we'll create a game in which you add co ins in an attempt to match a target amount o f money.We'll combine material we've learned in prio r lessons with the generics material from this lesson.

Page 317: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

User Interface Prototypes

Here's the image o f the Coins program running program UI:

Runt ime UI

Creating the User Interface

Let's start by creating our user interface.

Select File | New | Pro ject . Change the Name of the pro ject to Co ins and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 title bar's Text property to Co ins. Also ,

change the StartPosition property to Cent erScreen. Click to save your changes.

Now let's add the contro ls we'll need.

Use this table and the pro to type image to add and name the main contro ls:

Co nt ro l T ype Co nt ro l Name T ext Pro pert y

PictureBox moneyPictureBox

Button addPennyButton Add Penny

Button addNickelButton Add Nickel

Button addDimeButton Add Dime

Button addQuarterButton Add Quarter

Button resetButton Reset

Button exitButton Exit

Label targetTextLabel Target Amount:

Label targetValueLabel 0

Label currentTextLabel Current Amount:

Label currentValueLabel 0

Page 318: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Label countPennyLabel 0

Label countNickelLabel 0

Label countDimeLabel 0

Label countQuarterLabel 0

Label matchStatusLabel ...

Label clickCoinTextLabel Click co in in list to remove

Set the PictureBox BorderStyle property to FixedSingle . Click .

Although we'll be adding almost all o f our event handlers using code, we need to add a handler fo r the FormShown event.

Select the Form, and add an event handler fo r the Form Shown event by selecting the Properties Event s icon,

and double-clicking on the Sho wn event. Click to save your changes.

Adding the Code

Now that we have all o f the UI elements added, we can start coding. Create a Money class that will serve asthe base class o f the money classes we'll use in our program.

Right-click Co ins in the So lution Explorer, select Add | Class, enter Mo ney in the Name textbox, and clickAdd. Modify Mo ney.cs as shown:

Page 319: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Coins{ public class Money { // Private backing class variables private decimal _worth; private Point _location; private Size _dimensions; private string _text; private Color _color; // Accessors public decimal Worth { get { return _worth; } } public Point Location { get { return _location; } set { _location = value; } } public Size Dimensions { get { return _dimensions; } set { _dimensions = value; } } public string Text { get { return _text; } set { _text = value; } } public Color Color { get { return _color; } set { _color = value; } } // Constructors public Money(decimal worth, string text, Color color) { _worth = worth; _text = text; _color = color; } // Public methods public bool Hit(Point location) { return new Rectangle(_location, _dimensions).Contains(location); } }}

Click to save your changes.

We added the System.Drawing namespace, and we compacted the accessor code lines to take up lessspace.

Next, let's add four classes that will represent each o f our co ins. We could add four new classes, but fo r thislesson, we'll show how to place multiple classes in a single physical file instead.

Modify Mo ney.cs as shown:

Page 320: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Coins{ public class Money { . . . } public class Penny : Money { public Penny() : base(0.01m, "Penny", Color.Brown) { } } public class Nickel : Money { public Nickel() : base(0.05m, "Nickel", Color.Silver) { } } public class Dime : Money { public Dime() : base(0.10m, "Dime", Color.SlateGray) { } } public class Quarter : Money { public Quarter() : base(0.25m, "Quarter", Color.Gold) { } }}

Click to save your changes.

Let's discuss this code.

Page 321: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Coins{ public class Money { . . . } public class Penny : Money { public Penny() : base(0.01m, "Penny", Color.Brown) { } } public class Nickel : Money { public Nickel() : base(0.05m, "Nickel", Color.Silver) { } } public class Dime : Money { public Dime() : base(0.10m, "Dime", Color.SlateGray) { } } public class Quarter : Money { public Quarter() : base(0.25m, "Quarter", Color.Gold) { } }}

Each o f the four co in classes contains a constructor that calls the base Mo ney class constructor. Should weput these co in classes in the Money.cs class file, o r should they have their own separate file? This code willwork, so we're go ing to leave these classes in this single file; generally you should consider placing any non-private or internal classes in their own files. In the Money.cs file, all o f the classes are public, so placing themin their own files would be recommended, despite the fact that the co in classes are small.

Next, let's add a generic co llection class that will pull together our co in classes.

Right-click Co ins in the So lution Explorer, select Add | Class, enter Co unt er in the Name textbox, and clickAdd. Modify Co unt er.cs as shown:

Page 322: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace Coins{ public class Counter<T> where T : Money { // Private class variables private List<T> _counterList; // Private backing class variables private decimal _totalWorth = 0.00m; // Accessors public decimal TotalWorth { get { return _totalWorth; } } public int Count { get { return _counterList.Count; } } // Indexers public T this[int index] { get { if (_counterList.Count > 0 && index < _counterList.Count) return _counterList[index]; else return default(T); } } // Constructors public Counter() { _counterList = new List<T>(); } // Public methods public void Add(T t) { _counterList.Add(t); _totalWorth += t.Worth; } // Public enumerator public IEnumerator<T> GetEnumerator() { return _counterList.GetEnumerator(); } public void Reset() { _counterList.Clear(); _totalWorth = 0.00m; } public void Remove(T t) { T foundT = _counterList.Find(item => item == t); if (foundT != null) { _totalWorth -= foundT.Worth; _counterList.Remove(foundT); } } }

Page 323: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

}

Click to save your changes.

Let's discuss this code:

Page 324: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace Coins{ public class Counter<T> where T : Money { // Private class variables private List<T> _counterList; // Private backing class variables private decimal _totalWorth = 0.00m; // Accessors public decimal TotalWorth { get { return _totalWorth; } } public int Count { get { return _counterList.Count; } } // Indexers public T this[int index] { get { if (_counterList.Count > 0 && index < _counterList.Count) return _counterList[index]; else return default(T); } } // Constructors public Counter() { _counterList = new List<T>(); } // Public methods public void Add(T t) { _counterList.Add(t); _totalWorth += t.Worth; } // Public enumerator public IEnumerator<T> GetEnumerator() { return _counterList.GetEnumerator(); } public void Reset() { _counterList.Clear(); _totalWorth = 0.00m; } public void Remove(T t) { T foundT = _counterList.Find(item => item == t); if (foundT != null) { _totalWorth -= foundT.Worth; _counterList.Remove(foundT); } } }

Page 325: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

}

The Co unt er class uses the generic type parameter T , making this class a generic class. We've also utilizedthe where contraint to limit the data types that may be used to instantiate the Co unt er class to the Mo neyclass, or any Mo ney derived classes. We will in fact do both.

In addition to Co unt er being a generic class, it also instantiates _co unt erList , a List standard genericco llection. In declaring _co unt erList we use the T class type parameter to ensure that the co llectioncontains compatible data. We have also added a t his indexer method to allow easy access to the underlyingclass co llection data, employing the def ault keyword to ensure setting value types to 0 , or reference types tonull. Since we've constrained our class to allow only Mo ney data types, we could have just used null,because Mo ney is a reference type.

You should note the difference uses o f the T class type parameter, such as in the constructor, when addingelements in the Add method, and again when removing data in the Remo ve method. In Remo ve , we alsoutilize the List generic co llection Find method, using a lambda expression to locate the correct element fo rremoval.

We added the class variable _t o t alWo rt h to help track the to tal value o f all o f the added co ins, using thedecimal data type suffix m (we could also have used M).

Now, we need to add a class to manage the co in game.

Right-click Co ins in the So lution Explorer, select Add | Class, enter Co inGame in the Name textbox, andclick Add. Modify Co inGame.cs as shown:

Page 326: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Coins{ public class CoinGame { // Private class variables private Counter<Money> _myCounter = new Counter<Money>(); private int _coinHeight = 20; private Random _random = new Random(); // Private backing class variables private decimal _targetAmount = 0.00m; // Accessors public decimal TargetAmount { set { _targetAmount = value; } get { return _targetAmount; } } public decimal TotalWorth { get { return _myCounter.TotalWorth; } } // Constructors public CoinGame() { SetTargetAmount(); } // Public methods public void Add(Money money) { _myCounter.Add(money); } public void Update(Graphics graphics, Size boardSize) { // Note using for loop to allow updating money for (int i = 0; i < _myCounter.Count; i++) { Money money = _myCounter[i]; // Set location int startingY = boardSize.Height - (i * (_coinHeight + 1)) - _coinHeight - 1; money.Location = new Point(1, startingY); money.Dimensions = new Size(boardSize.Width - 2, _coinHeight); // Draw coin using (SolidBrush solidBrush = new SolidBrush(money.Color)) graphics.FillRectangle(solidBrush, new Rectangle(money.Location, money.Dimensions)); // Draw text RectangleF boundingRectangle = new RectangleF(money.Location.X, money.Location.Y, money.Dimensions.Width, money.Dimensions.Height); using (Font font = new Font("Arial", 12, FontStyle.Bold)) using (StringFormat stringFormat = new StringFormat()) using (SolidBrush brush = new SolidBrush(Color.White)) { // Align text horizontally and vertically stringFormat.Alignment = StringAlignment.Center; stringFormat.LineAlignment = StringAlignment.Center; graphics.DrawString(money.Text, font, brush, boundingRectangle, stringFormat); }

Page 327: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} } public void Remove(Point location) { // Loop through each coin to see which was selected foreach (Money money in _myCounter) { if (money.Hit(location)) { _myCounter.Remove(money); break; } } } public void Reset() { _myCounter.Reset(); SetTargetAmount(); } // Private methods private void SetTargetAmount() { _targetAmount = _random.Next(1, 100) / 100.00m; } }}

Click to save your changes.

Let's discuss this code.

Page 328: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Drawing;

namespace Coins{ public class CoinGame { // Private class variables private Counter<Money> _myCounter = new Counter<Money>(); private int _coinHeight = 20; private Random _random = new Random(); // Private backing class variables private decimal _targetAmount = 0.00m; // Accessors public decimal TargetAmount { set { _targetAmount = value; } get { return _targetAmount; } } public decimal TotalWorth { get { return _myCounter.TotalWorth; } } // Constructors public CoinGame() { SetTargetAmount(); } // Public methods public void Add(Money money) { _myCounter.Add(money); } public void Update(Graphics graphics, Size boardSize) { // Note using for loop to allow updating money for (int i = 0; i < _myCounter.Count; i++) { Money money = _myCounter[i]; // Set location int startingY = boardSize.Height - (i * (_coinHeight + 1)) - _coinHeight - 1; money.Location = new Point(1, startingY); money.Dimensions = new Size(boardSize.Width - 2, _coinHeight); // Draw coin using (SolidBrush solidBrush = new SolidBrush(money.Color)) graphics.FillRectangle(solidBrush, new Rectangle(money.Location, money.Dimensions)); // Draw text RectangleF boundingRectangle = new RectangleF(money.Location.X, money.Location.Y, money.Dimensions.Width, money.Dimensions.Height); using (Font font = new Font("Arial", 12, FontStyle.Bold)) using (StringFormat stringFormat = new StringFormat()) using (SolidBrush brush = new SolidBrush(Color.White)) { // Align text horizontally and vertically stringFormat.Alignment = StringAlignment.Center; stringFormat.LineAlignment = StringAlignment.Center; graphics.DrawString(money.Text, font, brush, boundingRectangle, stringFormat); }

Page 329: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} } public void Remove(Point location) { // Loop through each coin to see which was selected foreach (Money money in _myCounter) { if (money.Hit(location)) { _myCounter.Remove(money); break; } } } public void Reset() { _myCounter.Reset(); SetTargetAmount(); } // Private methods private void SetTargetAmount() { _targetAmount = _random.Next(1, 100) / 100.00m; } }}

As you review this code, most o f it should be understandable. A few pieces require a bit o f explanationthough, such as _myCo unt er, an instance o f the Co unt er generic class with the Mo ney class as the datatype. Some of the class methods allow us to add or remove co ins, or restart the game (Add, Remo ve , andReset ). The Updat e method is used to draw the co ins. We have an internal (private) method,Set T arget Amo unt that is used to generate a random target co in value. We have an accessor,T o t alWo rt h, to get the current to tal value o f the co ins.

Let's update the Form code so we can actually play the game!

Modify MainFo rm.cs as shown:

Page 330: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;

namespace Coins{ public partial class MainForm : Form { private CoinGame _coinGame = new CoinGame(); public MainForm() { InitializeComponent(); } private void MainForm_Shown(object sender, EventArgs e) { // Exit event lambda expression exitButton.Click += (from, ea) => this.Close(); // Reset event lambda expression resetButton.Click += (from, ea) => { _coinGame.Reset(); moneyPictureBox.Invalidate(); CheckAmount(); }; // PictureBox Mouseup lambda expression addPennyButton.Click += (from, ea) => { _coinGame.Add(new Penny()); moneyPictureBox.Invalidate(); CheckAmount(); }; addNickelButton.Click += (from, ea) => { _coinGame.Add(new Nickel()); moneyPictureBox.Invalidate(); CheckAmount(); }; addDimeButton.Click += (from, ea) => { _coinGame.Add(new Dime()); moneyPictureBox.Invalidate(); CheckAmount(); }; addQuarterButton.Click += (from, ea) => { _coinGame.Add(new Quarter()); moneyPictureBox.Invalidate(); CheckAmount(); }; // Setup Picturebox Paint event moneyPictureBox.Paint += (from, ea) => { _coinGame.Update(ea.Graphics, moneyPictureBox.ClientSize); currentValueLabel.Text = _coinGame.TotalWorth.ToString(); targetValueLabel.Text = _coinGame.TargetAmount.ToString(); }; // PictureBox Mouseup lambda expression moneyPictureBox.MouseUp += (from, ea) => { _coinGame.Remove(ea.Location); moneyPictureBox.Invalidate(); CheckAmount(); }; } private void CheckAmount() { if (_coinGame.TotalWorth == 0) matchStatusLabel.Text = ""; else if (_coinGame.TargetAmount == _coinGame.TotalWorth) matchStatusLabel.Text = "Matched!"; else if (_coinGame.TotalWorth > _coinGame.TargetAmount) matchStatusLabel.Text = "Over!"; else matchStatusLabel.Text = "Under...";

Page 331: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} }}

Click and to run the program. You can now add and remove co ins, reset the game, and exit the game.Note that the co in counts next to each button are not yet working.

Let's discuss this code.

Page 332: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;

namespace Coins{ public partial class MainForm : Form { private CoinGame _coinGame = new CoinGame(); public MainForm() { InitializeComponent(); } private void MainForm_Shown(object sender, EventArgs e) { // Exit event lambda expression exitButton.Click += (from, ea) => this.Close(); // Reset event lambda expression resetButton.Click += (from, ea) => { _coinGame.Reset(); moneyPictureBox.Invalidate(); CheckAmount(); }; // PictureBox Mouseup lambda expression addPennyButton.Click += (from, ea) => { _coinGame.Add(new Penny()); moneyPictureBox.Invalidate(); CheckAmount(); }; addNickelButton.Click += (from, ea) => { _coinGame.Add(new Nickel()); moneyPictureBox.Invalidate(); CheckAmount(); }; addDimeButton.Click += (from, ea) => { _coinGame.Add(new Dime()); moneyPictureBox.Invalidate(); CheckAmount(); }; addQuarterButton.Click += (from, ea) => { _coinGame.Add(new Quarter()); moneyPictureBox.Invalidate(); CheckAmount(); }; // Setup Picturebox Paint event moneyPictureBox.Paint += (from, ea) => { _coinGame.Update(ea.Graphics, moneyPictureBox.ClientSize); currentValueLabel.Text = _coinGame.TotalWorth.ToString(); targetValueLabel.Text = _coinGame.TargetAmount.ToString(); }; // PictureBox Mouseup lambda expression moneyPictureBox.MouseUp += (from, ea) => { _coinGame.Remove(ea.Location); moneyPictureBox.Invalidate(); CheckAmount(); }; } private void CheckAmount() { if (_coinGame.TotalWorth == 0) matchStatusLabel.Text = ""; else if (_coinGame.TargetAmount == _coinGame.TotalWorth) matchStatusLabel.Text = "Matched!"; else if (_coinGame.TotalWorth > _coinGame.TargetAmount) matchStatusLabel.Text = "Over!"; else matchStatusLabel.Text = "Under...";

Page 333: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

} }}

I hope you aren't tired o f lambda expressions! We could have added typical event handlers, but usingdelegates and lambda expressions simplifies the process o f event mapping, especially if you change yourmind about which events you want to handle.

In addition to lambda expressions, we've added _co inGame , an instance o f the Co inGame class. We'veutilized methods from this class to make the game function as we respond to user actions. We use thePictureBox Paint event to call the _co inGame Updat e method to redraw the co ins and update Labels onthe Form to tell us the current target and values o f the co ins, as well as update a status Label using the privateCheckAmo unt Form method. We handle adding co ins by adding event delegate lambda expressions toeach o f the co in Button contro ls, and removing co ins by handling the PictureBox Click event.

Tip

Whenever two statements are combined on the same line o f code in Studio and you attempt toadd a breakpo int by clicking to the left o f the line o f code, the entire statement is flagged as thebreakpo int, but in actuality, the first statement is the breakpo int. If you want to break on the secondstatement, you can highlight the entire second statement code, right-click on the selectedstatement, and select Breakpo int | Insert Breakpo int.

Let's make the final changes to determine the number o f each co ins we're using.

Modify Co inGame.cs as shown:

Page 334: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// Private class variablesprivate Counter<Money> _myCounter = new Counter<Money>();private Counter<Penny> _myPennies = new Counter<Penny>();private Counter<Nickel> _myNickels = new Counter<Nickel>();private Counter<Dime> _myDimes = new Counter<Dime>();private Counter<Quarter> _myQuarters = new Counter<Quarter>();private int _coinHeight = 20;private Random _random = new Random();...public decimal TotalWorth { get { return _myCounter.TotalWorth; } }public int CountPenny { get { return _myPennies.Count; } }public int CountNickel { get { return _myNickels.Count; } }public int CountDime { get { return _myDimes.Count; } }public int CountQuarter { get { return _myQuarters.Count; } }...// Public methodspublic void Add(Money money){ _myCounter.Add(money); switch (money.GetType().Name) { case "Penny": _myPennies.Add(money as Penny); break; case "Nickel": _myNickels.Add(money as Nickel); break; case "Dime": _myDimes.Add(money as Dime); break; case "Quarter": _myQuarters.Add(money as Quarter); break; }}

public void Remove(Point location){ // Loop through each coin to see which was selected foreach (Money money in _myCounter) { if (money.Hit(location)) { switch (money.GetType().Name) { case "Penny": _myPennies.Remove(money as Penny); break; case "Nickel": _myNickels.Remove(money as Nickel); break; case "Dime": _myDimes.Remove(money as Dime); break; case "Quarter": _myQuarters.Remove(money as Quarter); break; } _myCounter.Remove(money);

Page 335: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

break; } }}

public void Reset(){ _myCounter.Reset(); _myPennies.Reset(); _myNickels.Reset(); _myDimes.Reset(); _myQuarters.Reset(); SetTargetAmount();}...

Click to save your changes.

Let's discuss this code.

Page 336: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Private class variablesprivate Counter<Money> _myCounter = new Counter<Money>();private Counter<Penny> _myPennies = new Counter<Penny>();private Counter<Nickel> _myNickels = new Counter<Nickel>();private Counter<Dime> _myDimes = new Counter<Dime>();private Counter<Quarter> _myQuarters = new Counter<Quarter>();private int _coinHeight = 20;private Random _random = new Random();...public decimal TotalWorth { get { return _myCounter.TotalWorth; } }public int CountPenny { get { return _myPennies.Count; } }public int CountNickel { get { return _myNickels.Count; } }public int CountDime { get { return _myDimes.Count; } }public int CountQuarter { get { return _myQuarters.Count; } }...// Public methodspublic void Add(Money money){ _myCounter.Add(money); switch (money.GetType().Name) { case "Penny": _myPennies.Add(money as Penny); break; case "Nickel": _myNickels.Add(money as Nickel); break; case "Dime": _myDimes.Add(money as Dime); break; case "Quarter": _myQuarters.Add(money as Quarter); break; }}

public void Remove(Point location){ // Loop through each coin to see which was selected foreach (Money money in _myCounter) { if (money.Hit(location)) { switch (money.GetType().Name) { case "Penny": _myPennies.Remove(money as Penny); break; case "Nickel": _myNickels.Remove(money as Nickel); break; case "Dime": _myDimes.Remove(money as Dime); break; case "Quarter": _myQuarters.Remove(money as Quarter); break; } _myCounter.Remove(money);

Page 337: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

break; } }}

public void Reset(){ _myCounter.Reset(); _myPennies.Reset(); _myNickels.Reset(); _myDimes.Reset(); _myQuarters.Reset(); SetTargetAmount();}...

We've added four Co unt er generic class instances, one for each o f the four co in classes, demonstrating thatwe can use the same generic class with different data types. Even though the Co unt er class was constrainedby the Mo ney class, because the four co in classes were derived from Mo ney, we're able to create instancesof the Co unt er generic class.

Rather than discuss all four co in classes, let's focus on the additions related to the Penny class. We'veadded a property Co unt Penny to facilitate retrieving the current count o f pennies in the Penny classinstance _myPennies. We've also updated both the Add and Remo ve methods to add a new co in orremove a selected co in from _myPennies. Notice that we had to determine the class name of the mo neyselected co in to determine which co in to deal with using the Name property o f the Get T ype method. Wealso updated the Reset method to remove any co ins from the additional co in classes.

Let's make our final changes to the Form.

Modify MainFo rm.cs as shown:

Page 338: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void MainForm_Shown(object sender, EventArgs e){ // Exit event lambda expression exitButton.Click += (from, ea) => this.Close(); // Reset event lambda expression resetButton.Click += (from, ea) => { _coinGame.Reset(); moneyPictureBox.Invalidate(); CheckAmount(); }; // PictureBox Mouseup lambda expression addPennyButton.Click += (from, ea) => { _coinGame.Add(new Penny()); moneyPictureBox.Invalidate(); CheckAmount(); }; addNickelButton.Click += (from, ea) => { _coinGame.Add(new Nickel()); moneyPictureBox.Invalidate(); CheckAmount(); }; addDimeButton.Click += (from, ea) => { _coinGame.Add(new Dime()); moneyPictureBox.Invalidate(); CheckAmount(); }; addQuarterButton.Click += (from, ea) => { _coinGame.Add(new Quarter()); moneyPictureBox.Invalidate(); CheckAmount(); }; // Setup Picturebox Paint event moneyPictureBox.Paint += (from, ea) => { _coinGame.Update(ea.Graphics, moneyPictureBox.ClientSize); currentValueLabel.Text = _coinGame.TotalWorth.ToString(); targetValueLabel.Text = _coinGame.TargetAmount.ToString(); countPennyLabel.Text = _coinGame.CountPenny.ToString(); countNickelLabel.Text = _coinGame.CountNickel.ToString(); countDimeLabel.Text = _coinGame.CountDime.ToString(); countQuarterLabel.Text = _coinGame.CountQuarter.ToString(); }; // PictureBox Mouseup lambda expression moneyPictureBox.MouseUp += (from, ea) => { _coinGame.Remove(ea.Location); moneyPictureBox.Invalidate(); CheckAmount(); };}...

Click and to run the program. Now as you play, the co in counts are updated.

Let's discuss this code:

Page 339: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void MainForm_Shown(object sender, EventArgs e){ // Exit event lambda expression exitButton.Click += (from, ea) => this.Close(); // Reset event lambda expression resetButton.Click += (from, ea) => { _coinGame.Reset(); moneyPictureBox.Invalidate(); CheckAmount(); }; // PictureBox Mouseup lambda expression addPennyButton.Click += (from, ea) => { _coinGame.Add(new Penny()); moneyPictureBox.Invalidate(); CheckAmount(); }; addNickelButton.Click += (from, ea) => { _coinGame.Add(new Nickel()); moneyPictureBox.Invalidate(); CheckAmount(); }; addDimeButton.Click += (from, ea) => { _coinGame.Add(new Dime()); moneyPictureBox.Invalidate(); CheckAmount(); }; addQuarterButton.Click += (from, ea) => { _coinGame.Add(new Quarter()); moneyPictureBox.Invalidate(); CheckAmount(); }; // Setup Picturebox Paint event moneyPictureBox.Paint += (from, ea) => { _coinGame.Update(ea.Graphics, moneyPictureBox.ClientSize); currentValueLabel.Text = _coinGame.TotalWorth.ToString(); targetValueLabel.Text = _coinGame.TargetAmount.ToString(); countPennyLabel.Text = _coinGame.CountPenny.ToString(); countNickelLabel.Text = _coinGame.CountNickel.ToString(); countDimeLabel.Text = _coinGame.CountDime.ToString(); countQuarterLabel.Text = _coinGame.CountQuarter.ToString(); }; // PictureBox Mouseup lambda expression moneyPictureBox.MouseUp += (from, ea) => { _coinGame.Remove(ea.Location); moneyPictureBox.Invalidate(); CheckAmount(); };}...

With these four lines o f code, we call the correct Co unt method o f the _co inGame class instance, andupdate the co in counts.

That's it fo r the tutorial!

Before you move on to the next lesson, well, you know what to do.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 340: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Creating and Manipulating Strings

In this lesson we'll learn more about strings.

We'll be covering these topics:

What are Strings?Immutability and Empty Versus NullStringBuilderRegular Expressions

StringsLet's review a few important aspect o f strings and introduce a few new key concepts about them as well.

Immutability and Empty Versus NullStrings are reference data types that are immut able , which means that when you create a string variable in C#, you'veessentially created a reference to a fixed memory location where the string content is stored. You can change the valueof a string, but keep in mind that when you do that, you're actually creating a new memory location to ho ld the newstring value, and your string variable references that new location. The original string location is then marked forrecovery by the Garbage Collector.

As a reference type, a string may be null. Whenever you create a string variable, but do not initialize it, the string is notreferencing memory and is null. However, if you initialize a string variable with double quotation marks (""), you havecreated a reference to an immutable block o f memory. In addition to using an empty string, you may also use thestring.Empty field to initialze your string; they're equivalent, because once a string has been initialized and referencesmemory, it's immutable. You may not want to allocate memory to a string if you're eventually go ing to populate thestring with data (o ther than an empty string). However, if you do elect to leave the string variable as null, you'll want touse either the string method st ring.IsNullOrEmpt y() o r IsNullOrWhit eSpace() , rather than Length to evaluatewhether a string contains information, because you cannot call a class method when the object (the string) has notbeen allocated. The method IsNullOrEmpty is, as the method name implies, returning a boo lean value if a string is nullo r if the string is empty. The IsNullOrWhiteSpace includes all the functionality o f IsNullOrEmpty, and a check to seewhether a string is composed o f only white space (space, tab, etc.). It also performs better than using the string Trimmethod.

Let's create a program and start working with strings. We'll create a too l that allows us to translate a sentence fromEnglish to the fictional language IggyLatin, a derivate o f Pig Latin.

Select File | New | Pro ject . Change the Name of the pro ject to IggyLat in and click OK.

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 title bar's Text property to IggyLat in - 3 Ways. Also

change the StartPosition property to Cent erScreen. Click to save your changes.

Add UI contro ls to the MainForm to match the UI in the image below, naming the contro ls according to the table below

the image. Click to save your changes.

Page 341: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Co nt ro l T ype Co nt ro l Name T ext Pro pert y

GroupBox stringsGroupBox

Label iterationsLabel Iterations:

Label originalLabel Original:

Label stringLabel String:

Label stringIggyLabel ...

Label builderLabel String Builder:

Label builderIggyLabel ...

Label expressionLabel Regular Expression:

Label expressionIggyLabel ...

TextBox originalTextBox

NumericUpDown iterationsNumericUpDown

Button convertButton Convert

Button exitButton Exit

Change the iterationsNumericUpDown contro l property Minimum to 10 , Maximum to 100 , and Increment to 10 .Double-click the co nvert But t o n and exit But t o n contro ls to generate Click event handlers. Add the usual code to

the exitButton Click event to exit the application when the Exit button is clicked. Click to save your changes.

Now that we've got the IggyLatin interface in place, let's see what we can do with strings. We'll create a class that willhandle the language conversion first.

Right-click IggyLat in in the So lution Explorer, select Add | Class, enter IggyCo nversio n in the Name textbox, andclick Add. Modify the IggyCo nversio n.cs class code as shown.

Note We have included a number o f comment lines that explain the IggyLatin language conversion rules. Youmay omit this comment block from your code if you so choose. The rules are explained later as well.

Page 342: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace IggyLatin{ /* * Iggy Latin Rules: * * 1. Vowels including y (a,e,i,o,u,y) are prepended with ig * 2. Append all words ending in consonant (except y) with iggy * 2. Trailing y replaced with iggy * 3. Append all words ending in vowel with ggy * * Samples * --------------------------------------------------------------- * lie => ligieggy * happy => higappiggy * cat => cigatiggy * hyper => higypigeriggy * * Original: Now is the time for all good men to come to the aid of their country * Solution: Nigowiggy igisiggy theggy tigimeggy figoriggy igalliggy gigoigodiggy * migeniggy toggy cigomeggy toggy theggy igaigidiggy igofiggy thigeigiriggy * cigoiguntriggy * * */ public static class IggyConversion { // Private class variables private static readonly char[] _prependList = "yiaeou".ToCharArray(); private static readonly char[] _appendList = "aeiou".ToCharArray(); private static readonly char[] _appendSpecialList = "y".ToCharArray(); private static readonly string _prependText = "ig"; private static readonly string _appendText = "ggy"; private static readonly string _appendSpecialText = "iggy"; // String methods public static string ConvertUsingString(string sentence) { string convertedSentence = string.Empty; // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(sentence)) { // Parse out each word string word = ""; for (int position = 0; position < sentence.Length; position++) { string character = sentence.Substring(position, 1); if (!char.IsWhiteSpace(character, 0)) word += character; if (char.IsWhiteSpace(character, 0) || position == sentence.Length - 1) { convertedSentence += ConvertWordUsingString(word); if (position < sentence.Length - 1) convertedSentence += character; word = string.Empty; } } }

Page 343: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

return convertedSentence; } private static string ConvertWordUsingString(string word) { string convertedWord = string.Empty; // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(word)) { // Convert each character, with special handling for last character for (int position = 0; position < word.Length; position++) { string character = word.Substring(position, 1); if (character.ToLower().IndexOfAny(_prependList) >= 0) { // "Vowels" if (position < word.Length - 1) // Prepend convertedWord += _prependText + character; else if (character.ToLower().IndexOfAny(_appendSpecialList) >= 0) // Append special convertedWord += _appendSpecialText; else if (character.ToLower().IndexOfAny(_appendList) >= 0) // Append convertedWord += character + _appendText; } else { // "Consonants" if (position < word.Length - 1) // Append convertedWord += character; else // Append special convertedWord += character + _appendSpecialText; } } // Fix first character case if (word.Substring(0, 1).Equals(word.Substring(0, 1).ToUpper(), StringComparison.Ordinal)) convertedWord = char.ToUpper(convertedWord[0]) + convertedWord.Substring(1); } return convertedWord; } }}

Click to save your changes.

Use these rules when converting a sentence into IggyLatin:

Vowels including y (a,e,i,o ,u,y) are prepended with ig.Append all words ending in consonant (except y) with iggy.Trailing y replaced with iggy.Append all words ending in vowel with ggy.

Let's discuss the code:

Page 344: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;

namespace IggyLatin{ public static class IggyConversion { // Private class variables private static readonly char[] _prependList = "yiaeou".ToCharArray(); private static readonly char[] _appendList = "aeiou".ToCharArray(); private static readonly char[] _appendSpecialList = "y".ToCharArray(); private static readonly string _prependText = "ig"; private static readonly string _appendText = "ggy"; private static readonly string _appendSpecialText = "iggy"; // String methods public static string ConvertUsingString(string sentence) { string convertedSentence = string.Empty; // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(sentence)) { // Parse out each word string word = ""; for (int position = 0; position < sentence.Length; position++) { string character = sentence.Substring(position, 1); if (!char.IsWhiteSpace(character, 0)) word += character; if (char.IsWhiteSpace(character, 0) || position == sentence.Length - 1) { convertedSentence += ConvertWordUsingString(word); if (position < sentence.Length - 1) convertedSentence += character; word = string.Empty; } } } return convertedSentence; } private static string ConvertWordUsingString(string word) { string convertedWord = string.Empty; // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(word)) { // Convert each character, with special handling for last character for (int position = 0; position < word.Length; position++) { string character = word.Substring(position, 1); if (character.ToLower().IndexOfAny(_prependList) >= 0) { // "Vowels" if (position < word.Length - 1) // Prepend convertedWord += _prependText + character; else if (character.ToLower().IndexOfAny(_appendSpecialList) >= 0)

Page 345: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

// Append special convertedWord += _appendSpecialText; else if (character.ToLower().IndexOfAny(_appendList) >= 0) // Append convertedWord += character + _appendText; } else { // "Consonants" if (position < word.Length - 1) // Append convertedWord += character; else // Append special convertedWord += character + _appendSpecialText; } } // Fix first character case if (word.Substring(0, 1).Equals(word.Substring(0, 1).ToUpper(), StringComparison.Ordinal)) convertedWord = char.ToUpper(convertedWord[0]) + convertedWord.Substring(1); } return convertedWord; } }}

We've made our class st at ic, as well as all o f the class variables and methods. Why? We do not need an instance o fthe class to do our translation; we only need to call the methods. Often you will create a static class to encapsulatemethods that do not require an instance variable. When creating such classes, you still work to encapsulate relatedfunctionality within the same class.

The basic algorithm used to translate into IggyLatin is to find each word, then convert each word independently. Theimplementation o f the algorithm we've chosen is to demonstrate using string functions explicitly. First, we create avariable to ho ld our translated sentence, setting our string to Empt y. Then, we use the IsNullOrWhit eSpace stringmethod to ensure that we actually have a valid string to test. Then, to find each word, we loop through each character inthe sentence, using the subst ring string method to extract a single letter. Next, we use the IsWhit eSpace charmethod to determine whether we've found a word boundary. If we do find a word boundary, or if we've reached the endof our sentence, we call the Co nvert Wo rdUsingSt ring method to translate the actual word.

Before working through the Co nvert Wo rdUsingSt ring method, let's clarify the difference between char and string.The string data type is a reference data type, whereas char is a value type. When you deal with a single character, youmay need to look to the char data type for a method that works with a single character. We created a number o f classvariables (like _prependList ) o f constant (reado nly char arrays (char[]) using the T o CharArray string method. Wewant to use the IndexOf Any string method that allows us to search a char array to see whether the word we'relooking for matches any o f the items in the char array.

To translate each word, we loop through each character again, looking for "vowels" and "consanants," and apply thetranslation rules, depending on what we find and where we find it. After translating the word, we maintain the case forthe first letter o f each word by using the Equals string comparison operator. When comparing strings, .NETrecommends using Equals because it allows you to specify a comparison type. In our code, we useSt ringCo mpariso n.Ordinal, which means the strings must match exactly.

Let's add code to our Form so we can see this first translation in action. We'll use the .NET Stopwatch Diagnosticsclass to track how long it takes to do our translation. Since a single translation would be too fast fo r our purposes,we'll add code that allows looping through the translation multiple times (iterations).

Modify MainFo rm.cs as shown:

Page 346: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Diagnostics;

namespace IggyLatin{ public partial class MainForm : Form { public MainForm() { InitializeComponent(); } private void exitButton_Click(object sender, EventArgs e) { this.Close(); } private void convertButton_Click(object sender, EventArgs e) { if (!string.IsNullOrWhiteSpace(originalTextBox.Text)) { int loops = (int)iterationsNumericUpDown.Value; Stopwatch stopWatch = new Stopwatch(); string result = ""; long elapsedTicks = 0; // Run through stop watch the first time (per Microsoft) to ensure more accurate measurements stopWatch.Start(); stopWatch.Stop(); stopWatch.Reset(); // String stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingString(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; stringIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); } } }}

Click and to run the program. Enter a sentence and observe the translation and timing. Try it again with evenmore iterations to see how that effects the code.

Let's discuss this code:

Page 347: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

using System;using System.Collections.Generic;using System.ComponentModel;using System.Data;using System.Drawing;using System.Linq;using System.Text;using System.Windows.Forms;using System.Diagnostics;

namespace IggyLatin{ public partial class MainForm : Form { public MainForm() {` InitializeComponent(); } private void exitButton_Click(object sender, EventArgs e) { ` this.Close(); } private void convertButton_Click(object sender, EventArgs e) { if (!string.IsNullOrWhiteSpace(originalTextBox.Text)) { int loops = (int)iterationsNumericUpDown.Value; Stopwatch stopWatch = new Stopwatch(); string result = ""; long elapsedTicks = 0; // Run through stop watch the first time (per Microsoft) to ensure more accurate measurements stopWatch.Start(); stopWatch.Stop(); stopWatch.Reset(); // String stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingString(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; stringIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); } } }}

We added the Diagno st ics namespace to enable us to use the St o pwat ch class. We use this class to determine thenumber o f elapsed "ticks" o f the underlying timer mechanism using the ElapsedT icks method o f the St o pwat chclass. We use the St art , St o p, and Reset methods o f this class to perform our timing as we loop over the sametranslation a number o f times. After we finish translating, we output the timing and translation results using the Fo rmatstring method. This method allows us to integrate parameters into a string using place ho lders, where the first placeho lder, {0} , will be replaced by the first parameter, elapsedT icks, and so on. The Format method supports a widevariety o f fo rmatting options, but we'll use the basic fo rm for now. Use this general syntax for the replaceableparameters:

OBSERVE:

{ index[,alignment][ : formatString] }

Page 348: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Required code is located within opening and closing curly braces ({}). The square brackets ([]) denote optionalelements. index refers to the zero-based index o f the parameters for fo rmatting strings. alignment is an optionalsigned integer that indicates the to tal length o f the field into which the parameter is placed. If the integer is positive, thestring is right-aligned. If the integer is negative, the string is left-aligned. If alignment is omitted, no leading or trailingspaces are inserted. The f o rmat St ring supports a wide variety o f fo rmatting options depending on the parametertype. Go ahead and explore them! Start with the String.Format Method, and fo llow the links under the f o rmat St ringsection.

Okay, let's move on to using the StringBuilder class.

StringBuilderEarlier we talked about how constant manipulation o f the contents o f a string results in the steady allocation anddeallocation o f memory, which can result in diminished performance. To remedy that issue, .NET provides theStringBuilder class that can dramatically improve performance. You can also pre-allocate memory you anticipate usingto improve performance even more. Let's add our StringBuilder version.

Modify IggyCo nversio n.cs as shown below:

Page 349: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.// StringBuilder methodspublic static string ConvertUsingStringBuilder(string sentence){ StringBuilder convertedSentence = new StringBuilder(sentence.Length); // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(sentence)) { // Parse out each word StringBuilder word = new StringBuilder(); for (int position = 0; position < sentence.Length; position++) { string character = sentence.Substring(position, 1); if (!char.IsWhiteSpace(character, 0)) word.Append(character); if (char.IsWhiteSpace(character, 0) || position == sentence.Length - 1) { convertedSentence.Append(ConvertWordUsingStringBuilder(word.ToString())); if (position < sentence.Length - 1) convertedSentence.Append(character); word.Clear(); } } } return convertedSentence.ToString();}

private static string ConvertWordUsingStringBuilder(string word){ string convertedWord = string.Empty; // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(word)) { StringBuilder workingWord = new StringBuilder(word.Substring(0, word.Length - 1)); // Replace all "vowels" except skip last character (special handling) foreach(char ch in _prependList) workingWord.Replace(ch.ToString(), _prependText + ch); // Replace last character string lastCharacter = word.Substring(word.Length - 1, 1); if (lastCharacter.IndexOfAny(_appendSpecialList) >= 0) workingWord.Append(_appendSpecialText); else if (lastCharacter.IndexOfAny(_appendList) >= 0) workingWord.Append(lastCharacter + _appendText); else workingWord.Append(lastCharacter + _appendSpecialText); // Fix first character case if (word.Substring(0, 1).Equals(word.Substring(0, 1).ToUpper(), StringComparison.Ordinal)) workingWord[0] = char.ToUpper(workingWord[0]); convertedWord = workingWord.ToString(); } return convertedWord;}...

Page 350: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click to save your changes.

Let's discuss this code:

Page 351: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// StringBuilder methodspublic static string ConvertUsingStringBuilder(string sentence){ StringBuilder convertedSentence = new StringBuilder(sentence.Length); // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(sentence)) { // Parse out each word StringBuilder word = new StringBuilder(); for (int position = 0; position < sentence.Length; position++) { string character = sentence.Substring(position, 1); if (!char.IsWhiteSpace(character, 0)) word.Append(character); if (char.IsWhiteSpace(character, 0) || position == sentence.Length - 1) { convertedSentence.Append(ConvertWordUsingStringBuilder(word.ToString())); if (position < sentence.Length - 1) convertedSentence.Append(character); word.Clear(); } } } return convertedSentence.ToString();}

private static string ConvertWordUsingStringBuilder(string word){ string convertedWord = string.Empty; // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(word)) { StringBuilder workingWord = new StringBuilder(word.Substring(0, word.Length - 1)); // Replace all "vowels" except skip last character (special handling) foreach(char ch in _prependList) workingWord.Replace(ch.ToString(), _prependText + ch); // Replace last character string lastCharacter = word.Substring(word.Length - 1, 1); if (lastCharacter.IndexOfAny(_appendSpecialList) >= 0) workingWord.Append(_appendSpecialText); else if (lastCharacter.IndexOfAny(_appendList) >= 0) workingWord.Append(lastCharacter + _appendText); else workingWord.Append(lastCharacter + _appendSpecialText); // Fix first character case if (word.Substring(0, 1).Equals(word.Substring(0, 1).ToUpper(), StringComparison.Ordinal)) workingWord[0] = char.ToUpper(workingWord[0]); convertedWord = workingWord.ToString(); } return convertedWord;}...

Page 352: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Using the St ringBuilder class, we iso late the words, initializing our StringBuilder variable to the length o f thesentence. We loop through each character, but this time we use the Append StringBuilder method to add eachcharacter rather than using string concatenation. Once we've iso lated each word, we callCo nvert Wo rdUsingSt ringBuilder, calling the T o St ring method to convert our StringBuilder object into a string.Once the translation is returned, we append any whitespace within the word, and use Clear to clear the StringBuildercontents.

To translate each word, we use the f o reach syntax to loop through each "vowel" that supports prepending. However,we are omitting the last character o f the word because that character requires special handling. After we've used theReplace method to perform any substitutions, we determine which handling o f the last character is necessary, anduse the Append method to update our StringBuilder variable accordingly. Then we account fo r the possibility o fuppercase letters (although this time we used the T o Upper method o f char rather than string) as we're updating aspecific character in the wo rkingWo rd StringBuilder variable.

Let's update our Form with this code, and test our translation.

Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.private void convertButton_Click(object sender, EventArgs e){ if (!string.IsNullOrWhiteSpace(originalTextBox.Text)) { int loops = (int)iterationsNumericUpDown.Value; Stopwatch stopWatch = new Stopwatch(); string result = ""; long elapsedTicks = 0; // Run through stop watch the first time (per Microsoft) to ensure more accurate measurements stopWatch.Start(); stopWatch.Stop(); stopWatch.Reset(); // String stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingString(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; stringIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); // StringBuilder stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingStringBuilder(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; builderIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); }}...

Click and to run the program. Enter a sentence and observe the translation and timing. Change the number o fiterations and observe the elapsed tick results.

Let's discuss this code:

Page 353: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void convertButton_Click(object sender, EventArgs e){ if (!string.IsNullOrWhiteSpace(originalTextBox.Text)) { int loops = (int)iterationsNumericUpDown.Value; Stopwatch stopWatch = new Stopwatch(); string result = ""; long elapsedTicks = 0; // Run through stop watch the first time (per Microsoft) to ensure more accurate measurements stopWatch.Start(); stopWatch.Stop(); stopWatch.Reset(); // String stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingString(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; stringIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); // StringBuilder stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingStringBuilder(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; builderIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); }}...

Other than calling Co nvert UsingSt ringBuilder, our code is identical to the code we used with strings. So, when yourun the program, did StringBuilder execute faster, o r slower? Most likely, it executed slower. Why? There are a numberof possible reasons, but most likely the overhead o f repeatedly creating a StringBulder object is still greater than theamount o f string processing we're do ing. With longer sentences, the StringBuilder results are closer (if no t better) thanstring processing results, although as you increase the number o f iterations, strings outperform StringBuilder. So,when using StringBuilder, you'll want to try to code your so lution so that it doesn't create new StringBuilder objectsrepeatedly.

Let's move on to our last translation technique, regular expressions.

Regular ExpressionsRegular expressions is a process o f matching, extracting, splitting, and replacing information, using a flexible, yethighly structured syntax. In C#, we use the Regex class to handle regular expressions. We're only go ing to take asmall bite out o f regular expressions in this lesson, but feel free to explore more about them later at The RegularExpression Object Model. You'll have a chance to use this site more during the lesson pro ject.

Let's modify IggyCo nversio n.cs to perform the translation using regular expressions. We'll use these code changesto help illustrate how regular expressions work:

Page 354: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Text.RegularExpressions;... // Convert using regular expressions public static string ConvertUsingRegularExpressions(string sentence) { // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(sentence)) { // Step 1: Prepend all vowels and y with ig not at end of word string matchPattern = @"[aeiouy](?!\b)"; string replacePattern = _prependText + @"$&"; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); // Step 2: Replace all words ending in y with iggy matchPattern = @"[y]\b"; replacePattern = _appendSpecialText; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); // Step 3: Replace all words ending in vowel with vowel+ggy matchPattern = @"[aeiou]\b"; replacePattern = @"$&" + _appendText; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); // Step 4: Replace all words not ending in vowels or y with iggy matchPattern = @"[^aeiouy\W]\b"; replacePattern = @"$&" + _appendSpecialText; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); } return sentence; }...

Click to save your changes.

Let's discuss this code:

Page 355: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.// Convert using regular expressionspublic static string ConvertUsingRegularExpressions(string sentence){ // Make sure we have something to convert if (!string.IsNullOrWhiteSpace(sentence)) { // Step 1: Prepend all vowels and y with ig not at end of word string matchPattern = @"[aeiouy](?!\b)"; string replacePattern = _prependText + @"$&"; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); // Step 2: Replace all words ending in y with iggy matchPattern = @"[y]\b"; replacePattern = _appendSpecialText; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); // Step 3: Replace all words ending in vowel with vowel+ggy matchPattern = @"[aeiou]\b"; replacePattern = @"$&" + _appendText; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); // Step 4: Replace all words not ending in vowels or y with iggy matchPattern = @"[^aeiouy\W]\b"; replacePattern = @"$&" + _appendSpecialText; sentence = Regex.Replace(sentence, matchPattern, replacePattern, RegexOptions.IgnoreCase); } return sentence;}...

That looks different! To use regular expressions for replacement, we'll use the Regex class as a static object, callingthe Replace method. For arguments to the Replace method, we use the string to be processed, a pattern to match on(mat chPat t ern), a replacement pattern (replacePat t ern), and options that indicate how we want to do theprocessing. In this case, we want to ignore the case, so we use the Igno reCase option.

We reintroduce the @ symbol. By prefixing a string literal with the @ symbol, we are creating a verbat im st ringlit eral. The @ symbol directs the C# compiler to interpret the contents o f the string as literal. We do not need toescape characters that we normally would, like the backslash commonly used in path names on Windows.

When creating a matching pattern, there are several possible items we can use. Rather than repeat what's alreadyavailable, take some time to read through the material on MSDN at Regular Expression Language - Quick Reference.You'll no tice that regular expression pattern components may be grouped into a number o f categories: characterclasses, anchors, grouping constructs, quantifiers, backreference constructs, alternation constructs, substitutions,character escapes, and miscellaneous. Let's evaluate the regular expression patterns used in our code:

Step 1: [aeio uy](?!\b)[aeio uy] - Match any single character in this character class(?!\b) - Use a negative lookahead assertion using the word boundary anchor indicating that thenext character must not be a valid word boundary character

Step 2: [y]\b[y] - Match any single character in this character class\b - Use the word boundary anchor indicating that the next character must be a valid wordboundary character

Page 356: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Step 3: [aeio u]\b[aeio u] - Match any single character in this character class\b - Use the word boundary anchor indicating that the next character must be a valid wordboundary character

Step 4: [^aeio uy\W]\b[^aeio uy\W] - Match any single character not in this character class, which includes any non-word character\b - Use the word boundary anchor indicating that the next character must be a valid wordboundary character

As you can see that the complexity o f regular expressions lies within constructing the right pattern-matching sequence.Use the Regular Expression Language - Quick Reference site to review each o f the regular expressions. Unlessindicated, you're matching on a single character. If we wanted to match more than one character, we would add one o fthe quantifiers, such as $, *, + or ?, after our single character matching statement.

The replacement pattern argument is used with the Replace method. With Replace, the $& represents the results o f thematch. For each match in the original string, the matched string will be replaced with whatever is specified in thereplacement argument. Three o f the four Replace calls use the $& as part o f the replacement, which means forhowever many matches we found in our string, we'll use part o f the match as part o f the replacement. In one o f ourReplace calls, we did not use $&, which means that no part o f the match is included in the replacement.

Let's add the code to enable using the regular expression version o f our translator.

Modify MainFo rm.cs as shown:

Page 357: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void convertButton_Click(object sender, EventArgs e){ if (!string.IsNullOrWhiteSpace(originalTextBox.Text)) { int loops = (int)iterationsNumericUpDown.Value; Stopwatch stopWatch = new Stopwatch(); string result = ""; long elapsedTicks = 0; // Run through stop watch the first time (per Microsoft) to ensure more accurate measurements stopWatch.Start(); stopWatch.Stop(); stopWatch.Reset(); // String stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingString(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; stringIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); // StringBuilder stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingStringBuilder(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; builderIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); // Regular Expression stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingRegularExpressions(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; expressionIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); }}...

Click and to run the program. Enter a sentence again and observe the translation and timing.

Let's discuss this code:

Page 358: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void convertButton_Click(object sender, EventArgs e){ if (!string.IsNullOrWhiteSpace(originalTextBox.Text)) { int loops = (int)iterationsNumericUpDown.Value; Stopwatch stopWatch = new Stopwatch(); string result = ""; long elapsedTicks = 0; // Run through stop watch the first time (per Microsoft) to ensure more accurate measurements stopWatch.Start(); stopWatch.Stop(); stopWatch.Reset(); // String stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingString(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; stringIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); // StringBuilder stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingStringBuilder(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; builderIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); // Regular Expression stopWatch.Reset(); stopWatch.Start(); for (int i = 0; i < loops; i++) result = IggyConversion.ConvertUsingRegularExpressions(originalTextBox.Text); stopWatch.Stop(); elapsedTicks = stopWatch.ElapsedTicks; expressionIggyLabel.Text = string.Format("{0} - {1}", elapsedTicks, result); }}...

We call Co nvert UsingRegularExpressio ns method to process our sentence using regular expressions.

We only included the Replace method o f Regex. For the lesson pro ject you'll be creating a utility that will allow you totest your regular expressions, so you'll need to use the matching capability o f Regex. The code snippet here illustratesthe pattern you'll need to fo llow when matching:

OBSERVE:

Match match = Regex.Match(originalString, patternString, RegexOptions);while (match.Success){ Console.WriteLine(match.ToString()); match = match.NextMatch();}

Page 359: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

With this code we create a Mat ch instance mat ch by calling the Mat ch method o f Regex. Note that one o f thearguments o f Match is RegexOpt io ns. where typical options are IgnoreCase or None. Then, we check the Successproperty o f mat ch, and enter a loop, displaying each match using the Next Mat ch and T o St ring methods.

And that's it fo r this lesson on strings! That's quite a bi to digest, but I'm confident you can handle it. You'll get topractice all o f it in the homework for this lesson. Keep working on the pro ject until feel like you've got a good grasp o fstrings.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 360: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Introduction to Database Access Using .NET

In this lesson we'll learn to use a Microsoft Access database with Visual Studio and C#. You'll use several Studio wizards tocreate an application that connects to a database and allows you to add, update, and delete records. We'll also use drag anddrop to update images within the database. We will no t be introducing or using Structured Query Language (SQL) in this lesson.

This lesson includes these topics:

What are Databases?Data Sources WindowWorking with Datasets

What are Databases?Generally, a database is a repository fo r information that includes some mechanism for adding and deletinginformation, and a way to search for information. There are various types o f databases, such as object-orienteddatabases and relational databases. We'll focus on relational databases, specifically Microsoft Access, because it's afile-based so lution that is realtively uncomplicated to work with and distribute.

So, what is a relational database? It's a co llection o f data organized as a set o f fo rmally described tables from whichdata can be accessed. The relational database is the preferred model used for storing data. For our purposes, we willlimit our discussion to the concepts o f a database file, a table within that file, and rows within the table that havedifferent co lumns repeated in each row. If you're familiar with a spreadsheet program, you might visualize a databasewhere a table is an individual sheet with rows and co lumns, and where different sheets represent different tables. Anindividual database would be the equivalent o f a spreadsheet file.

If you use your web browser to visit any popular website where you can buy products, the contents o f that website aregenerated from information stored in a database. That information could be stored in simple files, but as those filesgrow larger, and as the information they contain becomes more complex, using a database provides the flexibility andspeed required to interact with our data faster.

For most o f the lessons in this course, we've focused on creating games, but fo r this lesson and the next, we'll focuson a business application. Let's get started!

Data Sources WindowWith Visual Studio , connect to an existing database using the Data Sources window. The Data Sources window islocated next to the Too lBox window. For this lesson, we'll use a Microsoft Access database named MyDatabase.

Right-click the link above for MyDatabase, select Save T arget As..., and save the file in your Pro ject s fo lder.

Now, let's create a new pro ject and work through connecting to our Access database. The pro ject will be a catalog o fphotos.

Note All o f the photos used in this lesson are freely available on the website fontplay.

Select File | New | Pro ject . Change the Name of the pro ject to Pho t o s and click OK. Click to save yourchanges.

Make the Data Sources window visible by selecting Dat a | Sho w Dat a So urces.

Page 361: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Once you start the Data Source Configuration Wizard, you won't be able to return to this lesson text until you getthrough all o f the steps,, so scro ll through it now (enlarge the panel if necessary) so you can get a sense o f what'scoming, then fo llow these steps:

1. Click Add New Dat a So urce in the Data Sources window. In the first "Choose a Data Source Type"dialog, select Dat abase (the default) and click Next .2. In the "Choose a Database Model" dialog, select Dat aset (the default) and click Next .3. In the "Choose Your Data Connection" dialog, click the New Co nnect io n button.4. In the "Add Connection" dialog, click Bro wse.... In the "Select Microsoft Access Database File" dialog,find the MyDat abase.mdb file in your Pro ject s fo lder, and click Open.5. In the "Add Connection" dialog, click the T est Co nnect io n button to confirm we've connected to theMyDatabase Access file, then click OK.6 . Back at the "Choose Your Data Connection" dialog, you now see MyDat abase.mdb.7. Click the + button next to the Connection string. You'll see something likePro vider=Micro so f t .Jet .OLEDB.4.0;Dat a So urce="\\...\MyDat abase.mdb" . Click Next .8 . You may be asked if you want to copy the database file to your pro ject. If so , click Yes.9 . In the "Save the Connection String to the Application Configuration File" dialog, accept the default value,leaving the checkbox checked. Click Next .10 . In the "Choose Your Database Objects" dialog, click the + next to Tables and check the boxes next to thePho t o s and Pho t o Cat ego ries tables. Click Finish.

11. Click to save the changes to your pro ject.

You've added a Dataset to your pro ject, along with a connection string. The connection string has been added to a filein your pro ject named app.co nf ig, which contains configuration information about your pro ject. Let's take a quicklook at this file.

Locate the app.co nf ig entry in the So lution Explorer, then double-click it to open the file in an editor. The connectionstring you added is in a section called co nnect io nSt rings. Close the app.config file.

NoteThe App.Config file stores information in XML format. Typically you should not edit this file directly,instead use Studio features to adjust its contents. For example, you may right-click on the pro ject in theSolution Explorer, then select Pro pert ies to open the Pro ject Properties window. Then you'll find theapp.config information under the Settings tab.

A Dat aset represents information from a database, but this information is not "live," so it's o ften referred to as adetached o r offline cache o f your data. After you add a Dataset to your pro ject, there's a new entry in the So lutionExplorer: MyDat abaseDat aSet .xsd. Let's go over this new entry.

Double-click MyDat abaseDat aSet .xsd in the So lution Explorer window. The Dataset Designer window appears, andthe MyDatabaseDataSet tables and relationships are displayed. Also, the DataSet components are now visible in theToolBox:

Page 362: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

We could use the Dataset Designer to create additional database objects, but fo r this lesson, we'll work with existingobjects in the Dataset. Each entry in the Dataset Designer is a Dat aT able (click either table and observe theinformation in the Properties Window). The Dataset Designer also lets us see the tables and table information that'savailable. The MyDatabase Access database file containes two tables: a main Pho t o s table to ho ld photos andinformation about our photos, and a Pho t o Cat ego ries table that serves as a "lookup" table to select a category forour photos. Let's use this Dataset in an application.

Working with DatasetsLet's add a few contro ls to help us work with our data:

Change the entry fo r Form1.cs to MainFo rm.cs, and the Form1 Form title bar's Text property to Pho t o s. Change theStartPosition property to Cent erScreen. Now, make the Form larger. Drag a MenuStrip contro l onto the Form, changethe Name property to mainMenuSt rip, and add the File menu item, along with an Exit submenu item. Implement the

Exit event. Click to save your changes.

We could add contro ls to our Form manually, but Studio provides a convenient way to add contro ls, and have themmap directly to our data: we just drag co lumns from the Dataset within the Data Sources window to our Form. We caneven determine which contro l will be generated. Take a look:

Note The Data Sources window is sensitive to which editor is open and has focus. We will be dragging itemsfrom the Data Sources window to the Form.

Select the Form Design Editor, then in the Data Sources window, click on the + next to MyDat abaseDat aSet toexpand the tree node. Click on the + next to Pho t o s to open the node that displays the available co lumns. Click on thedropdown box next to Photo ID to see the available contro ls:

Page 363: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

As you can see, Studio has selected an appropriate contro l based on the underlying data type o f the table. In the caseof Photo ID, a TextBox contro l is selected. All o f the o ther co lumns have been set to TextBox as well, except fo rDatePhotoTaken, which has been set to a DateTimePicker (which is appropriate for a date field), and Photograph whichhas been set to None. Let's drag one contro l to the Form.

In the Data Sources window, select the Pho t o ID co lumn under the Photos table, and drag this co lumn onto the Form:

Once you've dragged the Photo ID co lumn onto the Form, you'll no tice that a Label contro l and TextBox contro l werecreated. Additionally, a BindingNavigator contro l was added automatically that will let you navigate through records(individual rows) in the Dataset. You can see that BindingNavigator below the Form as photosBindingNavigator, alongwith an instance o f the Dataset (myDatabaseDataSet). Three o ther objects were added automatically:photosBindingSource, a BindingSource contro l used by the BindingNavigator to access the DatasetmyDatabaseDataSet object; photosTableAdapter, a TableAdapter contro l that links the Dataset object to the DatasetmyDatabaseDataSet object; and tableAdapterManager, a TableAdapterManager contro l that manages saving data in

Page 364: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

the correct order. The two tables in our database are related; we have a PhotoCategoryID in the Photos table thatreferences as a foreign key into PhotoCategories.

We didn't save the Photo ID drag operation, because we need to correct the contro l used with this co lumn. Photo ID is aprimary key o f the table, and not a co lumn that the user should be editing manually. Whenever a new record is added tothe table, this co lumn is populated automatically with an auto-incremented number.

Let's fix our contro l. Select the Photo ID label and TextBox contro l and delete them. In the Data Sources window, selectthe Pho t o ID co lumn of the Photos table, and in the Dropdown box, change the contro l type from TextBox to Label.

Drag the Pho t o ID co lumn back onto the Form. Click and to run the program.

When the program runs, you can use the BindingNavigator navigation buttons to move through the records in thedatabase. You could also click on the red X button to delete a record, although the changes are not be saved back tothe database until you click the floppy disk Save button. Changes are saved in the Dataset until you save the changesback to the database.

Tip

When we select our data source, we are prompted to copy our database to our pro ject fo lder. Each time webuild our pro ject, the database is copied to the pro ject's output directory. So when you run the program, youcan delete or change any record you like, because do ing so changes only the version o f the database inthe output directory. Running your application alone may not cause a build. A build will occur only if you'vechanged one o f the files, or if you select one o f the build options under the Build menu. If you want to testyour database changes, run the Photos application, make your changes, exit the Photos application, thenrun the application again.

Let's add the rest o f the fields (except fo r the Photograph co lumn). We'll use a Panel contro l with this contro l. We'llchange the PhotoCategoryID co lumn to a ComboBox contro l, since this co lumn will eventually display photo categorynames, rather than just a photo category ID.

Select the pho t o sBindingNavigat o r and change the Dock property to Bo t t o m . Then, in the Data Sources window,change the Pho t o Cat ego ryID co lumn to a Co mbo Bo x contro l. Drag the Pho t o Cat ego ryID, Pho t o Name ,Dat ePho t o T aken, PlacePho t o T aken, and Pho t o Descript io n co lumns onto the Form, lining them up belowPhoto ID. (See the example below.)

Tip As you add each co lumn, the Label and co lumn contro ls are both selected so you can move them both atthe same time.

Click and to run the program. Now, as you navigate through the records, all o f the contro ls change. ThePhoto ID is now a Label, so you don't have to worry about a user attempting to change the primary key o f the row(which could lead to an error).

What code did the Studio wizards add for us automatically? Look at MainForm.cs:

Page 365: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE: MainForm.cs: Studio Wizard Generated Code

.

.

.private void photosBindingNavigatorSaveItem_Click(object sender, EventArgs e){ this.Validate(); this.photosBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.myDatabaseDataSet);}

private void MainForm_Load(object sender, EventArgs e){ // TODO: This line of code loads data into the 'myDatabaseDataSet.Photos' table. You can move, or remove it, as needed. this.photosTableAdapter.Fill(this.myDatabaseDataSet.Photos);}...

The wizard added two event handlers to our Form code. The first event handler occurs when the Form Load event fires,u sing the pho t o sT ableAdapt er object to fill the myDat abaseDat aSet Dataset object. The second event handleruses the BindingNavigator to save changes, calls validation code, code to end editing, and finally uses thet ableAdapt erManager to save all o f our changes to the Dataset and underlying database.

By dragging contro ls from the Data Sources window, we're creating databound contro ls. Let's verify that data bindusing the Properties window.

Select the Pho t o Name TextBox contro l. In the Properties window, expand the Dat aBindings node and click on theT ext property dropdown. Don't change the binding.

When we run the Photos program, the PhotoCategoryID displays a number. Even though we changed the relatedcontro l to a ComboBox, if you click on the ComboBox, you can't change the category. Let's fix that.

In the Data Sources window, click on the Pho t o Cat ego ries table, then drag and drop it onto the PhotoCategoryIDComboBox contro l. Be careful not to drop the table onto the Form. If you do drop the PhotoCategories onto the Form

accidentally, use Edit | Undo (o r Ct rl+Z ) to undo the table drop action. Click and to run the program.

Now we see the photo category rather than a number. When we link these two tables with our drag-and-drop action,

Page 366: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Studio adds two more contro ls, another BindingSource contro l and TableAdapter, to handle this relationship. Newcode was also added to the Form Load event handler:

OBSERVE:

.

.

.private void MainForm_Load(object sender, EventArgs e){ // TODO: This line of code loads data into the 'myDatabaseDataSet.PhotoCategories' table. You can move, or remove it, as needed. this.photoCategoriesTableAdapter.Fill(this.myDatabaseDataSet.PhotoCategories); // TODO: This line of code loads data into the 'myDatabaseDataSet.Photos' table. You can move, or remove it, as needed. this.photosTableAdapter.Fill(this.myDatabaseDataSet.Photos);}...

Now for the fun part; let's add our Photograph to the form!

Expand the Form window as necessary and add a Panel contro l to the Form. Set the Panel contro l AutoScro ll propertyto T rue , and the BorderStyle to Fixed3D. Change the Photograph co lumn related contro l from No ne to Pict ureBo x.Drag the Photograph co lumn from the Data Sources window onto the Panel. Delete the "Photograph:" Label that wascreated, and move the PictureBox contro l to the top-left corner o f the Panel contro l. Change the PictureBox contro l

SizeMode to Aut o Size . Drag the edges o f the Panel to make it as large as possible in the available space. Click

and to run the program.

The images are bigger than our Panel, so you have to scro ll to see them. Let's modify it so that the images fit insidethe PictureBox.

Select the PictureBox contro l, change the SizeMode to St ret chImage . Click the PictureBox smart tag (the small black

right arrow at the top-right o f the contro l on the Form), and select Do ck in Parent Co nt ainer. Click and torun the program.

Now we can see the complete image, although depending on how you sized your Panel contro l, the image may bedistorted.

We're able to change any o f the data on the form and save it except fo r the Photograph. Let's add code now that lets usdrag a file onto the PictureBox contro l o f an existing record or a new record, and update the Photograph. We'll need touse drag and drop again, which means we need to create a couple o f event handlers. We'll need to test to make surewe're getting a file drop on the PictureBox contro l. We'll also need a bit o f code to make sure we're able to read theimage and use it.

Select the PictureBox contro l, then the Properties Window Event s icon, and double-click the DragEnt er and

DragDro p events. Click to save your changes.

Modify MainFo rm.cs as shown:

Page 367: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void photographPictureBox_DragEnter(object sender, DragEventArgs e){ if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy; else e.Effect = DragDropEffects.None;}

private void photographPictureBox_DragDrop(object sender, DragEventArgs e){ if (e.Data.GetDataPresent(DataFormats.FileDrop)) { string[] filePaths = (string[])e.Data.GetData(DataFormats.FileDrop); if (filePaths.Length > 0) { // Attempt to load, may not be valid image string path = filePaths[0]; try { using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(System.IO.File.ReadAllBytes(path))) { Image tempImage = Image.FromStream(memoryStream); photographPictureBox.Image = new Bitmap(tempImage); } } catch (Exception ex) { MessageBox.Show("Error loading file: " + ex.Message); } } }}...

Click and to run the program. Now you can drag files onto the PictureBox and save them.

Let's discuss this code:

Page 368: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void photographPictureBox_DragEnter(object sender, DragEventArgs e){ if (e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy; else e.Effect = DragDropEffects.None;}

private void photographPictureBox_DragDrop(object sender, DragEventArgs e){ if (e.Data.GetDataPresent(DataFormats.FileDrop)) { string[] filePaths = (string[])e.Data.GetData(DataFormats.FileDrop); if (filePaths.Length > 0) { // Attempt to load, may not be valid image string path = filePaths[0]; try { using (System.IO.MemoryStream memoryStream = new System.IO.MemoryStream(System.IO.File.ReadAllBytes(path))) { Image tempImage = Image.FromStream(memoryStream); photographPictureBox.Image = new Bitmap(tempImage); } } catch (Exception ex) { MessageBox.Show("Error loading file: " + ex.Message); } } }}...

You may remember some of this code from our earlier drag and drop lesson. When handling the drag and drop o ffiles, we use the Dat aFo rmat s FileDro p fo rmat to indicate that we've received a file drop. We extract the list o f filepaths into a string array, and then use only the first file that was dropped on the PictureBox. Then we attempt to read thefile contents using a Memo rySt ream object, attempt to load from the memory stream into an Image object, and thenfinally attempt to create a Bitmap object from the Image object. If our code executes thos tasks successfully, then weset the PictureBox Image property to the newly created Bitmap.

Why are there so many steps required to read the image file? We use the ReadAllByt es File method to read the filecontents into a memory stream. Once that's done, ReadAllBytes closes the file to prevent conflicts with o therprograms. That's a good thing. A memory stream is a block o f memory that we want to use and then dispose o f, so weuse the using syntax to ensure proper disposal o f the object. Before exiting the using block, we attempt to convert thememory stream into an Image object. The Image Fro mSt ream method is a handy way to attempt to convert fileimages into a format that's compatible with a PictureBox. (The FromStream typically supports BMP, GIF, JPG, PNG,and o ther image formats.) Next, we need to create a new Bitmap object to ho ld our Image data, as the Image object willgo out o f scope once we leave the using block and the memory stream will be destroyed.

So, that's your introduction to databases using .NET. By no means have we covered all o f the possible techniques thatmay be used to connect and manipulate database data, but this introduction demonstrates a quick but useful pattern tofo llow.

Practice the techniques you've learned in this lesson in any homework, quizzes and pro jects assigned for this lesson beforeyou move on...

Copyright © 1998-2014 O'Reilly Media, Inc.

Page 369: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 370: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Working With Databases and Visual DataComponents

In this lesson we'll explore interacting with databases using Visual Studio too ls further. We'll also utilize more databasecontro ls, and explore working directly with database information in code.

This lesson includes these topics:

Making the ConnectionModifying Database DataWorking With Datasets

Making the ConnectionWe've already know that when we use the Data Sources window to add a dataset, we're prompted to add the database.In this lesson, we'll begin by adding the database directly.

Select File | New | Pro ject . Change the Name of the pro ject to AddressBo o k and click OK. Add a MenuStrip,rename it to mainMenuSt rip, add File menu and Exit submenu item, create the Exit event handler, and add the exitcode. Rename Form1 to MainFo rm , the Form Text property to Address Bo o k, and the StartPosition property to

Cent erScreen. Click to save your changes.

Right-click the AddressBo o k pro ject in the So lution Explorer, and select Add | Exist ing It em.... In the Add ExistingItem dialog box, navigate to the Pro ject s fo lder (where we have the MyDat abase.mdb Microsoft Access databasefile), and select the file. You may have to change the filter in the dialog box to show All Files (*.*). If the Data SourceConfiguration Wizard dialog box appears, click Cancel.

You see MyDatabase.mdb in your pro ject in the So lution Explorer. Studio has also added a connection to our pro jectdatabase. We could see it by opening the App.Config file, but instead we're go ing to use another window in Studiocalled the Server Explorer.

Open the Server Explorer window, which is located next to or close to the Too lBox window. You may have to make theServer Explorer window visible by selecting View | Server Explo rer.

You may or may not see a small red X like the one in the image. The red X indicates that we are not connected to thedatabase—so if we're not already, let's connect to the database now.

Expand the MyDat abase.mdb node to view the items within the database. Then, expand the T ables node to view thetables within the database:

Page 371: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

It is worthwhile to verify that we are indeed connected to the database in our pro ject file.

Select MyDat abase.mdb in the Server Explorer, then move your cursor over the Connection String property value inthe Properties window to view the connection string.

Modifying Database DataYou may remember that we populated the photo tables in the database, but we didn't populate the Addresses table.Let's see how we can use Studio to add a couple o f rows to our database. We'll need these rows as we start workingwith o ther databound contro ls.

In the Server Explorer window, right-click the Addresses table, and select Ret rieve Dat a from the menu:

You can use the Data Editor window to add new data to the table, update existing data, or delete data. When using theeditor, remember to click away from any co lumn you're changing to make sure the editor notes the change. The rowwith the asterisk represents a new row; as soon as you type in the first field (a co lumn in a specific row) in the new row,two rows will appear. You will be editing the first row; the row below it represents another new row that won't reallyexist until you edit it. The changes you make will no t be saved to the database until you click away from the new rowthat is displayed. As you make changes, red exclamation po ints appear next to each field that you change, and thennext to any row that has not been saved. Let's walk through the process o f adding a row. Avo id editing the AddressIDfield, as this field is automatically incremented when you add a row.

In the MyDatabase Data Editor window, select the Last Name field and enter the name James fo r the value. Continueto enter data for each o f the rest o f the fields according to the fo llowing table. (We won't enter anything in thePhotograph field right now):

Co lumn Value

LastName Jones

FirstName William

Nickname Bill

Address 1122 Nowhere Ave

City Nowhere

StateOrProvince ST

PostalCode 12345

Country USA

EmailAddress [email protected]

HomePhone (111) 123-4567

Page 372: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

MobilePhone (111) 123-4567

WorkPhone (111) 123-4567

Birthdate 01/01/1970

Photograph

Notes John's bro ther

After you enter the last field, click the row beneath the row you've been editing. All o f the red ! icons in the co lumnsdisappear and another red icon might appear next to the row you just added:

Move the cursor over the red icon; you'll see something like this:

The message indicates that we need to refresh the data.

In the Data Editor window, right-click and select Execut e SQL:

Page 373: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Now there are no red error icons and the data has been added. Note that the AddressID field was incremented. That is,the AddressID field was incremented by one from the AddressID that was previously the greatest number, even if wasdeleted. The number you see is relative to the next greatest number for that field, including any rows that have beendeleted.

Repeat the process to add two more rows in the Data Editor window. Enter data that's different from the data weentered.

Once you've added a couple o f new rows, delete a row. When deleting a row, make sure you've selected the entire rowand not just a field. Select an entire row by clicking the button to the left o f the first co lumn in the row, which will highlightthe entire row.

Tip If you accidentally delete a field value, just press Esc to undo the change.

Select the last row you added in the Data Editor window by clicking the button to the left o f the first co lumn. Then, right-click anywhere on the selected row, and select Delet e from the menu (or press the Del key). A delete rowconfirmation dialog box appears. Select Yes to confirm that you want to delete the row:

Page 374: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

It's convenient to edit database data using Visual Studio , but remember that we're editing the version o f the database inthe pro ject, and that this database will be copied into our pro ject output directory, so any database changes we makewill be part o f our running application. However, any changes we make to the database while our application is runningwill be saved only within the database that was copied to the output directory.

Close the Data Editor window now.

Working With DatasetsWe've seen how important a Dataset object is fo r working with our database data. Now let's look at another way to adda Dataset using Studio . You may find this method easier.

NoteYou'll want to have the Server Explorer window and Data Sources window visible. If the Data Sourceswindow is not visible already, select Dat a | Sho w Dat a So urces. If the Show Data Sources option is notvisible, switch to the Form Designer window and try again.

Right-click the AddressBo o k entry in the So lution Explorer, select Add | New It em.... In the Add New Item dialog,select Dat aSet , change the Name to Addresses, and click Add. When the empty Dataset Designer window appears,select the Addresses table in the Server Explorer window, and drag and drop the Addresses table onto the Dataset

Designer window. Click to save your changes.

Look at the Data Sources window. It now contains our Addresses Dataset, and one entry in the Dataset, the Addressestable. However, as we've seen previously, a Dataset may contain more than one entry. And, in fact the entry isn't reallyjust a table, but instead the results o f a Structured Query Language (SQL) statement. SQL is the language used tointeract with relational databases such as Microsoft Access (as well as Microsoft SQL Server, Oracle, MySQL, andmany o thers). Let's take a look at the SQL that underlies our Addresses "table" (actually the Addresses DataTable) inthe Addresses Dataset.

In the Dataset Designer window, right-click the Fill, Get Dat a() method entry o f the Addresses DataTable, and selectCo nf igure :

Page 375: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

The TableAdapter Configuration Wizard contains information about stuff that's loaded into the Addresses DataTable.The text in the Wizard is SQL, that lists which co lumns from the Addresses table to include. We won't discuss SQL indetail in this lesson, but let's at least look at this basic SQL statement to clarify how it works:

OBSERVE:

SELECT AddressID, LastName, FirstName, Nickname, Address, City, StateOrProvince, PostalCode, Country, EmailAddress, HomePhone, MobilePhone, WorkPhone, Birthdate, Photograph, NotesFROM Addresses

The statement specifies the co lumns in the table and the t able name . The SQL keyword SELECT specifies whichco lumns to include, and FROM specifies the source. Let's create another entry in our Dataset that specifies a minimumamount o f information about our contacts, including their birthdays.

Drag the Addresses table from the Server Explorer window onto the Dataset Designer window again. Click on theTitlebar o f the Addresses1 DataTable in the Dataset Designer, and change the title from Addresses1 to Birt hdays (o ryou can change the Name property in the Properties window). Right-click the Fill, Get Dat a() method entry o f theBirthdays Data Table, and select Co nf igure . Modify the SQL statement as shown:

Page 376: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

SELECT AddressID, LastName, FirstName, Nickname, Address, City, StateOrProvince, PostalCode, Country, EmailAddress, HomePhone, MobilePhone, WorkPhone, Birthdate, Photograph, Notes FROM Addresses

Click Finish. Now we have two different table entities we could reference to interact with the Dataset, and potentially theunderlying database. Why just potentially? We may decide we don't want to update the data, but use only a Dataset fo rviewing data. In fact, let's preview the data now.

Right-click the Fill, Get Dat a() method entry o f the Birthdays DataTable and select Preview Dat a. In the Preview Datadialog box, click the Preview button to see the data. You can select the Addresses item to preview as well, using theSelect an o bject t o preview dropdown.

In the Data Sources window, we can see our Dataset and the two tables based on underlying SQL. These tables,Addresses, and Birthdays, are known as typed datasets. When we drag a field from one o f these typed datasets aswe've done in the past, we create contro ls bound to a single field. Once used, these typed datasets cannot be removedfrom your pro ject, as they form the basis o f classes used to create o ther objects. If you look in the So lution Explorer,you'll see an entry fo r Addresses.xsd that was created with the creation o f the dataset. Within this node are entries forthe C# and o ther code used for instances o f this object.

When we drag an item from the Data Sources window onto the Form, the data type o f the contro l that's createddepends on which kind o f item we dragged. For individual fields, we got a databound contro l like a TextBox or Label.We can also drag the entire table onto the Form, but the contro ls that are created depend on which "mode" we'veselected. Just like with individual fields, the table has a dropdown that may be used to select either DataGridView orDetails view. By default, DataGridView is selected, which means that if you drag the table onto the Form, you'll create adatabound DataGridView contro l. If you have Details selected, you'll create individual contro ls fo r all o f the co lumnsbased on the settings for each field. Since we've already worked with individual fields, let's create the DataGridViewcontro l.

Make sure the MainForm.cs Design view is open. In the Data Sources window, click the Addresses DataTable entry(not the top-level node, that represents the entire dataset), and make sure that Dat aGridView is selected in the

Page 377: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

dropdown menu. Drag the Addresses DataTable entry onto the Form. Don't save your changes:

We could use this DataGridView contro l to add, edit, and delete data, but a better approach is to use a master-detailinterface. Let's undo the drag operation first.

Click on the Form, highlight the DataGridView contro l, and delete it. In the component tray, delete all o f the contro lslisted except fo r the mainMenuStrip. Edit MainFo rm.cs and remove theaddressesBindingNavigat o rSaveIt em_Click event handler method completely; also remove all lines o f code

from the MainFo rm_Lo ad event handler, but not the method itself. Click to save your changes.

Now that we have our empty Form back, let's discuss how we're go ing to create a master-detail interface. A master-detail interface means that you have a list o f items, and when you select an item in the list, you can see the detailsseparately. We want to create an address book application that uses a DataGridView contro l to list our addresses.When we double-click on an entry, another Form appears with individual fields for us to edit. Once we've completed theedit, our changes will be saved to the database, and our list updated. We'll also add menu items for adding, editing,and deleting address entries. We'll reduce the co lumns displayed in the list. The images below illustrate the userinterface we'll create:

Page 378: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Let's get started.

Edit the mainMenuStrip to include Add, Edit and Delet e submenu options under the File menu, with a separatorbetween the new submenu items and Exit. Double-click the Add, Edit , and Delet e submenus to generate event

handlers for their Click events. Click to save your changes.

Page 379: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Drag a Dat aGridView from the Too lBox onto the Form. Select the upper-right smart tag o f the DataGridView contro lon the Form, and select Do ck in Parent Co nt ainer; in the Choose Data Source dropdown, select Ot her Dat aSo urces | Pro ject Dat a So urces | Addresses | Addresses. Change the Name property in the Properties window

to addressesDat aGridView. Click to save your changes. Click to run the program:

We've added a DataGridView that will act as the master, but we need to make a few changes to the DataGridProperties.Specifically, we want to prevent anyone from attempting to add or delete data to the DataGridView, and we want anymouse clicks to select an entire row rather than just a cell on the grid. We'll also remove some of the co lumns.

Select the DataGridView, and in the Properties window, change the AllowUserToAddRows to False ,AllowUserToDeleteRows to False , ReadOnly to T rue , and SelectionMode to FullRo wSelect . Select theDataGridView smart tag, and select Edit Co lumns.... In the Edit Co lumns dialog box, remove the Address, Cit y,St at eOrPro vince , Po st alCo de , Co unt ry, and Pho t o graph co lumns by selecting each co lumn and clicking theRemo ve button. Click Ok.

Page 380: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click the smart tag again if necessary to hide the smart tag dialog box. Click to save your changes. Next, let's adda new Form to the pro ject that will serve as the detail page that we'll use to add and edit address information.

Click AddressBo o k in the So lution Explorer and select Add | New It em . In the New Item dialog, select Windo wsFo rm , change the Name to AddEdit , and click Add. Increase the size o f the Form to match the interface in the imagebelow. Change the Form Text property to Add/Edit . Add a Button contro l and change the Name property tosaveBut t o n, the Text property to Save , and the DialogResult property to OK. Add another Button contro l and changethe Name property to cancelBut t o n, the Text property to Cancel, and the DialogResult property to Cancel. Double-

click each Button contro l to generate Click event handler methods. Click to save your changes.

We set the DialogResult properties o f both buttons so we can determine which Button contro l was clicked when theForm is closed. Now let's add the databound contro ls from the Addresses Dataset.

In the Data Sources window, expand the Addresses DataTable entry node. Click the Addresses DataTable entry, andmake sure the dropdown shows Det ails. Next, make sure that all o f the field related contro ls are set to T ext Bo x,except fo r AddressID, which should be changed to Label, and Photograph which should be No ne . Drag theAddresses DataTable entry to the AddEdit Form near the top. Expand the width o f the Form. Add a Panel contro l to theright o f the o ther contro ls. Change the Panel AutoScro ll property to T rue , and BorderStyle property to Fixed3D. Add aLabel above the Panel, and change the Text property to Pho t o graph:. Change the related contro l o f the Photographfield in the Data Sources window to PictureBox, and then drag it onto the Panel contro l. Delete the Label contro l thatwas created, and click the PictureBox smart tag and click Do ck in Parent Co nt ainer. Set the PictureBox contro lSizeMode to St ret chImage . In the component tray, delete the addressesBindingNavigator. See the image o f the

detail interface above for the final appearance o f the AddEdit Form. Click to save your changes.

Modify AddEdit .cs as shown:

Page 381: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.public partial class AddEdit : Form{ public int Position { get; set; } public AddEdit() { Position = -1; InitializeComponent(); } private void saveButton_Click(object sender, EventArgs e) { this.Validate(); this.addressesBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.addresses); } private void cancelButton_Click(object sender, EventArgs e) { this.addressesBindingSource.CancelEdit(); } private void addressesBindingNavigatorSaveItem_Click(object sender, EventArgs e) { this.Validate(); this.addressesBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.addresses); } private void AddEdit_Load(object sender, EventArgs e) { // TODO: This line of code loads data into the 'addresses._Addresses' table. You can move, or remove it, as needed. this.addressesTableAdapter.Fill(this.addresses._Addresses); if (Position >= 0) { this.Text = "Edit"; this.addressesBindingSource.Position = Position; } else { this.Text = "Add"; this.addressesBindingSource.AddNew(); } } }...

Click to save your changes.

Let's discuss this code:

Page 382: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.public partial class AddEdit : Form{ public int Position { get; set; } public AddEdit() { Position = -1; InitializeComponent(); } private void saveButton_Click(object sender, EventArgs e) { this.Validate(); this.addressesBindingSource.EndEdit(); this.tableAdapterManager.UpdateAll(this.addresses); } private void cancelButton_Click(object sender, EventArgs e) { this.addressesBindingSource.CancelEdit(); } private void AddEdit_Load(object sender, EventArgs e) { // This line of code loads data into the 'addresses._Addresses' table this.addressesTableAdapter.Fill(this.addresses._Addresses); if (Position >= 0) { this.Text = "Edit"; this.addressesBindingSource.Position = Position ; } else { this.Text = "Add"; this.addressesBindingSource.AddNew(); } }}...

The code we deleted was an orphaned event handler left over from when we deleted the BindingNavigator. We won'tneed the ability to change records, and we'll save any changes using code. We copy the code that was in theBindingNavigator Save event handler to our Save event handler so we can use it later. The code we copy to thesaveButton Click event handler calls the Form Validat e method to initiate a validation step, then calls theaddressesBindingSo urce EndEdit to apply any pending changes to the underlying database source, finally callingthe t ableAdapt erManager Updat eAll to update the changes to the Dataset, and so to the underlying database. Thesave event handles both adds and edits.

How do we know if we're adding or editing? We added a public class property, Po sit io n, that will be used to pass therecord to be edited into the Form. When we load the Form, we check to see whether the Po sit io n is greater than orequal to 0 . Checking an existing record means we're editing, so we set the Form Text property (the titlebar text) to Edit,and then set the active record in the addressesBindingSo urce by setting the Po sit io n property to the Po sit io nvalue. If we're adding, we set the Form titlebar to Add, and call the addressesBindingSo urce AddNew method. Notethat we set the default Po sit io n to -1 in the Form constructor.

If we decide not to edit after all, we can click the Cancel Button, which calls the addressesBindingSo urceCancelEdit .

Note that we do not attempt to close the AddEdit Form. When you assign DialogResult properties to Buttons on aForm, when you click such Button contro ls, the Form will be closed automatically.

Page 383: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Now, we just need to figure out whether we're adding or editing, and call the AddEdit Form. Let's make those changesnext.

On the MainForm, select the DataGridView, and in the Event s property window, double-click the Do ubleClick eventto generate an event handler fo r this event. Modify MainFo rm.cs as shown:

Page 384: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void addToolStripMenuItem_Click(object sender, EventArgs e){ AddEdit addEdit = new AddEdit(); if (addEdit.ShowDialog() == DialogResult.OK) this.addressesTableAdapter.Fill(this.addresses._Addresses);}

private void editToolStripMenuItem_Click(object sender, EventArgs e){ HandleDataGridDoubleClick();}

private void deleteToolStripMenuItem_Click(object sender, EventArgs e){ int rowIndex = addressesDataGridView.CurrentRow.Index; if (rowIndex >= 0) { string addressID = addressesDataGridView[0, rowIndex].Value.ToString(); int bindingSourceID = addressesBindingSource.Find("AddressID", addressID); if (bindingSourceID >= 0) { DialogResult dialogResult = MessageBox.Show("Delete " + addressID, "Delete Entry", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); if (dialogResult == DialogResult.OK) { this.addressesBindingSource.RemoveAt(bindingSourceID); this.tableAdapterManager.UpdateAll(this.addresses); addressesDataGridView.ClearSelection(); } } }}

private void addressesDataGridView_DoubleClick(object sender, EventArgs e){ HandleDataGridDoubleClick();}

private void HandleDataGridDoubleClick(){ int rowIndex = addressesDataGridView.CurrentRow.Index; if (rowIndex >= 0) { string addressID = addressesDataGridView[0, rowIndex].Value.ToString(); int bindingSourceID = addressesBindingSource.Find("AddressID", addressID); if (bindingSourceID >= 0) { AddEdit addEdit = new AddEdit(); addEdit.Position = bindingSourceID; if (addEdit.ShowDialog() == DialogResult.OK) { this.addressesTableAdapter.Fill(this.addresses._Addresses); addressesDataGridView.ClearSelection(); addressesDataGridView.Rows[rowIndex].Selected = true; } } }}...

Page 385: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click and to run the program.

When you run the program, you can double-click a row to edit it, o r you can select a row, and then select File | Edit o rFile | Delet e to edit o r delete an entry, o r select File | Add to add a new entry. Let's discuss this code:

Page 386: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

.private void addToolStripMenuItem_Click(object sender, EventArgs e){ AddEdit addEdit = new AddEdit(); if (addEdit.ShowDialog() == DialogResult.OK) this.addressesTableAdapter.Fill(this.addresses._Addresses);}

private void editToolStripMenuItem_Click(object sender, EventArgs e){ HandleDataGridDoubleClick();}

private void deleteToolStripMenuItem_Click(object sender, EventArgs e){ int rowIndex = addressesDataGridView.CurrentRow.Index; if (rowIndex >= 0) { string addressID = addressesDataGridView[0, rowIndex].Value.ToString(); int bindingSourceID = addressesBindingSource.Find("AddressID", addressID); if (bindingSourceID >= 0) { DialogResult dialogResult = MessageBox.Show("Delete " + addressID, "Delete Entry", MessageBoxButtons.OKCancel, MessageBoxIcon.Question); if (dialogResult == DialogResult.OK) { this.addressesBindingSource.RemoveAt(bindingSourceID); this.tableAdapterManager.UpdateAll(this.addresses); addressesDataGridView.ClearSelection(); } } }}

private void addressesDataGridView_DoubleClick(object sender, EventArgs e){ HandleDataGridDoubleClick();}

private void HandleDataGridDoubleClick(){ int rowIndex = addressesDataGridView.CurrentRow.Index; if (rowIndex >= 0) { string addressID = addressesDataGridView[0, rowIndex].Value.ToString(); int bindingSourceID = addressesBindingSource.Find("AddressID", addressID); if (bindingSourceID >= 0) { AddEdit addEdit = new AddEdit(); addEdit.Position = bindingSourceID; if (addEdit.ShowDialog() == DialogResult.OK) { this.addressesTableAdapter.Fill(this.addresses._Addresses); addressesDataGridView.ClearSelection(); addressesDataGridView.Rows[rowIndex].Selected = true; } } }}...

Page 387: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Some of the code is similar to what we have in the AddEdit Form. We add a method calledHandleDat aGridDo ubleClick so we can call this method from both the DataGridView DoubleClick event handlerand from the event handler fo r the Edit menu. The HandleDat aGridDo ubleClick method determines if a row of theDataGridView is selected, then proceeds to extract the value o f the AddressID co lumn, which is the unique key into theAddresses table. Then we use the BindingSource addressesBindingSo urce Find method to locate the index in theBindingSource object o f the row we want to edit, passing in the name of the AddressID co lumn, and the index valueaddressID we just extracted from the DataGridView. We verify that we've retrieved a valid BindingSource index valueby testing bindingSo urceID; if it is in fact valid, we create an instance o f the AddEdit Form, setting the FormPo sit io n property to the index into the BindingSource. Next, we call the Form Sho wDialo g method to display amodal version o f the Form, checking the returned value against Dialo gResult .OK to determine if the Save button wasclicked on the AddEdit Form. If the Save button was clicked, then we call the Fill method o f theaddressesT ableAdapt er, which will load the Addresses Dataset. This process will in turn force the DataGridView toupdate itself, because its underlying data was updated. Then we clear any selected rows o f the DataGridView (whenrefilled, the DataGridView will select the first row by default), and select the row that was being edited.

When the Add menu item is selected, we again create an instance o f the AddEdit Form, handle the return value, andrefill the dataset if the user clicked the Save button.

Similarly, when deleting an entry, we determine whether we have a valid row selected, but this time we prompt the userto confirm that the entry is to be deleted. If they confirm the action, we call the Remo veAt method o f theaddressesBindingSo urce , call the Updat eAll method o f t ableAdapt erManager to update the dataset, then againclear any selected rows in the dataset. If we don't set another row, the first row in the DataGridView is selected bydefault. Although the row isn't highlighted, you can see a small arrow to the left o f the first co lumn, indicating that it's thecurrent selected row.

So, that concludes a somewhat more advanced application using database contro ls. The application is by no meansfinished, but demonstrates many useful techniques and features to create an application that uses a database.

Practice what you've learned here in any homework, pro jects, and quizzes for this lesson.

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.

Page 388: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

A Database GUI Application

In this last lesson, we will learn more about working with databases, using and manipulating data-bound contro ls, and using C#to further enhance working with both databases and data-bound contro ls. As part o f this lesson, you'll create a program to trackyour money. We'll help you along the way, but fo r the most part you'll working on your own.

This lesson includes these topics:

Course Pro jectApplication Development Steps

Course Project

The Assignment

You will create an application called MyMoney. This application will present a checkbook register that you canuse to record your income and expenses. Each entry in your register must be associated with a category.Each category is identified as either a credit category (income), or a debit category (expense). When you addan item to the register and select a category, the application will know if this entry is income or an expensebased on the category.

User Interface

The user interface elements o f the MyMoney application are:

Page 389: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You
Page 390: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Class Design Diagram

Below are the Class Design Diagrams for each o f the three forms used in the MyMoney application. Only thecomponents and methods we added are displayed; the database components are not. In addition, theconstructor o f each Form is displayed:

Page 391: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You
Page 392: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

TOE Chart

You'll need to determine the contro ls to use for each Form in the pro ject by examining the class diagrams foreach form, and the user interfaces o f each Form. As illustrated in the class design diagrams above, there arethree forms:

MainForm: This fo rm includes the menus, status strip, register, and the contro ls to add an item tothe register.CategoriesForm: This fo rm displays when you select File | Categories..., and is used to add, delete,or edit categories.CategoryBreakdownForm: This fo rm displays when you select Reports, Category..., and is used todisplay a breakdown of amount to tals by category for a specified date range (with a default o f thefirst and last day o f the current month and year).

All o f the forms have a horizontal SplitContainer contro l that should be placed on the Form first.

Use Cases - MainForm

Start application, load register from database into register DataGridView contro l, load categoriesinto category ComboBox contro l, update balance amount displayed on StatusStrip contro l.Use contro ls to add a new register item.Right-click the DataGridView contro l to bring up a ContextMenuStrip to display an option to deletethe selected register row.Display categories Form from menu selection.Display category breakdown Form from menu selection.Exit application from menu selection.

Use Cases - CategoriesForm

Display categories in DataGridView contro l that supports editing category information.Display and support BindingNavigator contro l to allow navigating, saving, or deleting categoryinformation.Prevent deleting category that is associated with any existing register items.Close the Form.

Use Cases - CategoryBreakdownForm

Display amount breakdown by category through first and last day o f current month and year.Update breakdown upon start o r end date change.

Page 393: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Close the Form.

DataSet Designer

Coding Requirements

As you create your pro ject, use programming techniques you've learned in this course. Even when we goover new material and illustrate how to implement the so lution, feel free to experiment using differenttechniques to accomplish tasks. For example, we're go ing to use explicit event handler methods, but you'refree to use delegates or lambda expressions if you like. Use try/catch blocks around code you add thatinteracts with the database.

Application Development StepsThis section lists all o f the steps you'll need to take to create the MyMoney application. New material will be introducedand explained in some of those steps:

First Steps

1. Create the MyMoney pro ject.2. Click MyMoney to download the Microsoft Access database and then use So lut io n Explo rer |Add | Exist ing It em to add it to the pro ject. When you add the database, complete the Data

Page 394: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Add | Exist ing It em to add it to the pro ject. When you add the database, complete the DataSource Configuration Wizard, create the connection string and the MyMoneyDataSet data set,select both the Categories, and Register tables from the MyMoney database.3. Rename the Form "MainForm," change the titlebar, and change the application starting position.4. Add and rename a MenuStrip and StatusStrip to the Form. Add menu items to the Menu Strip, andadd event handlers for the menu items. Add and rename a Too lStripStatusLabel to the StatusStrip.5. Add, rename, and change to horizontal o rientation a SplitContainer to the Form.6. Add, rename, and dock a DataGridView contro l to the top Panel o f the SplitContainer.7. Add and rename all o f the contro ls to the bottom Panel o f the SplitContainer. Change the Formatof the DateTimePicker to Sho rt , and the DropDownStyle o f the ComboBox to Dro pDo wnList .Add an event handler fo r the Button contro l.

Categories ComboBox

The MyMoney database contains two tables: Categories and Register. The Register table contains a co lumnthat references the Categories table. We could have linked these tables through this reference, and thendragged the Register entry from our DataSet using the DataSet Designer to create a DataGridView contro l, butwe want to illustrate using code for more database customization.

Now let's populate the Category ComboBox with all o f the categories by updating the ComboBox to adatabound contro l. When setting these properties manually, we select the co lumn to be displayed in theComboBox, and the co lumn to be returned as the value when selecting from the list o f items in theComboBox. We will use the categoryName for display, but return the categoryID as the value. Why? ThecategoryID field is stored in the Register table that makes the match possible.

Select the categoryComboBox smart tag, check the Use Dat a Bo und It ems box, and set the DataSource toCategories by selecting Ot her Dat a So urces | Pro ject Dat a So urces | MyMo neyDat aSet |Cat ego ries. Set Display Member to cat ego ryName , and Display Value to cat ego ryID.

Click and to run the program.

After setting the Data Source for the ComboBox, you'll no tice that three components were added to theComponent Tray (the area below the Form Designer). The component instances will be used to populate theCategory ComboBox. Also, just as we've seen before, code was added to the Form Load event handlerautomatically.

Note

We often see items appear in the Component Tray that we refer to as non-visual items, butactually many items that appear in the Component Tray do have visual aspects, such as aMenuStrip. The difference lies between Contro ls and Components. Contro ls are added directlyto a Form or o ther Contro l, whereas a Component is displayed on the Component Tray. Theway Contro ls and Components differ is not always apparent. Typically, a Contro l is aComponent that provides or enables user interface capabilities, but we've seen Componentsthat also include a user interface. Just keep in mind that a Component implements theIComponent interface.

Page 395: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

OBSERVE:

.

.

. private void MainForm_Load(object sender, EventArgs e){ // TODO: This line of code loads data into the 'myMoneyDataSet.Categories' table. You can move, or remove it, as needed. this.categoriesTableAdapter.Fill(this.myMoneyDataSet.Categories);}...

When we run the application, you may notice that the categories are not sorted. Let's modify the SQL codethat selects the data from the database to add a sort o rder.

In the So lution Explorer, double-click the MyMo neyDat aSet .xsd entry to bring up the DataSet Designer.Modify the SQL query by adding "ORDER BY categoryName" to the existing SQL statement:

Page 396: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click and to run the program. Now, the categories in the ComboBox are sorted.

When you run the application, you may also notice in the Immediate Window a "First chance exception" errorlike this:

When debugging a program by running the application in the IDE, exceptions are trapped by the debugger. So,what exactly is a first chance exception? A first chance exception indicates the first time that our code hasraised a particular exception and the exception has been handled by the debugger. We can break onexceptions to determine where they occur. If we choose to not break on exceptions, then the behavior thattakes place when an exception occurs will be dependent upon whether the exception is handled. If theexception is handled, the IDE will no tify us o f the first chance exception, and then continue. If the exception isnot handled, depending on the exception, the debugger may stop at the line o f code and bring up a warningbox. Let's see if we can determine which line o f code is causing the problem.

NoteIf you don't see the first chance exception in the Immediate Window, Studio must not beconfigured to redirect all output to the immediate window. You can change that setting in T o o ls |Opt io ns | Debugging | General, and then check the Redirect all Out put Windo w t ext t ot he Immediat e Windo w box.

Select Debug | Except io ns..., expand Co mmo n Language Runt ime Except io ns and Syst em , checkthe T hro wn box for Syst em.Argument Out Of RangeExcept io n, and click OK.

Page 397: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click and to run the program.

The exception occurs in Studio-generated source code when populating the Category ComboBox. Theexception indicates that the SelectedIndex parameter is invalid. This exception appears to be handled, so wecould ignore it, but let's apply a fix. The exception occurs while we're populating the categories DataSet.Because the Category ComboBox is bound, when updating the underlying DataSet, events are triggeredwithin the ComboBox, but unfortunately the data is not quite ready as these events "fire." To fix that, weremove the DataSource o f the ComboBox temporarily, populate the DataSet, then reconnect the DataSource.We also have to reset the Display Member and Display Value properties o f the ComboBox.

Modify MainFo rm.cs as shown:

Page 398: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void MainForm_Load(object sender, EventArgs e){ // TODO: This line of code loads data into the 'myMoneyDataSet.Categories' table. You can move, or remove it, as needed. this.categoriesTableAdapter.Fill(this.myMoneyDataSet.Categories); updateCategoryDropdown();}

private void updateCategoryDropdown(){ string displayMember = categoryComboBox.DisplayMember; string valueMember = categoryComboBox.ValueMember; categoryComboBox.DataSource = null; this.categoriesTableAdapter.Fill(this.myMoneyDataSet.Categories); categoryComboBox.DataSource = categoriesBindingSource; categoryComboBox.DisplayMember = displayMember; categoryComboBox.ValueMember = valueMember;}...

Click and to run the program.

Now when we run the application, no exception is raised. Also, we can refresh the register items now bycalling the updat eCat ego ryDro pdo wn method. We'll need this method when we implement the deletefunctionality.

Populating the Register DataGridView

As we mentioned earlier, we could have dragged from the DataSet Designer onto the Form to create theDataGridView of the Register table, but if we did that, we would see a number for the category, rather than thecategory name. We'll create a select-only TableAdapter using the DataSet Designer that we'll use to populatethe Register DataGridView contro l in a minute, but first, let's add an entry into the Register table.

Use the Server Explorer to select the Register table, right-click to Ret rieve Dat a, then edit the first row tocontain data, like this:

Remember to Execut e SQL! Now let's create a new TableAdapter.

Right-click in the DataSet Designer window and select Add | T ableAdapt er. In the TableAdapterConfiguration Wizard, accept the defaults until you get to the SQL Statement screen. Click the AdvancedOpt io ns... button and deselect all o f the checkboxes. Enter this SQL code into the textbox:

Page 399: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

SELECT Register.registerID, Register.registerDateTime, Register.registerIdentifier, Register.registerAmount, Register.registerDescription, Register.registerNote, Categories.categoryName, Categories.isDebitFROM (Categories INNER JOIN Register ON Categories.categoryID = Register.categoryID)ORDER BY Register.registerDateTime, Register.registerID

On the Methods to Generate screen, check only the Fill a Dat aT able box (deselect the Ret urn aDat aT able option), then click Finish.

Page 400: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

You'll see a new DataTable called Dat aT able1 on the DataSet Designer. Let's rename it.

Click on the titlebar o f the Dat aT able1 DataTable on the DataSet Designer and change the name toRegist erCat ego ry.

You may remember that you can right-click the Fill() entry on the DataTable, and select Preview Dat a... tosee what the SQL query will return. When we run this query now, rather than just a number for the categoryID,we see the categoryName displayed:

To see the Register data in the DataGridView, we need to set the Data Source.

Select the registerDataGridView smart tag, and set the Data Source to the Regist erCat ego ry entry. Also,uncheck the Enable Adding, Enable Edit ing, and Enable Delet ing boxes.

Click and to run the program.

NoteJust as when we set the Data Source o f the ComboBox, when we set the DataGridView DataSource, a line o f code was added to the Form Load event to populate the DataSet, and morenon-visual components were added to the Component Tray.

The DataGridView is displaying the Register table data. However, there are co lumns we don't need to see, theco lumn headers should be changed, and we need to make the money co lumn display as currency. Let's

Page 401: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

make those changes.

Click the registerDataGridView smart tag and select Edit Co lumns. Select the regist erID co lumn andchange the Visible property to False . Also , make the isDebit co lumn not visible. Change the HeaderTextproperty o f the remaining co lumns and change them to Dat e , ID, Amo unt , Descript io n, No t e , andCat ego ry. Select the Cat ego ry co lumn and use the up arrow to move it above the Note co lumn torearrange the co lumn display order. Select the Amo unt co lumn, select the ellipsis (...) o f theDef ault CellSt yle property, and in the CellStyle Builder dialog, select the ellipsis o f the Fo rmat property. Inthe Format String Dialog, select Currency. Click OK a few times to save the changes.

Click and to run the program. The Register co lumn headers should now match the user interfaceimages previously shown.

Inserting a New Record

We've seen how to use the DataGridView contro l to add a new record to the database, now let's see how toinsert a new record using code. We've discussed the non-visual components in the Component Tray, butwhere did those contro ls come from? We know that some of the contro ls are added automatically by Studioas we bind a contro l to a Data Source, but what about their definitions?

Select the MainForm in the Form Editor. Select the Too lBox and scro ll to the top until you see a section calledMyMo ney Co mpo nent s. Select Build | Clean MyMo ney, and note that the MyMoney Components areremoved from the Too lBox. Select Build | Rebuild MyMo ney, no ting that the MyMoney Componentsreappear.

Page 402: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

As we add Data Sources, DataSets, and TableAdapters to our pro ject, Studio constructs components that wecan use. When we Clean the pro ject, all intermediate and output files are deleted. When we Rebuild thepro ject, the components are rebuilt. The MyMoney Components in the Too lBox include DataSet andTableAdapters that have been created for us to use in our pro ject.

Select each item in the MainForm Component Tray and note its data type in the Properties Window:

You can identify the data type for each o f the components in the Component Tray: MenuStrip, StatusStrip,DataSet, TableAdapter, o r BindingSource. All o f the components have standard data types except fo r theDataSet and TableAdapter components. The data types o f these components are noted in this table:

Co mpo nent Inst ance Dat a T ype

myMoneyDataSet MyMoney.MyMoneyDataSet

categoriesTableAdapter MyMoney.MyMoneyDataSetTableAdapters.CategoriesTableAdapter

registerCategoryTableAdapter MyMoney.MyMoneyDataSetTableAdapters.RegisterCategoryTableAdapter

Each o f the data types o f these components is a new data type. MyMoneyDataSet contains TableAdaptersthat are specific to our Data Source, and the TableAdapters provide the communication link between ourapplication and the database. Each o f the TableAdapters exposes methods that we can use to performdatabase selection, insertion, or deletion. The methods exposed by TableAdapters are typed to co incide withthe underlying data in the database.

To insert a new record into the Register table, we'll use an instance o f the RegisterTableAdapter.

Drag an instance o f the Regist erT ableAdapt er from the MyMoney Components section o f the Too lBoxonto the Component Tray o f the MainForm Form. Click the regist erT ableAdapt er1 entry in the Component

Tray, and rename the component instance to regist erT ableAdapt er in the Properties Window. Click .

Now, we have a TableAdapter that we can use to insert data into the Register using code.

Modify MainFo rm.cs as shown:

Page 403: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void addButton_Click(object sender, EventArgs e){ // Get category int categoryID = Convert.ToInt32(categoryComboBox.SelectedValue); // Get amount decimal amount = 0.0m; decimal.TryParse(amountTextBox.Text, out amount); // Get date DateTime date; DateTime.TryParse(dateTimePicker.Value.ToShortDateString(), out date); // Perform insert registerTableAdapter.Insert( categoryID, string.IsNullOrWhiteSpace(identifierTextBox.Text) ? null : identifierTextBox.Text, string.IsNullOrWhiteSpace(descriptionTextBox.Text) ? null : descriptionTextBox.Text, string.IsNullOrWhiteSpace(noteTextBox.Text) ? null : noteTextBox.Text, amount, date); // Refresh dataset from database registerCategoryTableAdapter.Fill(myMoneyDataSet.RegisterCategory); // Clear add controls identifierTextBox.Text = ""; descriptionTextBox.Text = ""; amountTextBox.Text = ""; noteTextBox.Text = "";}...

Note the parameters for the registerTableAdapter:

Click then to run the program.

You'll see a new entry in the Register DataGridView contro l, unless an exception was raised because you lefto ff the description. We'll discuss this issue in more detail shortly.

Most o f this code should be familiar, except fo r the use o f the Insert method o f the registerTableAdapter. Weuse the ternary operator to test to find out whether the user entered something, and if no t, we use null as thevalue to be inserted. We often use null with database fields when we have no data. Null is not the same as anempty string. Some of the fields in the database are required and may not be null (categoryID,registerDescription, registerAmount, registerDateTime). If we try to add a null value to one o f these fields, we'llget an error, so make sure to wrap the last lines o f code with a try/catch block that handlesArgumentNullException.

Modify the addButton_Click function by surrounding all o f the code from the Insert to the end o f the eventhandler with a try/catch block that traps for ArgumentNullException. In the catch block, use the MessageBoxmethod to display the exception Message.

Page 404: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Tip To find out which fields do not allow null, click on each co lumn of the Register table in the ServerExplorer; you'll see the properties for that co lumn, and find out which include the Nullable property.

Register Balance and Nullable Data Types

We still have more to do to finish adding an item to our register; we need to get the register balance, and weneed to account fo r whether an amount is a credit o r a debit, depending on which category was selected. Forboth o f these functions, we'll add new database queries.

Dat aT able Funct io n Descript io n

Categories IsCategoryDebit(categoryID) Given a categoryID, return true if category is a debit, false if acategory is not a debit.

Register GetBalance Return the balance o f all rows in the register.

Let's add the IsCategoryDebit query to the Categories Data Table first.

In the Data Set Designer, right-click the Categories Data Table Fill, Get Dat a() entry, and select Add |Query.... Review the images below before you start, then proceed to add the IsCat ego ryDebit query. Youmust write the query exactly as shown, including the question mark.

Page 405: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You
Page 406: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Once you finish, you'll see the IsCategoryDebit entry in the Categories Data Table.

When you wrote the query, you used a question mark. Using question marks within a Microsoft Access querycreates a parameterized query, where the question mark is a place ho lder fo r a value that must be suppliedbefore the query can be executed. When constructing a query with parameter placeho lders, the syntax for theplaceho lder may differ depending on which type o f database you're using. While Microsoft Access uses aquestion mark, Microsoft SQL Server uses the @ symbol fo llowed by a variable name. We'll use this newquery to determine whether a category is a credit o r a debit.

Modify MainFo rm.cs as shown:

Page 407: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void addButton_Click(object sender, EventArgs e){ // Get category int categoryID = Convert.ToInt32(categoryComboBox.SelectedValue); // Get amount decimal amount = 0.0m; decimal.TryParse(amountTextBox.Text, out amount); // Determine if category is a debit, and adjust amount sign accordingly bool? isDebit = categoriesTableAdapter.IsCategoryDebit(categoryID); if (!isDebit.HasValue) isDebit = false; amount = Math.Abs(amount); if (isDebit.Value == true) amount *= -1.0m; // Get date DateTime date; DateTime.TryParse(dateTimePicker.Value.ToShortDateString(), out date); try { // Perform insert registerTableAdapter.Insert( categoryID, string.IsNullOrWhiteSpace(identifierTextBox.Text) ? null : identifierTextBox.Text, string.IsNullOrWhiteSpace(descriptionTextBox.Text) ? null : descriptionTextBox.Text, string.IsNullOrWhiteSpace(noteTextBox.Text) ? null : noteTextBox.Text, amount, date); // Refresh dataset from database registerCategoryTableAdapter.Fill(myMoneyDataSet.RegisterCategory); // Clear add controls identifierTextBox.Text = ""; descriptionTextBox.Text = ""; amountTextBox.Text = ""; noteTextBox.Text = ""; } catch (ArgumentNullException ex) { MessageBox.Show(ex.Message, "Null Value"); }}...

Click and to run the program.

So, what's the deal with that question mark fo llowing the boo l data type? This bo o l? is not related to theparameterized query for Microsoft Access databases that we were just discussing. Adding a question markafter a data type in C# makes that data type a nullable data type, which means that not only can the data typecontain a value, it may also be null. A nullable data type has a HasValue method we can use to determinewhether the variable has a value. In our code, we use the new IsCategoryDebit query to determine whether thecategory is a debit. We then use that boo lean result to determine whether the amount entered needs to be apositive value (a credit) o r a negative value (a debit). We use the Math.Abs abso lute value method to ensurethat regardless o f what the user enters, we get a positive value.

Page 408: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Tip Only non-reference data types may be defined as a nullable data type. Structs may be defined asnullable.

It's important to note that a nullable data type is not the same as the underlying data type. In o ther words, aboo l is not the same as a boo l?. If you need to return the value o f a nullable data type, and that result needs tobe be o f the underlying data type, you would use the Value property o f the nullable variable.

Next, add the GetBalance query to the Register Data Table.

Add a new query named GetBalance to the Register Data Table. Use the SQL below for the query:

CODE TO TYPE:

SELECT SUM(Register.registerAmount) FROM Register

Once you've created the GetBalance query, we can then use it in our code. However, we'll want to use thisquery in more than one location in our code, so create a method that can be called.

Create a method called updateBalance in MainForm that calls the GetBalance query and updates theToolStripStatusLabel with the register balance. The updateBalance method will need a try/catch block in caseof a database error. Also , you will need to use a decimal? (nullable decimal) data type as the result from theGetBalance method. The GetBalance method requires no parameters. Use the String.Format method toformat the Too lStripStatusLabel balance text, with {0:C} to fo rmat the balance as a currency string. Call theupdateBalance method from the Form Load event, and Click event in the addButton, to update the balance.

NoteYou may have noticed that we aren't showing you everything you need to do. This is by design.We want you to grapple with the new material, and work out as much o f the so lution for yourselfas you can.

Deleting a Register Entry

As part o f this tutorial, we want to demonstrate how to delete a row from the Register DataGridView. To deletea row, we'll need to enable the user to right-click on any cell o f any existing row in the Register DataGridView,causing that row to be highlighted, and enable a popup menu (also known as a context menu) to appear thato ffers an option to delete the selected row. Before handling the database delete, let's learn how to create anduse a ContextMenuStrip.

A ContextMenuStrip is a component on the Too lBox. We create a ContextMenuStrip just like we would aMenuStrip, except that we have to specify when and where it needs to appear. Let's add and set up ourContextMenuStrip fo r the deletion process.

Drag a ContextMenuStrip onto the MainForm Component Tray. Rename the contro l toregist erCo nt ext MenuSt rip. Add a Delet e menu option. Double-click the Delet e menu option togenerate the event handler. If necessary, click a different component in the Component Tray to exit editing the

ContextMenuStrip. Click .

We only want the context menu to appear when a valid cell in the DataGridView is clicked with the right mousebutton, so we need to handle the CellMouseDown event, checking for the right-click within the event.

Add an event handler fo r the registerDataGridView CellMo useDo wn event. Use the code below for thisevent. You will also need to change the registerDataGridView SelectionMode property to FullRo wSelect ,and MultiSelect property to False .

Page 409: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void registerDataGridView_CellMouseDown(object sender, DataGridViewCellMouseEventArgs e){ if (e.Button == MouseButtons.Right) { if (e.RowIndex >= 0) registerDataGridView.CurrentCell = registerDataGridView.Rows[e.RowIndex].Cells[1]; Point mousePoint = registerDataGridView.PointToClient(Cursor.Position); DataGridView.HitTestInfo hitTestInfo = registerDataGridView.HitTest(mousePoint.X, mousePoint.Y); if (hitTestInfo.Type == DataGridViewHitTestType.Cell) registerContextMenuStrip.Show(registerDataGridView, mousePoint); }}...

In this code, we ensure that the right mouse button was used, and that a valid row in the DataGridView isselected. Then we grab a cell from the selected row to set as the CurrentCell. Notice that we used Cells[1],which is the first visible co lumn (we hid Cells[0 ], the categoryID). By setting the CurrentCell, we force the rowthe user right-clicked to be highlighted and selected. Now we can highlight a row, but not have it selected. Theselected row is the row with the right arrow to its left.

To determine where to place the ContextMenuStrip, we convert the mouse Cursor Position to a Po int by usingthe Po intToClient method. We also extract information about what was clicked on using HitTestInfo so we canensure a Cell was clicked. Finally, we are able to Show the ContextMenuStrip using these new mousePointcoordinates.

Next, let's handle the delete request. We've already generated the event handler fo r the ContextMenuStripDelete menu item. In order to delete a register item, we'll see what Delete query is stored, and modify it to suitour purposes.

When we created a Data Table, we also created Select, Insert, Update, and Delete queries. To find thesequeries, go to the DataSet Designer, and select the Regist erT ableAdapt er text on the Register Data Table:

With the RegisterTableAdapter selected, in the Properties Window, expand the Delet eCo mmand property.Modify the CommandText property so it looks like the text here:

CODE TO TYPE:

DELETE FROM `Register` WHERE (`registerID` = ?)

Page 410: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Click . You probably noticed that the original CommandText was a lo t longer. For our current task, we justwant to provide the registerID value to delete a register row. Let's add that code next.

Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

.private void deleteToolStripMenuItem_Click(object sender, EventArgs e){ if (registerDataGridView.CurrentRow.Index >= 0) { string registerIDString = registerDataGridView.Rows[registerDataGridView.CurrentRow.Index].Cells[0].Value.ToString(); int registerID = -1; if (int.TryParse(registerIDString, out registerID)) { if (DialogResult.Yes == MessageBox.Show("Delete row " + registerDataGridView.CurrentRow.Index + " ?","Delete Entry", MessageBoxButtons.YesNo)) { registerTableAdapter.Delete(registerID); registerCategoryTableAdapter.Fill(myMoneyDataSet.RegisterCategory); updateBalance(); } } }}...

Click and to run the program.

In this code, we extract the registerID from the selected row, ensure the user wants to delete the row, then callthe Delete TableAdapter method, passing the registerID as our parameter. Then we refresh the DataSet andupdate our balance.

Update Categories

Next, we need to create the Form that will allow us to update the categories, and then call that Form.

Create a new Form named Cat ego riesFo rm . Add a horizontal SplitContainer to the Form and resize it. Dragthe Cat ego ries Data Table from the Data Sources window onto the top Panel o f the SplitContainer contro l.Dock the generated DataGridView in the Parent. Edit the co lumns in the DataGridView, hiding categoryID, andrename the o ther co lumns to Name , Descript io n, and Debit . Change the DataGridViewAutoSizeColumnsMode to Fill. Add a Button to the bottom DataGridView Panel, and rename itclo seBut t o n. Generate an event handler fo r this contro l, and add the code necessary to close the Form.

Click .

We need to add code to display the Form. Modify MainFo rm.cs as shown:

Page 411: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

. private void categoriesToolStripMenuItem_Click(object sender, EventArgs e){ using (CategoriesForm categoriesForm = new CategoriesForm()) { categoriesForm.ShowDialog(); } updateCategoryDropdown();}...

Click and to run the program.

With this code, we display the dialog box. We ensure that any possible changes to the categories are reflectedin the category ComboBox by calling the updateCategoryDropdown method. We use the using syntax toensure proper destruction o f our Form once we're done.

Although we've finished the categories Form, we're not quite done working. When a register entry is added,we select a category. What will happen if we delete a category that's in use? Any register items that referencedthat category would then have a categoryID value that doesn't exist in the Categories table. Depending onhow we have our Select query, those entries may not even display! So, we need to add code to preventdeletion o f any categories that are in use. We'll do that by adding another TableAdapter.

In the DataSet Designer, add a new TableAdapter named CategoriesHits. Use the SQL below for thisTableAdapter (you'll need to click the Advanced Options to make sure Insert statements are generated):

CODE TO TYPE:

SELECT C.categoryID, C.categoryName, C.categoryDescription, C.isDebit, IIF(ISNULL(rc.Hits), 0, rc.Hits) AS HitsFROM (Categories C LEFT OUTER JOIN (SELECT categoryID, COUNT(*) AS Hits FROM Register ri GROUP BY categoryID) rc ON C.categoryID = rc.categoryID)ORDER BY C.categoryName

NoteRemember that you can rename the DataTable by clicking on the titlebar in the DataSetDesigner. When you change the DataTable name, click on the TableAdapter caption and changethe TableAdapter name as well. The TableAdapter is located just above the list o fqueries/methods on the DataTable component in the DataSet Designer.

This SQL query will return all o f the Categories table co lumns and another co lumn named Hits that indicateshow many register entries are using that category. Then we can check the Hits co lumn before allowing acategory to be deleted. We could just try to change the current DataGridView Data Source with this newTableAdapter, but then the BindingNavigator would also need to be "rewired," so instead we're go ing toremove all o f the data-bound contro ls, the components on the Component Tray, the BindingNavigator Clickevent, and the code added to the Form Load event.

Highlight and delete the DataGridView and BindingNavigator on the CategoriesForm. Also, delete eachcomponent on the Component Tray for the CategoriesForm. Delete thecategoriesBindingNavigatorSaveItem_Click method, and remove all lines o f code from theCategoriesForm_Load method. Drag the CategoriesHits T ableAdapt er from the Data Sources window intothe top Panel o f the SplitContainer. Edit the co lumns to hide the cat ego ryID and Hit s co lumns, and edit thenames o f the remaining co lumns to be Name , Descript io n, and Debit . Dock the DataGridView in the Parentcontainer. Double-click the red X on the BindingNavigator (bindingNavigatorDeleteItem) to generate the eventhandler fo r the Click event. Modify Cat ego riesFo rm as shown:

Page 412: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

CODE TO TYPE:

.

.

.private void bindingNavigatorDeleteItem_Click(object sender, EventArgs e){ if (categoriesHitsDataGridView.CurrentRow.Index >= 0) { string categoryName = categoriesHitsDataGridView.CurrentRow.Cells[1].Value.ToString(); int hits = 0; int.TryParse(categoriesHitsDataGridView.CurrentRow.Cells[4].Value.ToString(), out hits); if (hits > 0) MessageBox.Show(categoryName + " category is in use and may not be deleted.", "Delete Category"); else { if (DialogResult.Yes == MessageBox.Show("Delete " + categoryName + "?", "Delete Category", MessageBoxButtons.YesNo)) { categoriesHitsBindingSource.RemoveCurrent(); categoriesHitsTableAdapter.Update(myMoneyDataSet.CategoriesHits); } } }}...

Click and to run the program.

When a user attempts to delete a row, we use the hidden co lumn Hits to determine whether the row may bedeleted. If no t, we display an appropriate message; o therwise, we call the RemoveCurrent method o f theBindingSource to mark the row as deleted, and call the Update method o f the TableAdapter to update thedatabase from the DataTable.

Category Breakdown Reports

The final component we'll add to the MyMoney application is the capability to produce the categorybreakdown report. Let's add the DataTable we'll need for the report.

In the DataSet Designer, add a new TableAdapter named Cat ego ryBreakdo wn. Rename the associatedTableAdapter to Cat ego ryBreakdo wnT ableAdapt er. Use the SQL below for this TableAdapter (we do notneed either checkbox in the Advanced Options checked):

CODE TO TYPE:

SELECT Categories.categoryName AS Category, SUM(Register.registerAmount) AS AmountFROM Categories INNER JOIN Register ON Categories.categoryID = Register.categoryIDWHERE Register.registerDateTime >= ? AND Register.registerDateTime <= ?GROUP BY Categories.categoryName, Register.registerAmountORDER BY Categories.categoryName

This SQL query has two parameter placeho lders for a beginning date and an ending date. Let's create theCategoryBreakdown Form and use this DataTable.

Create a new Form named Cat ego ryBreakdo wnFo rm . Add a horizontal SplitContainer to the Form, aDataGridView to the top Panel, and the o ther contro ls according to the earlier UI image to the bottom Panel.Add event handlers for both o f the DateTimePicker contro ls to handle the ValueChanged events. Handle the

Page 413: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Close Button. Add a Form Load event handler. Add a method named updat eRepo rt . Call the updateReportmethod from both ValueChanged event handlers, and from the Form Load event. Modify the Form Load eventas shown:

CODE TO TYPE:

.

.

. private void CategoryBreakdownForm_Load(object sender, EventArgs e){ // Set the DateTimePicker initial dates (1st and last day of the current month/year) DateTime firstDayOfMonth = new DateTime(DateTime.Now.Year, DateTime.Now.Month, 1); DateTime lastDayOfMonth = firstDayOfMonth.AddMonths(1).AddDays(-1); startDateTimePicker.Value = firstDayOfMonth; endDateTimePicker.Value = lastDayOfMonth; // Update the report updateReport();}

private void updateReport(){ categoryBreakdownTableAdapter.Fill(this.myMoneyDataSet.CategoryBreakdown, startDateTimePicker.Value, endDateTimePicker.Value);}...

Click .

This code sets the initial DateTimePicker values. With the o ther code changes, any time you change eitherDateTimePicker, the breakdown report is re-run.

Now we need to add code to display the Form. Modify MainFo rm.cs as shown:

CODE TO TYPE:

.

.

. private void categoriesToolStripMenuItem_Click(object sender, EventArgs e){ using (CategoryBreakdownForm categoryBreakdownForm = new CategoryBreakdownForm()) { categoryBreakdownForm.ShowDialog(); }}...

Click and to run the program.

Now you can produce a breakdown of the amounts for each category within a specific date range.

Tab Order

Before concluding this last lesson, we'll add one o ther item that you might find useful. When you run theapplication, you may use the Tab key to move between contro ls. The order in which you move is known asthe Tab Order, and may be changed. The initial tab order is set up in the order in which you add contro ls to the

Page 414: C#.NET 3: Advanced Algorithms and Database Accessarchive.oreilly.com/oreillyschool/courses/csharp3/C.NET 3 Advanced... · C#.NET 3: Advanced Algorithms and Database Access ... You

Form. If the Tab Order isn't correct, you can change it. Let's try that.

Select the View | T ab Order to display the tab order o f all o f the contro ls on the MainForm. Click on thecontro ls that typically would have focus when the application is running in the order you would want to have

the user move between them. Once you finish, press Esc to exit the Tab Order mode. Click .

Most contro ls have a TabStop property that contro ls whether the contro l would be included when tabbing.Contro ls such as Label contro ls do not have this property, as they do not support getting the Focus.

And there you have it. That concludes this lesson and this course! Great work. We've enjoyed having youalong to learn the material, and we hope you had some fun in the process!

Don't fo rget to do your homework—we're sure you know the routine by now!

Copyright © 1998-2014 O'Reilly Media, Inc.

This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 Unported License.See http://creativecommons.org/licenses/by-sa/3.0/legalcode for more information.