Click here to Skip to main content
Licence 
First Posted 30 May 2001
Views 105,738
Bookmarked 22 times

GDI+ in managed C++ applications.

By | 17 Oct 2001 | Article
This Sample demonstrates basic drawing techniques using GDI+ in a Managed C++ application.

Introduction

This Sample demonstrates basic drawing techniques using GDI+ in a Managed C++ application. The application implements a control which acts as a ticker which scrolls text across a window at a constant speed. The Client can control the scroll speed (how often ticker moves), the scroll smoothness (how many pixels it moves in one step), and the text to display.

The control uses an "off screen" painting technique similar to drawing to memory device contexts.

Let's start from the Managed C++ Class Library.

The application wizard creates the TickerControl.h and TickerControl.cpp files for us, and created a managed class TickerControl. We do not want to write the control from scratch, so let's derive it from the Control class in the System::Windows:Forms namespace and inherit the functionality of Control.

In order to do that we should add code to access the .NET framework classes. Add following lines to the top of your Control header file. #using <System.dll> #using <System.Drawing.DLL> #using <System.Windows.Forms.dll> using namespace System; using namespace System::Drawing; using namespace System::Windows::Forms;

Class members that are used by the control are defined in the Control Members section of the control's header class.

In the constructor we create a timer and subscribe to a Tick event.

CTickerControl() : m_nScrollSmoothness(1),
                   m_strTickerText(S"Ticker"),
                   m_nOffset(0),
                   m_nTickerWidth(0),
                   m_bmpTicker(NULL)
{
    // Create timer and set interval.
    m_tmTicker = new Timer();
    m_tmTicker->set_Interval(10);

    // Subscribe for timer events.
    m_tmTicker->add_Tick(new EventHandler(this, &CTickerControl::OnTimer));
}

In the destructor we unsubscribe from the timer event.

// Destructor.
~CTickerControl()
{
    // Unsubscribe from timer event.
    m_tmTicker->remove_Tick(new EventHandler(this, &CTickerControl::OnTimer));
}

Some of them we need to expose as control properties. The Control Properties section of the Control header class demonstrates how to do this. All properties have get_ and set_ functions, so they are accessible for reading and writing. You can omit one of the function and get read-only or write-only properties. The following code implements the TickerText property.

// Text to show in the ticker.
__property String* get_TickerText()
{
    return m_strTickerText;
}

    __property void set_TickerText(String *strTickerText)
{
    m_strTickerText = strTickerText;

    // Recreate face image bitmap for the new Ticker text.
    // But we shouldn't do it before the first OnPaint call,
    // otherwise both of the bitmap dimensions will be 0
    // and Bitmap constructor will throw the exception.
    if(m_bmpTicker != NULL)
        m_bmpTicker = CreateBitmap();
}

The StartTicker and StopTicker methods start and stop the text moving. Internally it just starts and stops timer.

void StartTicker()
{
    // Start timer.
    m_tmTicker->Start();
}

void StopTicker()
{
    // Stop timer.
    m_tmTicker->Stop();
}

The bitmap that we use for the control face is created by the Create bitmap function. At first it creates a graphics object that is used by the control window to paint.

graphMeasure = Graphics::FromHwnd(this->get_Handle());

We use

sizeString = graphMeasure->MeasureString(m_strTickerText, font);

to define how many pixels the Ticket Text string occupies. After that it creates a bitmap that has a width equal to the width of the control client rectangle plus the width of the Ticker text string. In order to draw on the bitmap we get a pointer to the graphics object associated with bitmap using

graphImage = Graphics::FromImage(bmpTicker);

The rest of the code just fills a bitmap background and draws the Ticker text string to the bitmap.

