previous chapter, we looked at the fundamentals of writing managed code using the Managed Extensions for C++. Now we can do something practical with that information. The common language runtime provides many features and services. Two of the most prevalent features include Windows Forms for creating desktop applications and ASP.NET for writing Web applications. We'll start off with Windows Forms. Another feature is managed data access. We'll look at managed data access using ADO.NET in Chapter 35.
Windows Forms
Earlier sections of this book covered classic Microsoft Windows development. We looked at the Microsoft Foundation Classes (MFC) as the quickest way to write high-performance Windows-based applications. For years, the best way to get the highest-performing, most feature-filled desktop application was to use MFC. Microsoft .NET includes a desktop application development framework named Windows Forms.Although the .NET initiative emphasizes Internet-based development, normal client applications will always be popular. The Windows user interface has been around a long time, and the underpinnings are not likely to go away soon. Under the hood, Windows applications will probably remain the same for the foreseeable future. You'll probably always be able to write Windows-based applications using WndProc functions and Petzold-style coding or using MFC. Windows Forms provides the highest-level abstractions available for Windows developers. They take a forms-based approach to development, much like Microsoft Visual Basic. However, Windows Forms makes available to all developers (including those using managed C++) the user interface facilities that Visual Basic developers have enjoyed for years.
Beneath the Veneer
As an MFC programmer, you're used to a single class library that works only under C++. The .NET Windows Forms library is a bit different. The Windows Forms classes are built into the .NET common language runtime. Earlier we looked at how MFC is basically a thin layer above API-level programming. If you look through the MFC source code, you'll find a WinMain function and some message loops—the heart of any Windows-based program. In fact, under the hood all Windows-based applications essentially work the same way. Windows-based applications register window classes that tie a WndProc to a default window style. Windows-based applications use the window classes to create instances of Windows user interface elements. Windows has some basic window classes defined under the hood (such as the BUTTON and the COMBOBOX classes).In the earliest days of Windows programming, all applications were created from scratch, and a large part of the developer's time was spent getting the boilerplate code to work correctly. Once the boilerplate code worked, you could add event handlers gradually to develop an application by adding cases to a switch statement. MFC did away with requiring developers to carve out all their own WinMain and WndProc functions. Windows Forms continues the trend of eliminating programming details so you don't have to spend as much time writing grunge code.
The Windows Forms Structure
Windows Forms applications are structured much like Visual Basic applications, and Windows Forms development is similar to standard Visual Basic forms-based development. SDK-style applications interact directly with the Windows API. We saw earlier that MFC is only a very thin veneer between the API and the C++ source code. Windows Forms programming hides even more of the boilerplate details of Windows programming than MFC did. Windows Forms applications have all the same general features of normal Windows-based applications. They respond to the usual events, such as mouse movements and menu selections. Windows Forms can also render within the client area. However, the syntax for managing these features is more abstract than the syntax in a program you write with the SDK or even with MFC.
Windows Forms technology is useful for creating all the standard Windows applications we've seen so far: Single Document Interface (SDI) applications, Multiple Document Interface (MDI) applications, and dialog box applications. Much of Windows Forms development involves managing a form (or forms) and defining a user interface in terms of controls (combo boxes, labels, text boxes, and so forth). All these controls are found in the common language runtime. Windows Forms aren't limited to just form-based applications. Windows Forms include a canvas on which you can draw anything you want—just as you're able to do with the standard GDI device context.Windows Forms simplifies desktop user interface programming in many ways. For example, Windows Forms define their appearance through properties. To move a Windows Form on the screen programmatically, you set the Windows Form's Location property. Remember that, when programming in MFC, moving a window involved calling CWnd::MoveWindow. Windows Forms manage their behavior with methods, and they also respond to events to define their interaction with the user.The classes comprising Windows Forms applications are found in the common language runtime. The fundamental class behind a Windows Forms application is the System::Windows::Forms::Form class. Writing a Windows Forms application is a matter of tweaking its properties to get the windows to look the way you want them to look, and setting up event handlers for mouse movements, menus, and command. Because a Windows Form is a regular common language runtime–based class that fully supports inheritance, you can build hierarchies of Windows Forms–based classes in a standard, object-oriented way. Right now, the common language runtime contains only the most rudimentary classes for creating applications. However, third parties are rapidly building Windows Forms components and controls.
A Windows Forms Wizard
Microsoft Visual Studio .NET includes a wizard called the Managed C Windows Forms Wizard for generating a Windows Forms application. You can find the wizard by searching on "Custom Wizard Samples" in the Visual Studio online help. Click the ManagedCWinFormWiz link and follow the instructions for installing the wizard. We'll use the wizard to create a simple Window Forms application so we can examine how Window Forms work.
The Ex33a Example: A Basic Windows Forms Application with a Menu and a Status Bar
You can look through the copy of Ex33a that comes with the companion CD, or you can use the Managed C Windows Forms Wizard to generate the example. To use the wizard, be sure it's installed. (You can get information about installing the wizard when you download it.) Choose New, Project from the File menu and then select the Managed C++ Windows Forms project. Type Ex33a in the Name text box and click OK. Here's the code produced by the wizard:
Source.cpp
#using <mscorlib.dll>
using namespace System;
// required dlls for WinForms
#using "System.dll"
#using "System.Windows.Forms.dll"
#using "System.Drawing.dll"
// required namespaces for WinForms
using namespace System::ComponentModel;
using namespace System::Windows::Forms;
using namespace System::Drawing;
__gc class WinForm: public Form
{
private:
StatusBar *statusBar;
Button *closeButton;
MainMenu *mainMenu;
MenuItem *fileMenu;
Label *todoLabel;
String *caption; // Caption of the WinForm
int width; // width of the WinForm
int height; // height of the WinForm
public:
WinForm()
{
// Set caption and size of the WinForm
caption = "Default WinForm Example";
width = 400;
height = 500;
InitForm();
}
void Dispose(bool disposing)
{
// Form is being destroyed. Do any
// necessary clean-up here.
Form::Dispose(disposing);
}
void InitForm()
{
// Setup controls here
// Basic WinForm Settings
Text = caption;
Size = Drawing::Size(width, height);
// Setup Menu
mainMenu = new MainMenu();
fileMenu = new MenuItem("&File");
mainMenu->MenuItems->Add(fileMenu);
fileMenu->MenuItems->Add(new MenuItem("E&xit",
new EventHandler(this, &WinForm::OnFileExit)));
Menu = mainMenu;
// Label
todoLabel = new Label();
todoLabel->Text = "TODO: Place your controls here.";
todoLabel->Size = Drawing::Size(150, 100);
todoLabel->Location = Point (50, 50);
Controls->Add(todoLabel);
// Set status bar
statusBar = new StatusBar();
statusBar->Text = "Status Bar is Here";
Controls->Add(statusBar);
// Setup Close Button
closeButton = new Button();
closeButton->Text = "&Close";
closeButton->Size = Drawing::Size(75, 23);
closeButton->TabIndex = 0;
closeButton->Location =
Drawing::Point(width/2 - (75/2), height - 23 - 75);
closeButton->Click +=
(new EventHandler(this, &WinForm::OnCloseButtonClick));
Controls->Add(closeButton);
}
void OnCloseButtonClick(Object *sender, EventArgs *e)
{
Close();
}
void OnFileExit(Object *sender, EventArgs *e)
{
Close();
}
};
void main()
{
// ds
// This line creates an instance of WinForm, and
// uses it as the Main Window of the application.
Application::Run(new WinForm());
}
The code listed above has been changed slightly. By default, the "todo:" comments generated by the wizard cover up the Close button generated by the wizard. The code listed above draws these comments a bit smaller. We'll look at the Form class in detail in a moment.Figure 33-1 shows the Ex33a Windows Forms application in action.

