Photo Editor
Photo Editor
C H A P T E R 5
This chapter discusses the third iteration of the project, which corresponds
to the elaboration phase of the Unified Process. In this phase the goals to
be achieved are to provide an architectural baseline and to formulate a
project agreement with the customer to further pursue the project.
The architectural baseline implements a working application with lim-
ited functionality. The implemented features in our example include ex-
ception handling, loading and saving images, and basic image operations.
The project agreement that is signed at the end of this phase should
include the time frame, equipment, staff, and cost. This leads to the goals
that are set for this iteration:
Most of the milestones are based on the work products that were
started in the inception phase. Based on those work products, the work
breakdown into the five core workflows is as follows:
■ Requirements: Refine the requirements and system scope.
■ Analysis: Analyze the requirements by describing what the system
does.
115
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 116
In the elaboration phase the main focus lies on the requirements and
analysis workflows. But as explained in Chapter 2, all five core workflows
are to be worked on in all phases. This means that in this iteration we spend
a considerable amount of time in the design, implementation, and test
workflows as well.
The refined vision is based on the analysis discussed in Chapter 4 and adds
new information.
The goal, or vision, for this project is to provide software components
to a printing and embossing business that allows online ordering of prints
and other items, such as cups, postcards, T-shirts, and mouse pads, that can
be customized with digital images.
To accomplish this task, the user must download a photo editor applica-
tion that is then installed on the customer’s computer. After installation the
user can alter images, adding graphics, text, and special effects. Users can
then save the altered images on their local computers and later upload the
images via the Online Photo Shop’s Web site to order prints or customized
products.
The new software will open the business to a new and larger customer
base, allowing for future growth.
Customize with
Picture
Add to Shopping
List
Order Products
Checkout
Collect Shipping
Information
Upload Pictures
Collect Payment
Select Information
Options
Print options
Summarize
Customer Order Printing
Business
Contrast
Adjustment
Apply Special
Effects
Color Adjustment
Figure 5.1 Refined Use Case Diagram for Online Photo Shop
The refined use case diagram clearly distinguishes the different parts of
Online Photo Shop, as discussed in Chapter 4. Online Photo Shop provides
a Web-based interface for ordering products, whereas the photo editor
application is used to edit pictures on the local computer. Because of that,
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 118
the photo editor and the Web order application are in different processes.
This also means that they are two independent applications and have no
anticipated dependencies. In addition, the new diagram shows additional
use cases for loading and saving pictures in the photo editor, as well as a use
case for uploading images to the Web application.
Table 5.1 shows the refined requirements for the current iteration.
For the remainder of the book, we show only the requirements that are
to be implemented in the described iteration. The alternative would be to
refine all the requirements in the first construction iteration, but we have
not chosen that approach for several reasons. First, it is easier to focus on
the functionality that needs to be implemented in the specified iteration.
Second, if requirements must be changed because of design or implemen-
tation issues, the changes usually will affect only the current iteration’s
requirements, and the requirements in later iterations can take all these
changes into account from the beginning. (If complete planning is done up
front, it is like the Waterfall model. Changes in later phases could trigger
many changes.) For reference, the complete list of refined requirements
can be found in the Requirement Specification XML file of each chapter
on the accompanying CD.
The refined requirements include more details on the actual use case
functionality and consider known constraints. In every iteration, we refine
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 119
Next, we analyze the refined requirements. The goal in the analysis work-
A flow is to describe what the system is supposed to do without defining how
it does it (the time to define how the system is expected to be implemented
is in the design workflow). For example, the photo_editor requirement
key defines what the graphical user interface should look like without
describing how it is implemented.
In this book, the analysis and design workflows are done just in time,
meaning they are done within the iteration that implements the correspond-
ing requirements. This approach is chosen because the team members are
highly experienced programmers in the domain and technology we are
using. Because of that, the effort estimates are based solely on the require-
ments and depend heavily on the experience of the team members. In
addition, this approach provides a clearer structure for this book in describ-
ing the analysis, design, and implementation of the features within the
chapter in which they are to be implemented. However, if a project has
less-experienced team members or an unknown domain, it might be neces-
sary to analyze all the requirements in more detail up front and to develop
the complete design in the elaboration phase in order to provide a reason-
able estimate. The estimates are the basis of the project agreement.
In the following subsections, we analyze, design, implement, and test
the requirement keys implemented in this iteration and the requirement
keys that define dependencies between the system’s modules.
Open, Save, Save As, and Exit. For the image-processing functionality, a
TabControl with buttons is provided. The idea is to group related function-
ality on one tab and to provide other tabs for other groups of functionality.
We have analyzed the requirements and have defined what the system is
D supposed to do. The next step is to decide how the system will be imple-
mented—in other words, to design the system.
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 123
A B C D
T
SampleClass SampleAbstractClass «interface»
InterfaceClass SampleParameterClass
-sampleAttribute -size : long = 0
#anotherAttribute +ISampleInterface() -example : T
+GetSize() : long
+sampleOperation() : uint +Operation() : short
E
SimpleClass
Prefix Visibility
- (hyphen) private: Can be seen only within the class that defines it
Class Relationships
Figure 5.4 shows the basic dependency principles among classes.
Figure 5.4A shows a dependency between Class1 and Class2. The
dependency indicates that changes to Class2 could trigger changes in
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 125
A Dependency
«call»
Class1 Class2
B Navigability
-role C5 -role C6
Class5 Class6
1 0..1
C Object Association
role C3role C4
Object1 : Class1 Object2 : Class2
D Multiplicities
Class7 Class8
1 *
Class9 Class10
0..1 1..*
Class11 Class12
0..* 2..9
1 Exactly one
* Many (zero or more)
0..1 Optional (zero or one)
1..* One or more
0..* Optional, one or many
2..9 Example of an m..n relationship; in this case, a two to nine
dependency
B Composition realization
C Generalization Class9
Class5 Interface
«implementation class»
AnotherImplementationClass
Class6
BoundElement
System.Windows.Forms.Form
+OnPaint() «interface»
IExceptionPublisher
+Publish()
PhotoEditorForm
-Picture : Picture -exception «implementation class»
-Exception : ExceptionPublisher 1 ExceptionPublisher
+OnPaint() : System.Windows.Forms.Form 1 1
+Publish()
-image Picture
-size
0..1
+FromFile() : Picture
+Dispose()
+RotateFlip()
+Save()
+SaveAdd()
+Size()
meetings (which last about 12 minutes) are held every other day for fre-
quent status updates (no discussions are allowed at these meetings). Stand-
up meetings often lead to the calling of a regular meeting at another time
so that team members can quickly discuss problems found.
From the Gantt chart we developed in Visio, we can export a variety of
diagrams, such as a timeline diagram. These exported diagrams are very
useful for reports to the customer or upper management (assuming that
the plan is kept up-to-date).
After all the ground work is set, the implementation can start. To begin the
I implementation, a project workspace must be set up and added to configu-
ration management. As mentioned in Chapter 4, the configuration man-
agement administrator is the person who should set up the directory
structure. Based on the directory structure (see Figure 4.3), a solution is
created that will contain all the subprojects of the software developed dur-
ing the course of this book.
To create an empty Visual Studio solution, open Visual Studio.NET
(see Figure 5.8). Then select Visual Studio Solutions and Blank Solution,
and name the solution Photo Editor. The solution should be crated in the
src directory, so browse to src in your project directory tree and click on
the OK button. The Visual Studio solution will be created.
Listing 5.1 shows the namespaces used by the code generated by Visual
Studio.NET. The System namespace contains the basic .NET Framework
types, classes, and second-level namespaces. In contrast, the second- and
third-level namespaces contain types, classes, and methods to support vari-
ous kinds of development, such as GUI, the runtime infrastructure, .NET
security, component model, and Web services development, to name only a
few categories.
In the photo editor application, you can see that several second-level
namespaces are automatically included. The System.Drawing namespace,
for example, provides rich two-dimensional graphics functionality and
access to Microsoft’s GDI+ functionalities. (GDI+ is explained in more
detail in Chapter 6.) For the remainder of this chapter, we use GDI+ to
provide a memory location where the image can be stored and then dis-
played; in addition, we use some GDI+ methods for image operations.
The System.Collections namespace holds collections of objects, such
as lists, queues, arrays, hash tables, and dictionaries. In addition, System.
ComponentModel implements components, including licensing and design-
time adaptations. For a rich set of Windows-based user interface features,
we also include the System.Windows.Forms namespace. Last but not least,
the System.Data namespace lets us access and manage data and data
sources. For more information on the namespaces provided by the .NET
Framework, please refer to the MSDN help.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
The next section in the source file defines the namespace for the appli-
cation, the basic application classes, and the methods. Listing 5.2 shows the
source code that is created.
namespace Photo_Editor_Application
{
/// <summary>
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 134
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after
// InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
{
this.components = new System.ComponentModel.Container();
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
}
}
Next comes a block specified with the #region and #endregion key-
words. This block allows the developer to write code that can be expanded
or collapsed within the Visual Studio.NET development environment. In
the example, the #region-#endregion block encloses the initializing
method of the Designer-generated form, as described earlier, and this code
should not be altered. You can collapse the code by pressing the “-” symbol,
or expand it by selecting the “+” symbol next to the keyword. Usually the
IDE provides the expand-collapse feature automatically for multiline com-
ments, class definitions, and method definitions, to name only a few. The
developer can define additional collapsible and expandable regions. The
region statements can be nested. In that case, the #endregion matches
the last defined #region statement that has not yet been matched.
The final part of the code defines the static main entry point for the
application. The application is defined to be running in a single-threaded
apartment (STA), and the main entry point then creates and runs an
instance of Form1.
The next step is to change the output directory of the compiler to the
bin and bind directories. As mentioned in Chapter 4, the bin directory
holds all assemblies necessary to run the photo editor application in re-
lease configuration, whereas the bind directory holds the same files but
compiled in debug configuration. To change the output directory, choose
the project in the Solution Explorer window and then choose Project |
Properties | Configuration Properties | Build | Output Path; change the
path to the bin directory (for release configuration) and bind (for debug
configuration).
Before you check the source files into the configuration management
system, you need to make some additional changes. For easier readability,
maintainability, and understanding, it is worthwhile to rename some of the
generated source files to more meaningful names. We do this before
checkin because renaming files already under configuration management is
not always easy.
Therefore, we change the name of the application source file from
Form1.cs to PhotoEditor.cs. We do this by right-clicking on the file name
Form1.cs in the Solution Explorer window and then going to the Proper-
ties window below Solution Explorer and changing the name in the File
Name field. After the name is changed, we adjust other properties of
PhotoEditor.cs[Design]. We select the corresponding tab and click on
the form. We change the Text field to Photo Editor Application, and
change the (Name) field to PhotoEditorForm.
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 137
To finish the cosmetics, click on the photo editor form and choose View
Code (by right-clicking on the form and choosing the option). Change
Form1()to PhotoEditorForm(), as shown in Listing 5.3.
Before you put the project into the source control system, make sure
that it compiles. Go to the Build menu in Visual Studio, and choose Build
Project (or use the shortcut by pressing Ctrl+Shift+B). If there are any
errors during compilation, they will be shown in the output window below
the main window. Double-clicking on the error message will open the cor-
rect source file to the approximate position of the error.
we just click OK. The system asks whether it should create the new project,
and again we simply confirm by clicking OK. As you can see, all files in
Visual Studio Explorer now have a little lock symbol next to them; this
means that the files are read-only (checked in) under source control.
Before a developer can make any change to the files, he or she must
check out the files to get write permissions to them. Checkout means that
the developer will work on a private copy of the file. The developer works
on the private copy, keeping it checked out, until the change is (partially)
complete and compiled; then the file must be checked in. At checkin time
the private file is added to the version control as the newest version of the
file; the file becomes read-only again, and all developers on the project can
see the the new file. Changes can be made only by the developer who cur-
rently has the file checked out. It is also possible to undo a checkout,
thereby reverting to the original version of the file and discarding the
changes made. (Nobody will ever know about the changes. Usually it is a
good practice to save the changes under a different file name in a private
file before undoing a checkout and losing the changes.)
Visual Source Safe provides many other useful tools, such as forcing the
system to get the latest version of all files and comparing the changes in dif-
ferent file versions. Most of the functionality is self-explanatory. For more
detailed information, you can consult the Visual Source Safe help files.
ment to the history of the file before checkout. The rule for the photo edi-
tor application is to add checkin comments that explain the changes made.
In Solution Explorer, you can now see the newly added references in the
Reference section. In addition to the references, we need to add a using
statement to the photo editor application to indicate the use of the exter-
nally defined functionality. Again, the source control will ask whether the
PhotoEditor.cs file should be checked out; we acknowledge this by
choosing checkout. The added code can be seen in Listing 5.4.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.ApplicationBlocks.ExceptionManagement;
Now that the photo editor application is aware of the exception man-
agement class, the classes and methods provided by it can be used. The
provided default publisher logs the exceptions to the event log. This is not
the behavior we intend for the photo editor application, so we must create a
custom publisher. Before continuing, make sure that all changes are saved
and the project compiles.
The message box used in this example takes six parameters, which are
explained in Table 5.3. For other variants of the message box, please refer
to the MSDN help.
To use the customized exception publisher, the final step is to provide a
configuration file. The configuration file makes the exception application
block aware of the custom publisher that is to be used (this is similar to reg-
istration of the custom publisher). To add an application configuration file,
right-click on Photo Editor Application in Solution Explorer and choose
Add | New item. In the dialog window that opens, choose Application Con-
figuration File and press Open. A configuration file is added to the solution.
Change the configuration file to correspond with Listing 5.6.
<exceptionManagement mode="on">
<publisher assembly="Photo Editor
Application"type=
"Photo_Editor_Application.ExceptionPublisher"
fileName="c:\PhotoEditorExceptionLog.xml"/>
</exceptionManagement>
</configuration>
As you can see, the configuration file provides information regarding the
customized publisher. The configuration entry <publisher assembly=...
defines the name of the assembly in which the customized exception pub-
lisher is defined. The type="..." defines the namespace and the method
name for the publisher, whereas the file name specifies the log file name to
which exceptions are logged in case the defined publisher cannot be found.
If an exception occurs, the Exception Manager Application Block will now
know about the customized exception publisher and will call the specified
exception publisher.
Make sure that the project compiles, and check in all the changes by
choosing the Pending Checkins tab below the main window. When you
check in a file, usually it is good practice to provide a meaningful comment.
The comment for the checkin at this point might read, “Added custom
exception handling using the Microsoft Exception Manager Application
Block. Exceptions are published in a window on the screen.” After typing
the comment, choose Check In. A dialog opens if the files really should be
checked in. Click on OK, and all the changes are available in the repository,
visible to everybody on the team.
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 146
Now the exceptions can be used in the photo editor application. All
code that could possibly throw an exception should be put in a try-catch-
finally block:
try
{
// Some code here
*
*
*
//in case a problem is found, an exception can be thrown
throw(new Exception("Some information here"));
}
catch(Exception exception)
{
ExceptionManager.Publish(exception);
}
finally
{
// Code here will always be executed, whether
//there was an exception thrown or not.
}
Change the name in the property section of the submenu item to Open
File. To add functionality to the menu, double-click on the OpenFile
menu item. The PhotoEditor.cs source file will be opened, and the
Designer adds a stub implementation of the OpenFile_Click event han-
dler. The Designer also adds code to make the system aware of the function
that handles the event of the specified type. Listing 5.7 shows the gener-
ated code that initializes some properties of the OpenFile menu item and
registers the event handler method.
//
// OpenFile
//
this.OpenFile.Index = 0;
this.OpenFile.Text = "&Open File";
this.OpenFile.Click +=
new System.EventHandler(this.OpenFile_Click);
//
// TODO: Add any constructor code after InitializeComponent call
//
loadFileDialog = new OpenFileDialog();
First, you define a file filter whose task it is to show only files of certain
file types within the chosen directory. Files of other types (or file exten-
sions) than the ones specified are not shown in the dialog window. This fil-
ter operation is provided by the OpenFileDialog class, which defines a
Filter property that can be used to filter files by their types. In our
example, the files that are of interest are image files of various types. The
supported types are images with the extensions .jpg, .gif, and .bmp. In
addition, we want to show all files in a directory when the file name is spec-
ified as “*.*”.
After the file filter is defined, the standard Windows File Open dialog
box is shown by calling its method ShowDialog(). A file dialog window
appears that enables the user to browse directories and select a file. After
the user has selected a file and clicked the Open button, the selected file
name can be extracted from the file dialog object using the FileName()
method. The .NET Framework provides converters to load and save the
most commonly used image types. For all image types supported by the
photo editor application, converters are provided by the .NET Framework.
Thus, we need no customized functionality to work with the various image
types. For supported formats and available conversion types, refer to the
MSDN help pages.
To work with the loaded image, we must create a Bitmap object and
allocate memory for it. We do this by calling the Bitmap constructor with
the file name as a parameter and assigning the image to loadedImage. The
loaded image field is not yet defined. Therefore, we add the following line
to the PhotoEditorForm class:
private Bitmap loadedImage;
As you can see, the application uses a bitmap type image to work with
rather than using the type under which the image was actually stored. It is
at this point that Windows’ automatic image type conversion saves a lot of
work. To show the loaded image on the screen, we next force a refresh,
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 150
invalidating the current Windows form. Invalidating the window (or parts
of it) sends a paint message to the appropriate window: either the control
or the child window. As a result, the part of the screen that has been invali-
dated is redrawn using the Paint event handler method.
Before an image is loaded by the user, a default image will be shown.
The image provided is Hawaii.jpg and should be located in the bin and
bind directories if the debug version is run. To initialize the bitmap, simply
add a line to the PhotoEditorForm constructor:
The last step tells Windows that custom drawing is needed for this
form. To do that, you overridee the OnPaint method. To implement that
method, you can either add the event handler manually by typing or create
a stub implementation using Visual Studio.NET. To automatically create a
stub, click on PhotoEditorForm in the PhotoEditor.cs[Design] view, and
go to the properties section. If you press the yellow lightning symbol
underneath the Solution Explorer window, you will see a tab with all the
events listed. Double-click on the Paint event. This will create a stub
implementation. Then implement the code as shown in Listing 5.9. The
implementation reveals that the Graphics class is used to save the image in
memory, which is then displayed on the screen.
/// <summary>
/// Custom paint method.
/// </summary>
protected override void OnPaint(PaintEventArgs e)
{
try
{
Graphics deviceContext = e.Graphics;
deviceContext.DrawImage(loadedImage, 0,0);
}
catch(Exception exception)
{
ExceptionManager.Publish(exception);
}
}
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 151
Compile and run the project. The default image will be displayed as
the background image when the application is first started. To display
another image, go to File | Open File. The Open File dialog will be shown,
and you can browse the file system. Select an image, and it will be displayed
in the application window.
Scrolling
Technique Description When to Use
Using a scrollable These are controls directly or Use this scrolling technique
control indirectly derived from System. when there is no need to
Windows.Forms.Scrollable draw in the control with
Control. They support scrolling, GDI+, the control is com-
provided by the .NET Framework. posed of this custom con-
For example, TextBox, ListBox, and trol and other controls, and
the Form class itself support scrolling. the virtual space is limited.
Placing a non- An instance of the Picture control Use this technique if you
scrollable control in is created, and the Picture control want to be able to draw into
the Panel control is placed in a Panel control. You the image with GDI+, the
then create a new image in which custom control is not com-
you can draw (possibly using GDI+). posed of other custom con-
Then the background image of the trols, and the virtual space
custom control is set to the new is limited.
image (including the graphics you
were drawing). (continued)
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 152
Scrolling
Technique Description When to Use
Using the Derive the control from the Use this technique if you
UserControl class UserControl class, build an image, need to draw into the lim-
with child controls draw into the image, and set the ited virtual space (possibly
BackgroundImage property of this using GDI+) and you’re
control to the build image. using child controls (con-
stituent controls) of the
custom controls.
Smooth scrolling Create a custom control that derives This technique creates a
from the User control, add vertical polished and professional
and horizontal scrollbars as child appearance. The image
(constituent) controls, and write a scrolls smoothly to the
Paint event handler that draws the desired position rather
image incrementally according to than jumping to the new
the scrollbar’s position. In addition, position, as it does when
the background color of the part of you use the build in
the control that is not covered by the scrolling.
image can be drawn in a defined
background color.
tor Application, and select Add | Add Class. Type Picture for the name of
the class to be created, and press Open. After that, add a reference to the
System.Drawing namespace to the new file by adding the following state-
ment:
using System.Drawing;
The reference is needed to access the type Bitmap, which is used to store
the image.
To implement the functionality to load an image, to the Picture class
we add a public method named LoadImage with the return type void and
no parameters. Switch to the class view and right-click on the Picture class
and Add | Add Method. Or simply add the following line:
We also add a field for storing the image data. Add the following line to
the Picture class:
loadedImage = value;
The next step is to add to the PhotoEditorForm class a public field that
is of type Picture and is named PictureObject. This field will hold a refer-
ence to an instance of a Picture class object. The PictureObject then
needs to be initialized with an allocated Picture class instance. This is
done in the constructor of the PhotoEditorForm. Add the following line to
the PhotoEditorForm() constructor:
PictureObject.LoadImage();
From the same method, we move the code for opening the file dialog
window to the LoadImage method of the Picture class. Basically this is all
the code except the following line:
this.Invalidate();
using System.Windows.Forms;
string defaultImage =
PhotoEditorForm.GetApplicationDirectory + @"\Hawaii.jpg";
loadFileDialog = new OpenFileDialog();
You can see in this code that we have introduced an additional method
to the PhotoEditorForm with the name GetApplicationDirectory. When
you call this method, the path to the directory in which the photo editor
application was started is returned. This is necessary in case a user starts
the application from a different directory via the command line (in that
case, if we would search the current directory for the default image we
would search in the directory the command line shows and not the direc-
tory where the application was started). To make this work, add the follow-
ing lines to the PhotoEditorForm class:
/// <summary>
/// Accessor to the Application directory.
/// </summary>
public static string GetApplicationDirectory
{
get
{
return applicationDirectory;
}
}
to the window. If the image is not scrolled, the pixel at position (0/0) of the
picture is shown at position (0/0) of the control. If the image is scrolled by
100 pixels in y direction, the picture position (0/100) is shown at the (0/0)
position of the custom control.
Rectangle ScrollingImageArea is a convenience accessor that returns
a rectangle whose size is measured from the origin of the control to the
x-coordinate of the vertical scrollbar and the y-coordinate of the horizontal
scrollbar. This is equivalent to the area of the control that is available for
drawing the image and is defined as the client area minus the area that is
taken by the scrollbars.
The base functionality of this control is to scroll through an image
smoothly. Therefore, we add vertical and horizontal scrollbars to the Custom
ScrollableControl form. The scrollbars can be dragged from the toolbox
onto the form. The scrollbars are positioned and docked in the form to the
right and the bottom of the form, as in other Windows applications.
private void drawImage is a helper method that is used to calculate and
draw the correct portion of the bitmap in the custom control. This method is
called directly by the scrollbars whenever a change is detected. The method
clips the region of the image to the area of the image that is visible in the con-
trol and draws the image. To clip the region, we use a Rectangle method
that is defined in the Windows.System.Drawing namespace.
We customize the Paint event handler using private void Custom
ScrollableControl_Paint so that we can repaint parts or the entire image
(in case, for example, the image is restored after the control was minimized).
The GDI+ drawing surface, provided as a parameter to PaintEventArgs,
is stored in a local variable called graphics. Then a solidBrush is created
to fill the client area with a solid color. Next, we check whether the
scrollingImage exists. If it does not, then there is no image and the com-
plete client area is filled by the solid brush.
After that, a local variable of type Rectangle is created. The rectangle
to the right of the image and left of the scrollbar is calculated and stored in
the local variable rect. If the calculated rectangle is not empty, this area
will be filled with the solid brush. After that, we do the same thing for the
area below the image and above the horizontal scrollbar. Then the small rect-
angle in the lower-right corner is calculated and filled with the solid brush.
The private void adjustScrollBars method dimensions and sets
certain properties for the scrollbars. This method does not take any param-
eters. A constant field is defined that is used to calculate the number of
incremental steps for each scroll request. Then we check whether an image
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 159
exists. If it does, the minimum and maximum values of the scrollbars are set
to 0 and the width or height of the image. In addition, we define the behav-
ior of small and large changes to the scrollbars. The actual values of the
scrollbars are set to the corresponding value of the viewport (meaning the
coordinates of the upper-left corner in the actual image).
The private void scroll method is the heart of the custom scrolling
functionality. This is the code that actually does the smooth scrolling. This
method handles the scrolling from the given previous position to the cur-
rent position of the scrollbars. To achieve a smooth scrolling effect, the
viewport is incrementally changed until it is in the new end position. In
between the incremental steps, the method is sleeping for a short time to
simulate the effect of a sliding image. Therefore, two constants are defined.
The first constant is used for the time period during which the control
sleeps before displaying the next image position relative to the viewport,
and the second is a divisor for the difference calculation of the previous and
the current position of the scrollbar.
We also create a local variable that holds the drawing context; this vari-
able is checked to see whether the previous value of the scrollbar is the
same as the current value. If it is not, we must apply horizontal scrolling. A
Boolean local variable indicates that the direction the scrollbar was moved,
and the defined integer divides the absolute change that was made into the
smaller, incremental steps. The incremental steps are then checked to see
whether they are smaller than 1. If they are, then the value is set to 1 for
scrolling up, or to –1 for scrolling down. Following that, the loop in which
the incremental scrolling over the image is executed.
Then some checks are added to make sure that scrolling is stopped if
the image is shown according to the scrollbar position and that the stepping
did not go too far (if it did, the values are set to the final position). Before
the image is drawn at its new position (with respect to the control), the con-
trol sleeps for a specified amount of time. Then the image is drawn, and the
next image position is calculated and displayed. This continues until the
image is shown in its final position, in which case a break statement is exe-
cuted what makes the program jump out of the while loop.
private void hScrollBar_Scroll and private void vScrollBar_
Scroll are the event handlers for the scrollbars. The Scroll event is trig-
gered whenever the user clicks on the scrollbar and changes its position. The
parameters that are passed to the event handler methods are references to
the sender’s object and ScrollEventArgs. The ScrollEventArgs object
provides information on the scroll type. If the user clicks on the small
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 160
/// <summary>
/// Opens a file dialog window so the user can select an
/// image to be loaded
/// </summary>
/// <param name="sender">A reference to the object calling
/// this method</param>
/// <param name="e">The event arguments provided by the
/// event handler</param>
/// <requirements>F:editor_load_and_save</requirements>
private void OpenFile_Click(object sender,
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 162
System.EventArgs e)
{
try
{
PictureObject.LoadImage();
if(PictureObject.LoadedImage == null)
throw(new Exception("Error, image could not be
loaded"));
DisplayImage();
}
catch(Exception exception)
{
ExceptionManager.Publish(exception);
}
}
The application, in its first cut, shows the default image when first
loaded. To get the same result with the custom control, we call the newly im-
plemented DisplayImage() method from within the customScrollable
Control_Load event handler (which was generated by Visual Studio.NET
automatically by the double-click on customScrollableControl in the
PhotoEditor.cs[Design] tab).
This completes the implementation of smooth scrolling. It is now time
to test the implemented functionality. Running the application and loading
an image shows that the scrolling works smoothly, but the image flickers
when the scrollbar is moved. We can prevent this by setting a style property
in the CustomScrollableControl constructor. As a result of the change,
the background is not drawn in the background color before the image is
drawn. The following line is used in the constructor to accomplish this:
this.SetStyle (ControlStyles.Opaque, true);
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 163
After this change, the control works without any noticeable flicker. The
next step is to provide the tab control containing the buttons for the basic
image operations.
Properties of TabControl
(Name) tabControl
Dock Bottom
Properties of Tab
(Name) basicImageOperations
Text Basic Image Operations
When the photo editor application is now run, the screen layout should
correspond to the GUI shown in the photo_editor requirements, except
that the buttons are still missing. To add a button for the cropping function-
ality, drag a button to the tab. Change the displayed text on the button to
Crop Image, and change the name of it to cropImageButton. Double-click
on the button to add the event handler for the click event. We use a dialog
box to collect the size information for the crop rectangle. To let users open
a dialog box when the crop button is pressed, you must add a new form to
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 164
the photo editor application. You do this by right-clicking the Photo Editor
Application project in Solution Explorer and selecting Add | Add Windows
Form. Name the new form CropDialog. Then drag two text boxes onto the
form and change their properties as follows:
In addition, add two buttons; change their names to OKBtn and CancelBtn
(and change the text to be displayed on the button accordingly). Also add
two labels next to the text boxes that explain what the text box input is—for
example, “Enter new image height.” The Form should now look approxi-
mately like Figure 5.16.
After adding all the design-related properties, we add the event han-
dlers. A double-click on the image width text box will add an imageWidth_
TextChanged event handler. Before implementing the event handler, add
to the CropDialog class two private integer variables called tempWidth
and tempHeight for the width and height. The event handlers are then
implemented. To extract the new width from the text box entry, the image
Width object provides a property, Text, that represents the text in the text
box. This text is converted to an integer value and stored in the tempWidth
variable. The functionality is shown in Listing 5.11.
editorForm = (PhotoEditorForm)sender;
After that, we implement the OK button click event handler for the
Crop dialog box. When the OK button is pressed, the stored values for
width and height are sent to PictureObject.CropImage. The Picture
object is then responsible for cropping the loaded image to the specified
size. Therefore, we add the event handler by double-clicking on the OK
button and adding the following lines to the event handler:
editorForm.PictureObject.CropImage(tempWidth, tempHeight);
editorForm.DisplayImage();
this.Dispose();
This will crop the image, assuming that a CropImage method is pro-
vided by the Picture class. Therefore, we must add the CropImage method
to the Picture class in the next step.
Add a new public void method CropImage to the Picture class. This
method takes two integer variables (the width and the height). Now that we
have defined the signature, let’s take care of the implementation. The
CropImage method should check whether the provided parameters specify
a region within the loaded image and whether the parameters are actually
larger than zero. After that, the clip region needs to be calculated as a rect-
angle. We then clone the current image by applying the calculated crop-
ping rectangle, and we store a copy of the cropped image in a temporary
bitmap called croppedImage. The loadedImage is then set to the cropped
image and is displayed. The complete implementation of the CropImage
method is shown in Listing 5.13.
/// <summary>
/// Method called from CropDialog. The current
/// shown image is cropped to the size provided
/// in the parameters. The cropping is done
/// with a rectangle whose center is put on
/// the center of the image.
/// </summary>
/// <requirement>F:image_crop</requirement>
/// <param name="newHeight>height of the cropped
/// image</param>
/// <param name="newWidth">width of the cropped
/// image</param>
public void CropImage(int newWidth, int newHeight)
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 167
{
// Check that cropping region is
// actually within the image and that
// the values provided are positive
if((newWidth < loadedImage.Size.Width) &&
(newHeight < loadedImage.Size.Height) &&
(newHeight > 0) && (newWidth > 0))
{
int xmin = (loadedImage.Size.Width / 2) -
(newWidth / 2);
int xdim = newWidth;
int ymin = (loadedImage.Size.Height / 2) -
(newHeight / 2);
int ydim = newHeight;
Rectangle rectangle = new Rectangle(xmin, ymin,
xdim, ydim);
if(rectangle.IsEmpty)
{
throw(new Exception("Error, CropImage failed to
allocate clipping rectangle"));
}
Bitmap croppedImage = loadedImage.Clone(rectangle,
System.Drawing.Imaging.PixelFormat.DontCare);
Bitmap oldImage = loadedImage;
loadedImage = new Bitmap(croppedImage,
rectangle.Size);
if(loadedImage == null)
{
throw(new Exception("Error, Image memory allocation
failed"));
}
}
}
The last step is to implement the cropImage button click event handler.
First, create the event handler. Then in the event handler create a new
CropDialog object and show the dialog on the screen by adding the follow-
ing lines:
After this change has been made, compile and run the application to
see the result. The images can now be loaded, rotated, flipped, cropped,
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 170
and saved. This is all the functionality that is needed for the project in the
elaboration phase. The next task is to write unit tests to validate the imple-
mented functionality.
Do It Yourself The buttons provided are not very nice-looking. You can
make the program more appealing by adding bitmaps that show the user
what the result of a requested image transformation will be. (Customize
the rudimentary buttons provided, or develop custom bitmaps that can be
loaded onto the buttons.) Also, we recommend that you change and play
around with the implementation to see what improvements you can make.
Listing 5.16 The Overloaded Save and Load Methods of the Picture
class
/// <summary>
/// Opens the file with the provided name.
/// </summary>
/// <param name="fileName">name of the file to be opened</param>
/// <requirement>F:image_load_and_save</requirement>
public void LoadImage(string fileName)
{
Bitmap oldImage;
oldImage = loadedImage;
loadedImage = new Bitmap(fileName);
if(loadedImage == null)
{
throw(new Exception("Error, LoadImage with file name
failed"));
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 172
}
oldImage.Dispose();
}
/// <summary>
/// Saves the current image
/// with the provided fileName.
/// </summary>
/// <param name="fileName">Name under which the image is
saved</param>
public void SaveImage(string fileName)
{
loadedImage.Save(fileName);
}
These two hooks can now be used by the test program to actually load
and save an image without having to go through a file load and save dialog
box. Very often, such hooks are implemented for testing. This is another
reason to include the test team (if there is a separate test team available) in
the project planning from the beginning. In this way, hooks for testing can
be discussed in the planning phase of the project and implemented as fea-
tures during development. In the photo editor project, this not as impor-
tant because the development team also does the testing. Nevertheless,
testing is incorporated in the project planning from the beginning.
namespace UnitTest
{
[SetUp] public void Init()
{
Console.WriteLine("********* New Test-case: *******");
Console.WriteLine("photo_editor");
TestImage = new Photo_Editor_Application.Picture();
}
For the file load test, we load the default image and check whether the
loaded image has the correct dimensions. The save file test crops the image
and saves it under a new file name. Then the regular default image is
loaded again (to make sure the Picture object has been changed and has
the original dimensions again) before the saved image is loaded. The saved
image is checked to see whether its dimensions are the same as the crop-
ping image information that was provided before the image was saved. The
XML tag <requirement> </requirement> identifies which requirement
key is tested in this test. To indicate that a method is a test method, the
attribute [Test] is used before the method definition. The implementation
can be seen in Listing 5.18.
/// <summary>
/// Test for F:image_load_and_save.
/// Windows standard dialogs are used, so really
/// this tests only whether the default image loaded has the
/// dimensions
/// that are expected!
/// </summary>
/// <requirement>F:image_load_and_save</requirement>
[Test] public void LoadandSaveImageTest()
{
const string fileName = "Test.jpg";
Console.WriteLine("image_load_and_save");
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 175
TestImage.CropImage(200, 400);
TestImage.SaveImage(fileName);
TestImage.LoadImage("Hawaii.jpg");
TestImage.LoadImage(fileName);
Now that the test cases have been implemented it is time to run them
to see whether the developed program actually does what it is expected to
do. We can run the test either from the command line or via the NUnit
GUI. In this chapter only the GUI is shown.
To start the tests, go to the Start menu; then go to Programs |
NUnitV2.0 | NUnit-GUI. The NUnit GUI will open. Choose File | Open
and navigate to the directory where the unit tests were built. The test tree
will be shown, and the tests can be run by pressing the Run button. The
result will look like Figure 5.19.
29823 05 pp115-178 r4jm.ps 8/6/03 3:53 PM Page 176
The GUI gives a nice overview of the test result. If the progress bar is
green, it means that all test cases passed. Otherwise, the bar will be red.
The Standard Out tab shows the results that were logged during the
test run. If there are errors, information about the failed cases can be found
in the Errors and Failures tab.
5.8 Conclusion
Build Comment Web Pages. In the displayed message box, we specify the
destination where the files will be saved to (in our case, the documentation
is stored in the doc folder of the project). Sample comment Web pages can
be found in the doc folder in the sample solution. In addition, we produce
the XML documentation, which is used for tracking (because it shows the
requirement keys that have been implemented).
To produce the XML files, go to Solution Explorer, right-click on Photo
Editor Application, and choose Properties. In the Configuration Properties
dialog, specify the file name to be used as the XML documentation file.
The XML file will be generated with every build, and the file will be saved
in the build directory (bin or bind in the case of the photo editor project).
The compiler will now create warnings if public members of a class do not
specify an XML-style comment.
In addition to the documentation, we apply a label to the software and
the documents (at least it should be done for the manually generated docu-
ments such as the project plan, the requirements document, and so on). To
add a label in Visual Source Safe, open the Source Safe application and
select the project. In the File menu choose Label. A window opens where
you can specify the label. The label will be something like “Version 0.1.0.”
This complies with the Microsoft .NET standard, which uses three num-
bers to identify a specific version. Because the version produced is the
first intermediate result, it is labeled with 0 (zero) for the main release, 1
for the intermediate release, and 0 for the minor releases. In addition, the
AssemblyInfo.cs file of the Photo Editor Application should be adjusted
to correspond to the version in the label before checkin.
5.8.1 Review
We must review the status of the project to decide whether the project will
be continued. If the project is continued and an agreement with the cus-
tomer is signed, then we check whether the project is ready to proceed to
the construction phase. To decide whether the project is ready, we assess
whether we have met the goals for the five core workflows:
It can be seen that the project meets all goals that were set for this
phase and iteration. Therefore, the project is ready to move on to the next
phase.
To get customer feedback early on, we deliver the project to the cus-
tomer as intermediate V 0.1.0. It is crucial to deliver intermediate results
that are functional but not yet the final product. In this way, the customer
has a better understanding of the progress, and any changes that might be
requested by the customer can be discussed and implemented as early as
the intermediate project is delivered instead of at the end of the project.
Especially with GUI development, this is very important. Another advan-
tage of intermediate deliveries is that errors may be found by the customer
and communicated to the development team while the product is still in
development.
UML
Jim Arlow and Ila Neustadt, UML and the Unified Process (London,
England: Addison-Wesley, 2002)
Martin Fowler, UML Distilled (Reading, MA: Addison-Wesley, 1999)
.NET Programming
www.dotnetexperts.com/resources/
www.gotdotnet.com
www.msdn.microsoft.com