Building a Simple GDI Application

   

Building a Simple GDI+ Application

Any application that is going to do special painting with GDI+, as is the case with GDI, most likely does it whenever a window needs painting. The techniques using Visual C++ .NET with managed and unmanaged code are very similar, as this section shows. Both applications paint a rectangle with a circle in the middle that is sized based on the window client area.

Using the .NET Framework

As usual, writing a VC++ .NET Windows application starts with a Windows Form class that is used as the main form for the application. Because this application doesn't serve any real purpose other than to demonstrate the GDI+ capabilities, the form is defined with no controls, as shown in Listing 9.1.

Listing 9.1 GraphicsWindowsForm Windows Form Class Declaration
 1: #include "stdafx.h"  2: #include <tchar.h>  3: #using <mscorlib.dll>  4: #using <System.DLL>  5: #using <System.Drawing.DLL>  6: #using <System.Windows.Forms.DLL>  7:  8: using namespace System;  9: using namespace System::Drawing; 10: using namespace System::Windows::Forms; 11: 12: __gc class GraphicsWindowsForm : public Form 13: { 14: protected: 15:    System::ComponentModel::Container* m_pComponents; 16: 17: protected: 18:    void InitForm(); 19: 20: public: 21:    GraphicsWindowsForm(); 22:    virtual ~GraphicsWindowsForm() {} 23: }; 24: 25: GraphicsWindowsForm::GraphicsWindowsForm() 26: { 27:    // Initialize the Windows Form 28:    InitForm(); 29: } 30: 31: void GraphicsWindowsForm::InitForm() 32: { 33:    m_pComponents = new System::ComponentModel::Container(); 34:    // Add Initialization Code 35:    //........................ 36: 37:    // Initialize Form attributes 38:    Text = "Graphic Form"; 39: } 40: 41: int _tmain(void) 42: { 43:    // TODO: Please replace the sample code below with your own. 44:    Console::WriteLine(S"Hello World"); 45:    return 0; 46: } 

In order to perform the painting, the first step is to add an event handler for the Paint event in the GraphicsWindowsForm class and provide an implementation. Add the following event handler declaration to the GraphicsWindowsForm class:

 void PaintHandler( Object* sender, PaintEventArgs* e ); 

In order for your paint handler function to be called, you must wire it up to the main class by providing a function pointer, also known as a delegate, to the Paint event. In the InitForm function, add the following call to wire your event handler to the Paint event:

 add_Paint( new PaintEventHandler(this, PaintHandler )); 

Providing the implementation of the PaintHandler method is where GDI+ comes into play. For the purposes of this example, GDI+ is used to paint a rectangle with an ellipse in the middle. Listing 9.2 shows the definition of the OnPaint() method.