Bitmap* CreateBitmap()
{
    Bitmap* bmpTicker;           // Result bitmap.
    Graphics* graphImage;        // Graphics used to draw to bitmap.
    Graphics* graphMeasure;      // Graphics used to measure string.
    SizeF sizeString;            // Size of Ticker text string.
    System::Drawing::Font* font; // Font used to draw string.
    SolidBrush* brush;           // Brush to fill background.
    Rectangle rect;              // Control client rectangle.

    int nBmpWidth;               // Bitmap width.
    int nBmpHeight;              // Bitmap height.
    int nRepeat;                 // How many time we should draw ticker
                                 // text string in the image.

    // Get a graphic object used by control window.
    graphMeasure = Graphics::FromHwnd(this->get_Handle());

    // Create a font and brush to draw.
    font = new System::Drawing::Font("Courier", 10);
    brush = new SolidBrush(get_BackColor());

    // Get a size that  ticker text string occupies on the string. 
    sizeString = graphMeasure->MeasureString(m_strTickerText, font);

    m_nTickerWidth = (int)sizeString.get_Width();
    
    rect = this->get_ClientRectangle();

    // Bitmap width equals size of the control client rectangle
    // plus width of the Ticker text string.
    nBmpWidth = rect.get_Width() + m_nTickerWidth;

    // Define how many time we have to draw the string to bitmap.
    nRepeat = (int)(nBmpWidth/m_nTickerWidth + 1);

    nBmpHeight = rect.get_Height();

    // Create a bitmap to draw off screen.
    bmpTicker = new Bitmap(nBmpWidth, 
                           nBmpHeight, 
                           graphMeasure);

    // Get a graphic object to draw to bitmap.
    graphImage = Graphics::FromImage(bmpTicker);

    // Fill the background.
    graphImage->FillRectangle(brush, 0, 0, nBmpWidth, nBmpHeight);

    // Draw string, if it is short, draw it several times.
    for(int nCounter = 0; nCounter < nRepeat; nCounter++)
        graphImage->DrawString(m_strTickerText, font, Brushes::Black, nCounter * m_nTickerWidth, 0);

    return bmpTicker;
}

OnPaint is a function that do actual painting job. We overwrite this virtual function to do our painting. All we do here is just get a pointer to the graphics object to draw and draw our bitmap using DrawImageUnscaled function using current offset.

virtual void OnPaint(PaintEventArgs* pe)
{
    Graphics* graph;

    // Get a Graphics object that is used to draw.
    graph = pe->get_Graphics();

    // If face image bitmap is not exist, create it.
    if(m_bmpTicker == NULL)
        m_bmpTicker = CreateBitmap();

    // Draw the face image bitmap.
    graph->DrawImageUnscaled(m_bmpTicker, Point((m_nOffset - m_nTickerWidth), 0));
}

Drawing or bitmap we cover all control client area, so n order to avoid flickering we should overwrite OnPaintBackground virtual function with doing nothing inside.

virtual void OnPaintBackground(PaintEventArgs* pe)
{
}

The last thing we should do is to implement Timer event handler. Here we just adjust bitmap offset and redraw control.

void OnTimer( Object* myObject, EventArgs* myEventArgs )
{
    // Adjust bitmap offset.
    m_nOffset -= m_nScrollSmoothness;

    // If we finish to scroll bitmap, recreate it.
    if(m_nOffset < 0)
        m_nOffset = m_nTickerWidth - m_nScrollSmoothness;

    // Redraw control.
    Invalidate();
    Update();
}

To create a client we need to create Managed C++ Application and add a code that implements a windows form to the main source file, it is very similar what we did for the control. Following code illustrates it.

#include "stdafx.h"

// Add it to get an access to .NET Framework classes.
#using <mscorlib.dll>
#using <System.dll>
#using <System.Drawing.DLL>
#using <System.Windows.Forms.dll>

// Import Ticker control information.
#using "..\Bin\TickerControl.dll"

using namespace System;
using namespace System::ComponentModel;
using namespace System::Drawing;
using namespace System::Windows::Forms;

// Class that implements simple windows form.
__gc class CTickerClientForm : public Form
{
    // Constructor.
public:
    CTickerClientForm()
    {
        // Create ticker control.
        ctrlTicker = new CTickerControl();

        InitForm();
    }

    // Initializes a form.
protected:
    void InitForm()
    {
        // Set window title and size.
        this->set_Text(S"Ticker");
        this->set_Size(System::Drawing::Size(400, 300));

        // Set control location and size on the form.
        ctrlTicker->set_Location(System::Drawing::Point(30, 100));
        ctrlTicker->set_Size(System::Drawing::Size(300, 100));

        // Set ticker properties and start ticker to move.
        ctrlTicker->set_TimerInterval(10);
        ctrlTicker->set_TickerText(S"This is very very ... long string.");
        ctrlTicker->set_ScrollSmoothness(1);
        ctrlTicker->StartTicker();
    
        // Add ticker to form controls collection.
        this->get_Controls()->Add(this->ctrlTicker);
    }

    // Members.
private:
    CTickerControl* ctrlTicker;        // Ticker control.
};