Figure 33-1: The Ex33a sample in action.
The Form Class
Windows Forms applications are based upon a class derived from the common language runtime Form class. Just as MFC used a C++ class library to hide the details necessary to manage a Windows application, the common language runtime classes hide the same details. That means no more defining WndProc functions, registering window classes, and running message loops.
Note the #using directive at the top of the Ex33a

Handling Events
Windows is an event-driven operating system. Consequently, the main purpose of any Windows user interface program is to handle the various Windows events. We've been writing MFC code to handle all kinds of events, including mouse movement, mouse button presses, and key presses. Windows Forms handle most events by plugging in an event handler for each event that a program will handle. Notice how the earlier Ex33a

Drawing
Any Windows programming framework requires you to draw on the screen. The Form class defines an event named OnPaint that traps the WM_PAINT message. The Form class intercepts the Paint event, and you can add a handler to draw on the form. Drawing on a Windows Form is generally simpler than using the raw GDI. The drawing operations are encapsulated in the Graphics object passed in OnPaint's arguments.The Ex33a sample application listed earlier simply places a Label control and a Button control on the form. However, in the next example we'll see how the Windows Forms painting model supports many of the graphics primitives that Windows developers are used to.
The Ex33b Example: Handling the Paint Event
Ex33b illustrates handling the Paint event. The core code for this example was generated by the Managed C Windows Forms Wizard mentioned earlier. Here's the listing for Ex33b:
Source.cpp
#using <mscorlib.dll>
using namespace System;
// required dlls for WinForms
#using "System.dll"
#using "System.Windows.Forms.dll"
#using "System.Drawing.dll"
// required namespaces for WinForms
using namespace System::ComponentModel;
using namespace System::Windows::Forms;
using namespace System::Drawing;
__gc class Shape
{
public:
Rectangle m_rect;
Color m_PenColor;
Shape()
{
m_rect.set_X(0);
m_rect.set_Y(0);
m_rect.set_Height(0);
m_rect.set_Width(0);
m_PenColor = Color::Black;
}
Shape(Rectangle r)
{
m_rect=r;
m_PenColor = Color::Black;
}
virtual void Draw(System::Drawing::Graphics* g)
{
}
};
__gc class Line : public Shape
{
public:
Line(Rectangle r) :
Shape(r)
{
m_rect=r;
}
Line():
Shape()
{
}
void Draw(System::Drawing::Graphics* g)
{
g->DrawLine(new Pen(m_PenColor), m_rect.Left,
m_rect.Top, m_rect.Right, m_rect.Bottom);
}
};
__gc class Circle : public Shape
{
public:
Circle(Rectangle r) :
Shape(r)
{
m_rect=r;
}
Circle():
Shape()
{
}
void Draw(System::Drawing::Graphics* g)
{
g->DrawEllipse(new Pen(m_PenColor), m_rect.Left,
m_rect.Top, m_rect.Right, m_rect.Bottom);
}
};
__gc class Rect : public Shape
{
public:
Rect(Rectangle r) :
Shape(r)
{
m_rect=r;
}
Rect():
Shape()
{
}
void Draw(System::Drawing::Graphics* g)
{
g->DrawRectangle(new Pen(m_PenColor),
m_rect.Left, m_rect.Top,
m_rect.Right, m_rect.Bottom);
}
};
__gc class WinForm: public Form
{
private:
MainMenu *mainMenu;
MenuItem *fileMenu;
String *caption; // Caption of the WinForm
int width; // width of the WinForm
int height; // height of the WinForm
Shape* l; // line
Shape* c; // circle
Shape* r; // rectangle
Shape* l2; // line
Shape* c2; // circle
Shape* r2; // rectangle
public:
WinForm()
{
// Set caption and size of the WinForm
caption = "Default WinForm Example";
width = 400;
height = 500;
InitForm();
}
void Dispose(bool disposing)
{
// Form is being destroyed. Do any necessary clean-up here.
Form::Dispose(disposing);
}
void CreateShapes()
{
int x = 10;
int y = 30;
l = new Line(Rectangle(x, y, 30, 60));
x = x + 50;
c = new Circle(Rectangle(x, y, 30, 60));
x = x + 170;
r = new Rect(Rectangle(x, y, 60, 60));
y = 160;
x = 10;
l2 = new Line(Rectangle(x, y, 30, 60));
l2->m_PenColor = Color::Red;
x = x + 50;
c2 = new Circle(Rectangle(x, y, 30, 60));
c2->m_PenColor = Color::Blue;
x = x + 170;
r2 = new Rect(Rectangle(x, y, 60, 60));
r2->m_PenColor = Color::Green;
}
void DrawShapes(System::Drawing::Graphics* g)
{
l->Draw(g);
c->Draw(g);
r->Draw(g);
l2->Draw(g);
c2->Draw(g);
r2->Draw(g);
}
void InitForm()
{
CreateShapes();
// Setup controls here
// Basic WinForm Settings
Text = caption;
Size = Drawing::Size(width, height);
// Setup Menu
mainMenu = new MainMenu();
fileMenu = new MenuItem("&File");
mainMenu->MenuItems->Add(fileMenu);
fileMenu->MenuItems->Add(new MenuItem("E&xit",
new EventHandler(this, &WinForm::OnFileExit)));
Menu = mainMenu;
//Paint Handler
Paint += new PaintEventHandler(this, OnPaint);
}
void OnPaint(Object* sender, PaintEventArgs* e)
{
SolidBrush* b;
b = new SolidBrush(Color::Black);
e->Graphics->DrawString("Hello World",
this->Font, b, System::Drawing::PointF(10, 10));
DrawShapes(e->Graphics);
}
void OnFileExit(Object *sender, EventArgs *e)
{
Close();
}
};
void main()
{
// This line creates an instance of WinForm, and
// uses it as the Main Window of the application.
Application::Run(new WinForm());
}
Graphical Output The sample code produced by the wizard didn't do much in the way of handling graphics. Ex33b does include some graphics-rendering code. The rendering code in Ex33b uses GDI+, an enhancement of the normal GDI we've already seen while working with MFC. Notice near the top of