Listing 9.2 OnPaint() Implementation Within the GraphicsWindowsForm Class
 1: void GraphicsWindowsForm::PaintHandler(Object* sender, PaintEventArgs* e)  2: {  3:    System::Drawing::Rectangle rcRect = get_ClientRectangle();  4:  5:    // Resize the rectangle to be 10 pixels smaller all the way around  6:    rcRect.Inflate( -10, -10 );  7:  8:    // Fill the background with the appropriate color  9:    e->Graphics->FillRectangle( 10:       new SolidBrush (Color::FromKnownColor( KnownColor::Control)), 11:            get_ClientRectangle() ); 12: 13:    // Draw the rectangle and circle 14:    e->Graphics->DrawRectangle( new Pen(Color::Black, 5), rcRect ); 15:    e->Graphics->DrawEllipse( new Pen(Color::Red, 1), rcRect ); 16: } 

Compiling and running this example creates a Windows Form that looks like the one shown in Figure 9.3.

Figure 9.3. GDI+ drawing a rectangle and ellipse within a Windows Form using VC++ .NET.

graphics/09fig03.jpg

Try resizing the window of the application. You will see a real problem, as shown in Figure 9.4. The window is not painting correctly when it is resized. What could make this happen? The client area is not entirely invalid when it is resized, and only the invalid portion of the client is being painted. The rest of the client area is left untouched for performance reasons.

Figure 9.4. Resizing problem with painting the client area and invalid regions.

graphics/09fig04.jpg

For some drawing, this is fine. However, when a rectangle and ellipse are being resized as the window is sized, this problem is the result. In order to solve the problem, add a delegate to handler the Resize event and invalidate the entire client area with the Invalidate() method. Do this by adding the following protected declaration in the GraphicsWindowsForm class:

 void ResizeHandler( Object* sender, EventArgs* e ); 

The ResizeHandler method is then defined with the following implementation:

 void GraphicsWindowsForm::ResizeHandler( Object* sender, EventArgs* e ) {     Invalidate(); } 

The last step in creating the Resize event handler is to notify your Form class that you wish to receive Resize events. In the InitForm function, following your recent addition of the Paint handler, add the following line:

 add_Resize( new EventHandler( this, ResizeHandler )); 

Now, running the application produces a result that is what you would expect. As the window is resized, the rectangle and ellipse are resized and painted. The entire client area is invalid now, which causes the entire client area to be repainted as the window is sized.

Using GDI+ in MFC

In order to compare the same functionality in Visual C++ .NET using unmanaged code and MFC with the Graphics object, you first need to create a new single document MFC application named UnmanagedGraphics. You can turn off the toolbar, status bar, and other nice application features if you choose. They are not needed for this example.

Creating and Setting Up the Project

When using GDI+ in unmanaged code, you need to include the header file, gdiplus.h, and you need to initialize the GDI+ system with a call to GdiplusStartup(). When the application finishes, you need to call GdiplusShutdown() to shut down the GDI+ subsystem. The best place to make these calls is in the InitInstance() and ExitInstance() methods within the CUnmanagedGraphicApp class.

First open the stdafx.h file and add the following statement to include gdiplus.h.

 #include <gdiplus.h> 

Then you need to modify the CUnmanagedGraphicApp class to add an override for the ExitInstance() method by adding the following public method declaration:

 virtual int ExitInstance(); 

Also add the following protected member data item to store the GDI+ token value used to shut down GDI+:

 ULONG_PTR  m_gdiplusToken; 

Finally, you need to modify the InitInstance() method and define the ExitInstance() method, as shown in Listing 9.3.

Listing 9.3 InitInstance() and ExitInstance() Methods Modified to Work with GDI+
 1: BOOL CUnmanagedGraphicsApp::InitInstance()  2: {  3:    CWinApp::InitInstance();  4:  5:    // Initialize GDI+  6:    Gdiplus::GdiplusStartupInput gdiplusStartupInput;  7:    Gdiplus::GdiplusStartup( &m_gdiplusToken, &gdiplusStartupInput, NULL );  8:  9:    // Standard initialization 10: 11:    return TRUE; 12: } 13: 14: 15: int CUnmanagedGraphicsApp::ExitInstance() 16: { 17:    // Shutdown GDI+ 18:    Gdiplus::GdiplusShutdown( m_gdiplusToken ); 19: 20:    return( CWinApp::ExitInstance() ); 21: } 

The final step in setting up an MFC application to use GDI+ is to change the project's link option to include the gdiplus.lib file, as shown in the project settings dialog in Figure 9.5.

Figure 9.5. Adding the gdiplus.lib to the linker options of UnmanagedGraphics application.

graphics/09fig05.jpg

Painting the View with GDI+

With MFC's document/view architecture, the area to focus on in the application to paint the client area of a single document application is the CUnmanagedGraphicsView class. The application wizard already provides the OnDraw() method, so all that is left is to add the GDI+ code to draw the rectangle and ellipse. Before you begin, however, the Project Wizard has commented out the pDC parameter in the parameter list. Because you will be using that parameter, you will need to uncomment it. Listing 9.4 shows the CUnmanagedGraphicsView::OnDraw() method implementation.

Listing 9.4 OnDraw() Method Implementation with GDI+ in an MFC Application
 1: using namespace Gdiplus;  2:  3: void CUnmanagedGraphicsView::OnDraw(CDC* pDC)  4: {  5:    CUnmanagedGraphicsDoc* pDoc = GetDocument();  6:    ASSERT_VALID(pDoc);  7:  8:    // Get client rectangle size  9:    CRect rcClientRect; 10:    GetClientRect( &rcClientRect ); 11: 12:    // Create a GDI+ rectangle the same as rcClientRect 13:    Rect rcClient( rcClientRect.left, rcClientRect.top, 14               rcClientRect.Width(), rcClientRect.Height() ); 15:    Rect rcRect; 16: 17:    // Create a GDI+ rectangle 10 pixels smaller in all dimensions 18:    rcRect = rcClient; 19:    rcRect.Inflate( -10, -10 ); 20: 21:    // Initialize the GDI+ Graphics object from the device context handle 22:    Graphics* Graphics = Graphics::FromHDC( pDC->m_hDC ); 23: 24:    // Initialize the color object for the brush 25:    Color clrBrush; 26:    clrBrush.SetFromCOLORREF( ::GetSysColor( COLOR_WINDOW ) ); 27: 28:    // Declare the Brush, and two pens to draw with 29:    SolidBrush oBrush( clrBrush ); 30:    Pen        oPen1( Color::Black, 5 ); 31:    Pen        oPen2( Color::Red, 1 ); 32: 33:    // Draw the contents of the window 34:    Graphics->FillRectangle( &oBrush,  rcClient ); 35:    Graphics->DrawRectangle( &oPen1, rcRect ); 36:    Graphics->DrawEllipse( &oPen2, rcRect ); 37: 38:    // Release the device context handle from GDI+ 39:    Graphics->ReleaseHDC( pDC->m_hDC ); 39: } 

The last thing you need to do is add a using statement for the GDI+ namespace, GdiPlus. After the #include statements in the same file as your OnDraw function, add the following line:

 using namespace Gdiplus; 

Compiling and running the application shows the window in Figure 9.6. The application written with MFC doesn't have the same painting issue as the .NET application; therefore, you don't have to invalidate the window on a resize operation. MFC will automatically call the appropriate painting methods in response to events that may have an effect on the client portion of a window. In this case, a Resize event would make portions of the client area invalid, so MFC counteracts this by making the appropriate function call into your OnDraw event handler.

Figure 9.6. Unmanaged MFC application using GDI+.

graphics/09fig06.jpg

As you can see, the .NET managed version of this application was much easier to write, even without the wizard to create all the source code for you, as is the case with the MFC application.

Removing Drawing Flicker

Up to this point, all graphical drawing has been done directly to the device through GDI+. As you size the application window, you will notice there is a flicker in the window as the background is painted and then the image is painted. The image is even painted one element at a time first the rectangle, then the ellipse.

A more advanced way of performing the drawing is to use a technique that actually draws on a bitmap and then displays the bitmap with a single statement. This method has been used for years by GDI developers to eliminate flicker. How is it done with GDI+?

Using the managed application for this example, the first step to flicker-free painting is to turn off the Windows Form painting of the background. This is done by overriding the OnPaintBackground() method and implementing it to do nothing, as shown in the following code segment:

 void GraphicsWindowsForm::OnPaintBackground( PaintEventArgs* pPaintArgs ) {    // Do nothing } 

With the OnPaintBackground() method doing nothing, the background of the window is effectively left unpainted. The same is accomplished in normal window development by setting the window class's background brush to NULL when registering the window class.

The next step is to change the way the drawing occurs. Instead of drawing directly to the Graphics object contained in the PaintEventArgs object, you need to create a new Graphics object from a new Bitmap object. All the drawing occurs on the new Graphics object. The Graphics object is a generic object used for a variety of drawing operations. It contains all the device context information you need when drawing, which means you don't have to worry about it, as was the case when using regular GDI. When the drawing is complete, the Bitmap object is drawn to the real Graphics object that is contained in the PaintEventArgs object. Listing 9.5 shows the new implementation of the OnPaint() method.

Listing 9.5 Avoiding Flicker by Drawing to a Bitmap First
 1: void GraphicsWindowsForm::PaintHandler(Object* sender, PaintEventArgs* e )  2: {  3:    // Create a bitmap image and create a new Graphics object from it.  4:    System::Drawing::Bitmap* bmp =        new System::Drawing::Bitmap( ClientRectangle.get_Width(),  5:                                         ClientRectangle.get_Height(),  6:                                         pPaintArgs->get_Graphics() );  7:    Graphics* bmpGr = Graphics::FromImage( bmp );  8:  9:    System::Drawing::Rectangle rcRect = get_ClientRectangle(); 10: 11:    rcRect.Inflate( -10, -10 ); 12: 13:    // Draw on the bitmap image 14:    bmpGr->FillRectangle(        new SolidBrush( Color::FromKnownColor( KnownColor::Control)  ), 15:                            get_ClientRectangle() ); 16:    bmpGr->DrawRectangle( new Pen( Color::Black, 5 ), rcRect ); 17:    bmpGr->DrawEllipse( new Pen( Color::Red, 1 ), rcRect ); 18: 19:    // Draw the bitmap onto the real Graphics object 20:    pPaintArgs->Graphics->DrawImageUnscaled( bmp, 0, 0 ); 21: 22:    // Clean up 23:    bmpGr->Dispose(); 24:    bmp->Dispose(); 25: } 

When this version of the application is compiled and run, you will see that there is no flicker as the window is sized. This is because the entire window content, including the background, is constructed in memory. Rather than first drawing an object and then drawing another object, and so on, you are drawing one single object each time you repaint. The flicker you initially saw was the different objects being drawn at different times.

graphics/bulb.gif

This is a tip for the more advanced GDI developers who know how to deal with memory device contexts and compatible bitmaps.

It is possible using unmanaged code and GDI+ to create a memory device context and select a compatible bitmap into the device context as you would if you were going to draw on the bitmap with GDI. Instead of using GDI, pass the device context off to GDI+ and use the advanced drawing techniques to draw on the bitmap. Once you are finished with GDI+, release the device context and call the BitBlt() function to display the bitmap on the real device context.



   
Top


Sams Teach Yourself Visual C++. NET in 24 Hours
Sams Teach Yourself Visual C++.NET in 24 Hours
ISBN: 0672323230
EAN: 2147483647
Year: 2002
Pages: 237

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net