int main(void)
{
    try
    {
        // Create Form.
        Application::Run(new CTickerClientForm());
    }
    catch(Exception* e)
    {
        Console::Write("Error occured: ");
        Console::WriteLine(e->get_Message());
    }

    return 0;
}

History

Oct 19 2001 - updated source files for beta 2

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Igor Chouvalov



United States United States

Member



Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
QuestionDraw Line based on User click Pinmemberhbjs16:40 11 Feb '06  
AnswerRe: Draw Line based on User click Pinmemberbob1697212:25 18 Feb '06  
GeneralRe: Draw Line based on User click Pinmemberhbjs17:51 18 Feb '06  
GeneralRe: Draw Line based on User click Pinmemberbob1697217:31 19 Feb '06  
Ivor Horton just came out with a new book for VC++ 2005 that has eveything from start to finish on a sketcher program. I have the VC++ 6.0 version of the book and I flipped through the 2005 version today at the store and it contains all the good stuff still. I like it because the first half of the book is on C++ and the second half is on Windows programming with C++ with a fair amount of GDI stuff.
 
If you don't rush out and buy it, at least take a look at the MFC class for CRectTracker which makes interactive geometric drawing childs play. I use it whenever I need to allow the user to create and interact with circles, rectangles, images, controls, lines, etc...
 
The Tracker has handles and edges that allow you to manipulate a rectangular area and you render your object accordingly as it is manipulated. Lines can be a bit tricky as you need to hide the unused handles and you need to draw the line in a rectangle bringing the concept of slope into your line data as a member.
 
I only rubberband lines since the Tracker renders itself for objects that fit in a rectangle. For lines you would hide the Tracker except the two handles at it's ends while you draw inverted lines as the user manipulates the Tracker.
 
To rubberband the line you basically set the ROP to R2_NOT and draw the line which will invert the pixels and draw it a second time in the same place with the ROP set to R2_NOT to invert them back. This technique has the added bonus that your rubberband will show even when it crosses other geometric objects of the same color since those pixels will be inverted and contrast the underlying graphic. When your done rubberbanding, you remove it and draw the final product and usually store the coordinates in the object and put the object in a collection so you can respond to paint messages and redraw the objects you created as necessary when the window gets invalidated.
 
Something like...
 
pDC->SelectStockObject(BLACK_PEN);
int oldROP=pDC->GetROP2();
pDC->SetROP2(R2_NOT); // Invert pixels
pDC->MoveTo(oldPointStart); // remove Old line
pDC->LineTo(oldPointEnd);
pDC->MoveTo(newPointStart); // New line
pDC->LineTo(newPointEnd);
pDC->SetROP2(oldROP); // Preserve original ROP
 
Ivor Horton really goes through most of this in both books so if your using VC++ 6.0 or VC++ 2005 (or anything in between) he will most certainly save you some time. MSDN is always a great resource but it can get very time consuming if your not being referred to something to start off with. There is also a book by Mahesh Chand for GDI+ which is written for C# but it's not too hard to adapt it to C++. Beware that GDI+ is written in C and provides some horribly thought out wrapper classes in C++ that are nowhere near as nice as the MFC objects (i.e CRect, CPoint vs. Rect, RectF, Point, PointF) and converting between the two will likely be necessary if your running GDI and GDI+ in an interoperable fashion.
 
Good luck!
GeneralRe: Draw Line based on User click Pinmemberhbjs14:17 20 Feb '06  
GeneralScaling Drawing PinmemberAnish George21:25 18 Apr '04  
QuestionWhat is the need for MC++ Pinmemberharanath6:10 11 Apr '03  
AnswerRe: What is the need for MC++ PinmemberDaniel Turini6:15 11 Apr '03  
QuestionWhy MC++ ? PinmemberNemanja Trifunovic7:15 19 Oct '01  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web02 | 2.5.120528.1 | Last Updated 18 Oct 2001
Article Copyright 2001 by Igor Chouvalov
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid