In our department's "Advanced Object Technology Using C#/.NET" course, a 300-level course, I spend some time discussing model-view design using delegate and event types. As is typical in such courses, time constraints as well as the student's backgrounds limit the size and scope of the examples that are presented in class and discussed.
To bring the concepts of model-view implementation to life, I have designed a major project that requires our students to delve much deeper into this design paradigm. A non-trivial grade book application that features multiple custom grids that may be linked provides a compelling opportunity for the students to exercise the model-view design principles studied in class.
In order to jump-start their work I provide the students with a stripped down simple one-grid implementation that establishes a simple framework of model-view design that they must significantly expand in order to meet the requirements of the application.
In this paper the requirements of this project are presented, a few highlights of the framework that is provided to the students are outlined and the challenges for future maintenance upgrades are described.
2 HIGH-LEVEL REQUIREMENTS
A grade book is to be implemented in C# using a model-view approach to design. Class GradeBookUI is used to implement the view (GUI). Class Gradebook is used to implement the model (stores grades and perform computations on these grades when needed by the view and later provides the basis for linking grade book grids).
A screen shot of the GUI with three students and three grades is given below.
The user can click any cell (other than ones colored gray) and then navigate around the grid using the up, down, left and right arrow keys. The Return key behaves like a right arrow key except if the "Navigate Vertically" box is checked. Then it acts like a down arrow key. It is illegal for any grayed cell to be selected or reached through navigation.
String data is entered by typing text into the cell selected by the mouse and either clicking another cell or moving away from the current cell with the Return key or one of the navigation keys.
In the left-most column (the column that holds names), any characters may be typed. In any of the columns that expect numerical input, the string is erased if it does not conform to the format of a numeric value.
An implementation of a stripped down and limited grade book with only one grid is provided containing classes GradeBookUI, Gradebook, and Global. Two additional classes complete this simple implementation ( GradeBookUI.Designer and Program ). Graphics (.gif) files are provided for the buttons that are used. Tooltips are implemented only for the buttons that are activated in this simple one-grid implementation.
The challenge in implementing the GUI is determining how to allow user input and navigation around the grid. One's first temptation might be to create a matrix of TextBox controls. This would allow user input from any cell in the grid. The problem with such an approach is its cost. Active controls such as a TextBox consume significant resources and perform inefficiently. In a large grid there could be hundreds of controls.
The approach used here is to have only one active TextBox control (field textbox in class GradeBookUI ). The rest of the grid is provided as panel graphics. This includes the horizontal and vertical lines, and all the String data that is displayed. These graphics are written to a Panel component with white background color. When the user clicks the mouse in a cell or navigates from one cell to another with arrow keys, the TextBox control is moved to the location in the panel that corresponds to the cell that has been selected. User input can be obtained through this roving TextBox control. When the input is completed by navigating to another adjoining cell or clicking the mouse in another cell location, the String data, if legal, is provided to the Gradebook object stored as a field of class GradeBookUI. Through this mechanism, the model is updated. An indexer with two parameters is provided in the Gradebook class to accomplish this.
A file Delegates.cs is defined that initially houses four delegate types. These provide the basis for the event types that provide the conduit of communication between each of the Gradebook models and their respective GradeBookUI views.
The students will need to provide additional delegate classes later as they complete this project.
To illustrate the use of this communication consider the implementation of the button handlers for adding a new student (i.e. a new row to the grid) and adding a new column to the grid in the implementation of the simple grid and model provided to the students.
The class Global contains a few public static objects that are accessible throughout the application. This includes NumberStudents (the same on every grid).
The command to FireDrawGrid, sent to the gradebook model, causes the drawGrid event to be activated in the gradebook object. This event is listened to in the GradeBookUI class in method DrawGrid.
A key event handler in GradeBookUI, the handler for mouse events, is given as:
If the right mouse button is clicked, the columns are sorted. If the name column (leftmost column) is right-mouse clicked, the grid is sorted based on alphabetizing the names. If any other column is right-mouse clicked the grid is sorted from largest to smallest (descending order) according to the selected column. The implementation of the Sort method in class Gradebook is given below. It uses LINQ and is quite elegant (C# 3.0 under Visual Studio 2008 is required for LINQ to be supported). The challenge is that the model contains a two-dimensional array of base-type String with column zero containing names and all other columns containing string representations of numeric values. An additional complication is that some cells in this two-dimensional array are blank.
The static query, Convert.ToDouble(value1[sortOnCol), is used in the LINQ script for descending in order to ensure that values are compared by numeric value rather than string value (e.g. the String "4" is larger than the String "20"). In order to ensure that the value is in the correct format (of a "double" string), the string "-1000.0" is inserted temporarily in the rollbook matrix wherever a null or blank cell is found. Then these values are filtered out when the results are copied back to the data matrix.
There are four event fields defined in the Gradebook class. Each of these fields has a "Register" method that allows a listener in the GradeBookUI class to connect itself to the appropriate event.
4 MORE COMPLETE GRADE BOOK SPECIFICATIONS
The specifications for the more complete grade book that the students are to implement are given in this section.
The functionality that must be implemented is triggered by the following buttons (each with a live tool tip):
Open file …
When the "Open file …" button is clicked, a modal dialog appears as follows:
This allows navigation to any sub-directory. Only files with the suffix, "gradebook" (e.g. Test2.gradebook) are displayed. A choice is made by double-clicking a file name.
One may create a new grade book by clicking anywhere in the gray zone outside of the white grid. Rows may be added using the button,
for adding a single row or the button
for adding multiple rows (number given the small text box below the button)
Rows may be later deleted using the button
for deleting a single row.
Columns may be added (one at a time) using the button
and later deleted using the button
A new worksheet, such as one designed to keep track of homework scores, may be created by first entering the worksheet name in the textbox provided. As an example,
Next the button
is clicked to create a new worksheet with the new grade sheet name ("Hmk" in the example shown). When a new grid is created a tab with its name appears at the bottom of the grid.
All of the names which are editable in the main worksheet are grayed out in the new worksheet. Changes to this list of names may be made only in the main worksheet. They are propagated to all supporting worksheets such as Hmk.
The "Average" column in the Hmk grid is linked to the fourth column in the Main grid. Any changes that might be made to the data in the Hmk grid will be immediately propagated to the fourth column in the Main grid. This linking must be achieved using the Gradebook models for the two grids (Hmk and Main ). The effect of linking is then visible in the two views (Main GradeBookUI and Hmk GradeBookUI ).
The process of linking is demonstrated by walking through an example.
A third grid, "Exams" is created. It looks as follows:
Exam 1 scores are entered by creating a fourth grid.
Suppose there were four problems on this exam with best 40, 30, 20, 10 weights (the best of the four scores is weighted 40 percent, the second best score is weighted 30 percent, the third best scores is weighted 20 percent and the lowest score is weighted 10 percent). This grid might look as follows after the grades have been entered:
The mean and median values for each problem and the weighted averages for each student are displayed. The only cells that can be edited have white background.
To link this Exam 1 grid to the third column in the Exams grid, select any cell in column 3 in the Exams grid. Then click the link button,
A dialog box that shows all the grids that one can link to will open. After selecting Exam 1, the Exams grid will become:
One could unlink this third column by selecting column 3 (now there is only one editable cell, colored yellow, that you may click to accomplish this) and then clicking the unlink button
After Exam 2 grades are obtained (at the end of the semester), another grid, Exam 2, is built. It is linked to column 4 in Exams. Now the Exams grid has two linked columns.
Best 65, 35 weights are applied to the Exams grid and the Exams grid is linked to column 3 of the Main grid to produce the final results:
All that remains is to obtain the final grades. This is accomplished using the Gradebook pull-down menu,
When "Display Grades" is clicked, the following dialog appears:
The thresholds may be changed (and they will be saved if the grade book is saved). If the "Rank Order" button is clicked, the redisplay of grades is as follows:
Whenever a grid is partially or completely obstructed by another window, the graphics must be restored using a Paint handler in the Panel containing the graphics.
If the "Show Notes" menu is clicked (Gradebook/Show Notes), a dialog is produced as in the following:
These notes are saved when the grade book is saved. The notes are associated with the.grade book file and not an individual grid.
5 DETAILED REQUIREMENTS
When the arrow keys are used, only white-background (editable) cells should be reachable. A non-editable cell (column 1, rows 1 and 2 and any linked column except for rows 0 and 3) should be skipped over.
For Equal Weights, whenever a numeric score is entered, the row average and mean and median values should be displayed. For Fixed, Best or Integer weights, only if all weights in row 3 (cells colored yellow) are specified should an average score be computed. The means and medians for each column will always be updated as new values are entered. A blank score will not affect the mean and median but will always be counted as a zero in computing the row average for a given student. If all scores are removed from a row, the average (in column 2) should disappear. If all scores are removed from a column, the mean and median should disappear. The actual computation of scores should be performed by the model class Gradebook. Using an appropriately defined event which is fired whenever statistics are computed, the view (class GradeBookUI ) must be notified.
This is one of the more challenging features of this project. When the average column of a source grid is linked to a column in a target grid, it is the models that are linked. When data in the source Gradebook is changed, the appropriate cell in the target Gradebook must be immediately changed. The result of this will not be seen until the user selects the target grid (by clicking the target tab).
The application through its Gradebook model classes must ensure that no circular links exist otherwise this would cause a stack overflow problem. For example, if grid 1 were linked to grid 2, then grid 2 should not be allowed to link to grid 1 otherwise an infinite sequence of updates would occur. Your program logic must therefore control the choices that are available for linking in the link dialog window that is displayed to the user so as to avoid a circular linking problem.
A grid is allowed to be linked only once (to some target grid). So when a grid has been linked, it must be removed from the list of choices for future linking. If a target has a column unlinked, then the original source grid may again be added to the list of possible future links.
All of the grids that have been created must be selectable by the tab control at the bottom of the GUI. When a tab is clicked the existing window must be removed and replaced by the grid that you have selected. For purposes of this project an entire grid form may be displayed. In future maintenance only the graphics portion of a grid form is to be displayed (to avoid flicker and jumping).
Adding rows and columns:
When a new row is added, the cells in a non-editable column must be grayed and not reachable through navigation or mouse clicks. When a column is added, rows 2 and 3 must be non-editable and row 4 colored yellow.
When any grid is resized, all other grids must be displayed with the new size. This value must be saved when the grid is saved. Next time the grids are loaded (through the Open button), their new size must be remembered and used. The tab control must remain properly positioned as the vertical size of a grid is changed.
When certain actions occur, the "Need To Save" label is displayed. This occurs when the data in a cell is changed or when the Notes are displayed or when the Final Grades dialog is displayed. Hitting "Ctrl-S" saves all data to the.gradebook file and removes this label from the GUI. When exiting the program, the user should be prompted to save only if the "Need To Save" label is visible.
Display final grades:
This specialized form is to be implemented. When the form is closed, all threshold values must be saved.
This specialized form is to be implemented. When the form is closed the content of the RichTextBox control embedded in the form must be saved.
Open and Save-As Dialogs:
The standard OpenFileDialog and SaveFileDialog controls are to be used in these dialogs. In the OpenDialog control only files ending with the suffix.gradebook are to be displayed. The application must remember the last sub-directory location in which a file was opened so that it will return to that same location in the future. This spares the user the need to navigate to the sub-directory containing the.gradebook files each time the Open option is selected.
Only the Gradebook models are to be saved using object serialization. When a.gradebook file is opened, the Gradebook models are recovered along with any other data that was saved using object deserialization. From these recovered Gradebook models, the tab control and all the grid views may be reconstructed. This is where a major payoff for the model-view design occurs.
The only written document that should accompany the project CD is a list of known bugs and omissions and a self-evaluation of the work.
It is expected that in a project of this complexity there will be bugs or omissions. It is better for the students to implement a sub-set of the requirements with quality than to implement all features with mediocre quality (many bugs). The linking features of this grade book application are very important and central. Omitting this feature will result in a greater loss of credit than omitting a more minor feature such as displaying notes or computing and displaying final grades. The highest priority features, in order of importance, are:
Items 1 through 4 on the above priority list should be considered "must-do" features of your project.
After completing your testing, a bugs and omissions document should be carefully prepared. It is much better for the student to identify and report a bug (or omission) than for it to be discovered when the project is evaluated.
7 FUTURE MAINTENANCE
Some important features that one might wish to add to this application to make it more useful in the future include:
About the author
Richard Wiener: "A Model-View Implementation of Linked Custom Grids in C#", vol. 8, no. 1, January-February 2009, pp 73-92 http://www.jot.fm/issues/issue_2009_01/column5/