The Shape class and its descendents are all defined as __gc classes, so they live on the garbage-collected heap. The Draw method takes an argument of type System::Drawing::Graphics. This type wraps the GDI's device context handle and manages calls such as LineTo, Ellipse, and Rectangle.The Form class has an event named Paint to which you can attach a handler. The form attaches its Paint event handler in the InitForm method. Notice that InitForm creates several instances of the Shape-derived classes. When Windows rerenders the form, the Paint handler runs through the Shape objects and asks each one to render itself by calling the Draw method.The Draw method extracts the Graphics object from the painting arguments and then draws each shape appropriately using a GDI+ call on the Graphics object. The Line object uses Graphics::DrawLine, the rectangle uses Graphics::DrawRectangle, and the circle uses Graphics::DrawEllipse. It's generally simpler to use GDI+ to render an object than it is to use GDI to render an object.Figure 33-2 shows Ex33b in action.

Figure 33-2: The Ex33b sample in action.
The Ex33c Example: An Interactive Drawing Program
To fully illustrate how Windows Forms works, let's take a look at a drawing program that interactively draws the shape objects listed earlier—a line, a square, and a circle. Ex33c is a slight variant of Ex33b. However, Ex33c handles mouse movement events and performs some custom tweaking of device context within the Graphics object.
As with Ex33a and Ex33b, Ex33c was created using the Managed C Windows Forms Wizard. I removed the "todo:" label and the Close button. Otherwise, it's a stock Windows Forms application. Here's the listing for Ex33c:
Source.cpp
#include "stdafx.h"
#include "math.h"
#using <mscorlib.dll>
using namespace System;
// required dlls for WinForms
#using "System.dll"
#using "System.Windows.Forms.dll"
#using "System.Drawing.dll"
// required namespaces for WinForms
using namespace System::ComponentModel;
using namespace System::Collections;
using namespace System::Windows::Forms;
using namespace System::Drawing;
using namespace System::Drawing::Drawing2D;
using namespace System::Diagnostics;
__value enum DrawingTypes
{
None, Line, Circle, Rect
};
//
//
// shape hierarchy shown later…
//
//
__gc class WinForm: public Form
{
private:
StatusBar *statusBar;
MainMenu *mainMenu;
MenuItem *fileMenu;
MenuItem *drawingMenu;
MenuItem *circleMenu;
MenuItem *lineMenu;
MenuItem *rectMenu;
MenuItem *helpMenu;
DrawingTypes drawingtype;
ArrayList *shapes;
String *caption; // Caption of the WinForm
int width; // width of the WinForm
int height; // height of the WinForm
Shape *currentShape;
public:
WinForm()
{
// Set caption and size of the WinForm
caption = "Default WinForm Example";
width = 600;
height = 500;
InitForm();
}
void Dispose(bool disposing)
{
// Form is being destroyed. Do any
// necessary clean-up here.
Form::Dispose(disposing);
}
void InitForm()
{
// Setup controls here
// Basic WinForm Settings
this->set_BackColor(Color::White);
Text = caption;
Size = Drawing::Size(width, height);
drawingtype = DrawingTypes::Line;
// Setup Menu
mainMenu = new MainMenu();
fileMenu = new MenuItem("&File");
mainMenu->MenuItems->Add(fileMenu);
fileMenu->MenuItems->Add(
new MenuItem("E&xit",
new EventHandler(this, &WinForm::OnFileExit)));
Menu = mainMenu;
drawingMenu = new MenuItem("&Drawing");
circleMenu =
new MenuItem("&Circle",
new EventHandler(this, OnDrawCircle));
lineMenu = new MenuItem("&Line",
new EventHandler(this, OnDrawLine));
rectMenu =
new MenuItem("&Rectangle",
new EventHandler(this, OnDrawRect));
drawingMenu->MenuItems->Add(lineMenu);
drawingMenu->MenuItems->Add(circleMenu);
drawingMenu->MenuItems->Add(rectMenu);
mainMenu->MenuItems->Add(drawingMenu);
helpMenu = new MenuItem("&Help");
mainMenu->MenuItems->Add(helpMenu);
helpMenu->MenuItems->Add(
new MenuItem("&About",
new EventHandler(this, OnHelpAbout)));
// Set status bar
statusBar = new StatusBar();
statusBar->Text = "Status Bar is Here";
Controls->Add(statusBar);
MouseDown += new MouseEventHandler(this,
MouseDownHandler);
MouseMove += new MouseEventHandler(this,
MouseMoveHandler);
MouseUp += new MouseEventHandler(this,
MouseUpHandler);
Paint += new PaintEventHandler(this, OnPaint);
shapes = new ArrayList();
UIUpdate();
}
void UIUpdate()
{
// uncheck all items
lineMenu->Checked = false;
rectMenu->Checked = false;
circleMenu->Checked = false;
switch(drawingtype)
{
case DrawingTypes::Line:
lineMenu->Checked = true;
break;
case DrawingTypes::Rect:
rectMenu->Checked = true;
break;
case DrawingTypes::Circle:
circleMenu->Checked = true;
break;
}
}
void OnDrawLine(Object* sender, EventArgs* e)
{
drawingtype = DrawingTypes::Line;
UIUpdate();
}
void OnDrawCircle(Object* sender, EventArgs* e)
{
drawingtype = DrawingTypes::Circle;
UIUpdate();
}
void OnDrawRect(Object* sender, EventArgs* e)
{
drawingtype = DrawingTypes::Rect;
UIUpdate();
}
void OnFileExit(Object *sender, EventArgs *e)
{
Close();
}
void OnHelpAbout(Object* sender, EventArgs* e)
{
::MessageBox(NULL,
"WinForms Drawing Example",
"About WinForms Drawing Example", MB_OK);
}
void MouseDownHandler(Object* sender, MouseEventArgs* e)
{
if(!this->Capture)
return;
switch(drawingtype)
{
case DrawingTypes::Line :
currentShape = new Line();
break;
case DrawingTypes::Circle:
currentShape = new Circle();
break;
case DrawingTypes::Rect:
currentShape = new Rect();
break;
default:
return;
};
try{
currentShape->m_topLeft.X = e->X;
currentShape->m_topLeft.Y = e->Y;
currentShape->m_bottomRight.X = e->X;
currentShape->m_bottomRight.Y = e->Y;
this->Capture = true; // Capture the mouse
// until button up
}
catch(Exception* ex) {
Debug::WriteLine(ex->ToString());
}
}
void MouseMoveHandler(Object* sender, MouseEventArgs* e)
{
if(!this->Capture)
return;
try{
Graphics* g = CreateGraphics();
Pen *p = new Pen(this->BackColor);
currentShape->Erase(g);
currentShape->m_bottomRight.X = e->X;
currentShape->m_bottomRight.Y = e->Y;
currentShape->Draw(g);
}
catch (Exception* ex) {
Debug::WriteLine(ex->ToString());
}
}
void MouseUpHandler(Object* sender, MouseEventArgs* e)
{
if(!currentShape)
return;
try{
shapes->Add(currentShape);
currentShape = 0;
this->Invalidate();
Capture = false;
}
catch (Exception* ex) {
Debug::WriteLine(ex->ToString());
}
}
void DrawShapes(System::Drawing::Graphics* g)
{
for(int i = 0; i < shapes->Count; i++)
{
Shape* s = dynamic_cast<Shape*>(shapes->get_Item(i));
s->Draw(g);
}
}
void OnPaint(Object* sender, PaintEventArgs* e)
{
Graphics* g = e->Graphics;
DrawShapes(g);
}
};
void main()
{
TextWriterTraceListener * myWriter = new
TextWriterTraceListener(System::Console::Out);
Debug::Listeners->Add(myWriter);
// This line creates an instance of WinForm, and
// uses it as the main window of the application.
Application::Run(new WinForm());
}
To draw a shape, select a shape from the Drawing menu, click and hold the left mouse button inside the form's client area, and then drag the mouse to a new location and release the mouse button. The shape is continually redrawn smaller or larger as you drag the mouse.This application uses a variant of the shape hierarchy from Ex33b. The application manages a list of Shape objects in an ArrayList (which you'll notice declared within the Windows Form). There are also a number of MenuItem objects declared and used. Let's start by hooking up the menu commands.Intercepting Commands In MFC, window messages are mapped to handlers in C++ classes using a message map. The Windows Forms model uses delegates to expose events. The first kind of event we'll look at is a command event—one that comes from a push button or a menu command.This application builds the menu manually, adding each menu command separately. Unfortunately, the current version of Visual Studio .NET doesn't include the high level of wizard integration for Windows Forms and managed C++ that we're used to with MFC applications. Each main menu command (File, Draw, and Help) is added to the top-level menu structure, and then individual commands are added to the main menus. We need only supply the string that appears on the menu, as well as a reference to a method that handles the menu event.This application includes a File menu for exiting the application, a Drawing menu for selecting which shape to draw, and a Help menu. The Drawing menu sets an internal variable to indicate the current shape (the shape that will be drawn next). Notice that the drawing handlers also set the state of the menu commands with check marks to indicate which shape is about to be drawn (a task we accomplished using MFC's command architecture).Intercepting Move Messages In addition to intercepting command messages, Windows Forms applications usually intercept other messages such as mouse movement. The Form class exposes the typical mouse events, such as mouse down, mouse move, and mouse up.Ex33c handles the mouse down event by capturing the mouse and creating an instance of the current shape type. Once the mouse is captured by the application, all mouse messages are sent to the captured form. Ex33c's mouse move handler erases the current shape (more on that in the next section) and then resets the coordinates of the current shape using the screen coordinates passed to the handler as arguments. Finally, the mouse up handler completes the shape and adds the shape object to its internal list of objects.
Advanced Graphics Rendering If you look at the code for the shape hierarchy from Ex33c, you'll notice that it's a bit different from the shape hierarchy from Ex33b. The reason for this difference is that Ex33b's shapes don't continually redraw themselves as you drag the mouse. Ex33c handles the mouse movement by constantly erasing and redrawing the shape at its new coordinates—certainly a reasonable approach for a drawing program. When you release the mouse button, the residue lines have to be cleared up. Here's the shape hierarchy from Ex33c that accomplishes this cleaning:
__gc class Shape
{
public:
Point m_topLeft;
Point m_bottomRight;
Color m_PenColor;
Shape()
{
m_topLeft.X = 0;
m_topLeft.Y = 0;
m_bottomRight.X = 0;
m_bottomRight.Y = 0;
m_PenColor = Color::Black;
}
Shape(Point topLeft, Point bottomRight)
{
m_topLeft = topLeft;
m_bottomRight = bottomRight;
m_PenColor = Color::Black;
}
virtual void Draw(System::Drawing::Graphics* g)
{
}
virtual void Erase(System::Drawing::Graphics* g)
{
}
int SetROP(HDC hdc)
{
int nOldRop = ::SetROP2(hdc, R2_NOTXORPEN);
return nOldRop;
}
void ResetROP(HDC hdc, int nOldRop)
{
::SetROP2(hdc, nOldRop);
}
};
__gc class Line : public Shape
{
public:
Line(Point topLeft, Point bottomRight) :
Shape(topLeft, bottomRight)
{
}
Line():
Shape()
{
}
void Draw(System::Drawing::Graphics* g)
{
System:IntPtr hdc;
hdc = g->GetHdc();
::MoveToEx((HDC)hdc.ToInt32(), m_topLeft.X,
m_topLeft.Y, NULL);
LineTo((HDC)hdc.ToInt32(), m_bottomRight.X,
m_bottomRight.Y);
g->ReleaseHdc(hdc);
}
void Erase(System::Drawing::Graphics* g)
{
System:IntPtr hdc;
hdc = g->GetHdc();
int nOldROP = SetROP((HDC)hdc.ToInt32());
::MoveToEx((HDC)hdc.ToInt32(), m_topLeft.X,
m_topLeft.Y, NULL);
LineTo((HDC)hdc.ToInt32(), m_bottomRight.X,
m_bottomRight.Y);
ResetROP((HDC)hdc.ToInt32(), nOldROP);
g->ReleaseHdc(hdc);
}
};
__gc class Circle : public Shape
{
public:
Circle(Point topLeft, Point bottomRight) :
Shape(topLeft, bottomRight)
{
}
Circle():
Shape()
{
}
void Draw(System::Drawing::Graphics* g)
{
// These are absolute coordiantes, so fixup
System:IntPtr hdc;
hdc = g->GetHdc();
::Ellipse((HDC)hdc.ToInt32(),
m_topLeft.X,
m_topLeft.Y,
m_bottomRight.X,
m_bottomRight.Y);
g->ReleaseHdc(hdc);
}
void Erase(System::Drawing::Graphics* g)
{
System:IntPtr hdc;
hdc = g->GetHdc();
int nOldROP = SetROP((HDC)hdc.ToInt32());
::Ellipse((HDC)hdc.ToInt32(),
m_topLeft.X, m_topLeft.Y,
m_bottomRight.X,
m_bottomRight.Y);
ResetROP((HDC)hdc.ToInt32(), nOldROP);
g->ReleaseHdc(hdc);
}
};
__gc class Rect : public Shape
{
public:
Rect(Point topLeft, Point bottomRight) :
Shape(topLeft, bottomRight)
{
}
Rect():
Shape()
{
}
void Draw(System::Drawing::Graphics* g)
{
System:IntPtr hdc;
hdc = g->GetHdc();
::Rectangle((HDC)hdc.ToInt32(), m_topLeft.X,
m_topLeft.Y, m_bottomRight.X, m_bottomRight.Y);
g->ReleaseHdc(hdc);
}
void Erase(System::Drawing::Graphics* g)
{
System:IntPtr hdc;
hdc = g->GetHdc();
int nOldROP = SetROP((HDC)hdc.ToInt32());
::Rectangle((HDC)hdc.ToInt32(), m_topLeft.X, m_topLeft.Y,
m_bottomRight.X, m_bottomRight.Y);
ResetROP((HDC)hdc.ToInt32(), nOldROP);
g->ReleaseHdc(hdc);
}
};
To make the rubber-banding work within the application (rubber-banding is the effect of stretching the shape as you move the mouse), you must make some standard GDI calls that aren't available within GDI+. Specifically, you need to call SetROP2 to set the binary raster operations. When you drag one shape over another, by default Windows simply brute-forces the pen to draw. Using the raster operations, you can set up the device context so it doesn't erase the current contents of the screen (drawn by a previous pen) as you draw new shapes.Each Shape class (the line, the circle, and the rectangle) has an Erase method as well as a Draw method. The Erase method uses the device context buried within the System::Drawing::Graphics object to set the raster operations. Calling Graphics::GetHdc gives you the same raw device context you get by calling the Win32 API method GetDC. The result you get from Graphics::GetHdc is a managed system type (an Int32Ptr). To get the actual device context, you must get the integer value (by calling ToInt32). You can then pass the device context to any function that needs it (such as the SetROP2 method).
Finally, if you look at the Draw methods of each of the shapes, you'll notice that they call the standard Win32 API methods for drawing lines, ellipses, and rectangles. Mixing GDI+ with classic GDI sometimes results in unpredictable side effects. In the case of setting up the raster operations, the drawing code doesn't erase the old lines correctly.Figure 33-3 shows Ex33c in action.

Figure 33-3: The Ex33c sample in action.