Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Gribble1 - CWnd goes full screen

0.00/5 (No votes)
16 Apr 2001 1  
This brief tutorial shows how to use a CWnd in 'full screen' mode, and provides a framework for investigating some simple Windows graphic calls.

 [Sample Image - 65K]

Introduction

gribble n. "Random binary data rendered as unreadable text. Noise characters in a data stream are displayed as gribble. Modems with mismatched bitrates usually generate gribble (more specifically, baud barf). Dumping a binary file to the screen is an excellent source of gribble, and (if the bell/speaker is active) headaches." The Jargon Lexicon

When I first started playing with 'pooters mumble years ago, one of the "fun" things to do was play with some simple graphics routines. Usually, there wasn't a lot you could do in terms of graphics, but that just made it all the more fun to try and get something going.

A little while after starting to play with Visual C and MFC, someone asked me to write a program that needed to display some graphics - and nothing else. It actually took me a while to figure out how to use a CWnd without all the attendant bells and whistles - frame, title bar, etc. But lo and behold, it's not very hard at all, and provides a neat canvas for playing with, well, whatever you want to play with.

I think the Gribble window I offer here is useful if you're thinking of writing a "kiosk" type program (as I was). I'm also hoping that the Gribble1 project can be a useful learning tool for those wishing to explore the world of Windows graphics, so I am writing this primarily as a beginners tutorial. That said, I don't go very deep into the subject of device contexts and GDI objects in this short article - the goal here is just to get the Gribble window up and running, and provide a simple example (the classic 'Lissajou' figure) to get things going.

The Gribble1 project

Gribble1 is a stock off-the-shelf VC6 AppWizard-generated MFC based EXE project, with no Doc/View support. With that in place, I just added a menu item called Gribble, with a Go option handled in CGribble1App.

Next, I made a CGribbleWnd class with the Class Wizard - selecting New Class then having my CGribbleWnd derive from a 'Generic CWnd'. (You have to scroll a bit to get to this one.) I then declared an instance of a CGribbleWnd in the App class, and called it m_wndGribble.

Now, a CWnd isn't a window until it gets created, and it's here where you get a chance to define some very basic properties. This is the only code we need outside of the CGribbleWnd itself, and you would use this if you were to incorporate a CGribbleWnd in your own project. In my case, the Gribble window gets created in CGribble1App::OnGribbleGo() with its own window class, and the very minimal property set of WS_VISIBLE|WS_POPUP. It's this minimal property set that gets rid of those nasty borders and stuff.

All we want to do here is register the window class and call m_wndGribble.CreateEx() - a built-in CWnd method:

void CGribble1App::OnGribbleGo() 
{
    // Let's create the Gribble window!

    if(!m_wndGribble.m_hWnd) {
        
        CString csWndClass = AfxRegisterWndClass(CS_OWNDC, 0, 0, 0);
        
        if(!(m_wndGribble.CreateEx(WS_EX_LEFT,
            (LPCTSTR)csWndClass,
            "Gribble Window",
            WS_VISIBLE|WS_POPUP,
            0,0,0,0, NULL, NULL ))) 
        {
            AfxMessageBox("Failed to Create Gribble Window)");
            return;
        }
    }
}

Our Gribble window should be able to take it from there, which it does in its OnCreate method:

int CGribbleWnd::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CWnd::OnCreate(lpCreateStruct) == -1)
        return -1;
 
    // load nifty cursor

    m_hCursor = AfxGetApp()->LoadCursor(IDC_CURSOR2);
 
    // we'll want to do this in OnActivate too

    SetCursor(m_hCursor);
    ShowCursor(true);
 
    // since this window has its own DC we can stash it away... 

    m_pDC = GetDC();
 
    // get some screen stats...

    m_pixelsX = m_pDC->GetDeviceCaps(HORZRES);
    m_pixelsY = m_pDC->GetDeviceCaps(VERTRES);
    m_bitsPixel = m_pDC->GetDeviceCaps(BITSPIXEL);
     
    TRACE("Screen is %d by %d\n", m_pixelsX, m_pixelsY);
    TRACE("Color depth is %d bits per pixel\n", m_bitsPixel);
 
    // full screen - in whatever color OnEraseBkgnd is using...

    MoveWindow(0,0,m_pixelsX, m_pixelsY);
 
    return 0;
}

Here, we do a bit of setup. First, we need the device context so we can find out what the size of the screen is. There's a lot more info we could ask for with GetDeviceCaps(), but right now all we're really concerned with is the maximum values for x and y. (The call to get the color depth is a holdover from the original class, which needed to check whether the settings were high enough for the bitmaps it was using.)

The next bit of housekeeping is to size the window to the screen size, with a call to MoveWindow(). (Note that when we called CreateEx() we specified 0s for top, left, width and height, so we need to do this here.)

The CWnd::MoveWindow() method has a fifth parameter bRepaint which defaults to TRUE and starts the ball rolling for us - telling the OS that we'd like to be in line for some painting messages. Which in turn will result is us getting some painting messages.

So far so good - but our generic CWnd derived class needs to handle these painting messages, especially WM_ERASEBKGND and WM_PAINT, because the base class (CWnd) doesn't do anything for us. Class Wizard to the rescue again - with CGribbleWnd selected, highlight the WM_ERASEBKGND entry and select Add Function, then Edit Code. My CGribbleWnd::OnEraseBkgnd() looks like this:

BOOL CGribbleWnd::OnEraseBkgnd(CDC* pDC) 
{
    // we'll configure this later...

    m_backColor = RGB(0,0,0);
 
    CBrush cb(m_backColor);
    HBRUSH hOldBrush = (HBRUSH)pDC->SelectObject(cb);

    RECT rect = {0,0,m_pixelsX,m_pixelsY};
    pDC->FillRect(&rect,&cb);

    pDC->SelectObject(hOldBrush);
    cb.DeleteObject();

    return TRUE;
}

Here we create a brush, a special type of GDI object, and use it to pain the screen black. This gets us a big black screen.

Note that there is a very important code sandwich here that you must adhere to if your program is to be a good Windows citizen, and that is that when a GDI object is selected into a device context the old one must be saved and restored. This allows the device context to properly return the resource when it is done, and saves you from the terrible sin of resource leaks. While we're on the subject, two more points - 1. Creating a window class as I have above that owns its own device context is also considered a bit of a no-no. I do it to (hopefully) save time, in the context of calling GetDC. 2. The code sandwich above can be replaced with calls to SaveDC() and RestoreDC(), which can be handy when a lot of objects have been selected into a device context.

Next, using the Class Wizard to create the OnPaint() method, we get a nice little place to practice our windows graphics. Mine looks like this:

void CGribbleWnd::OnPaint() 
{
    CPaintDC dc(this); // device context for painting

    
    /**
       Now do some drawing... how bout a Lissajou figure...
       {  Asin(wt + B) }
    **/
 
    // normally we'd do this kind of initialization in OnCreate or the constructor,

    // but I want to localize things here, so we can treat OnPaint as a mini program!

 
    // start with a classic seed for the Lissajou... one time init...

    static int s_nLisXCoef = 1;
    static int s_nLisYCoef = 3;
    static int s_nLisYOffset = 44;
 
    ++s_nLisXCoef;
    ++s_nLisYCoef;
    
    int x,y;
    for (float t = 0; t < 32767; t+=.2) 
    {
        x = sin(s_nLisXCoef*t) * m_pixelsX/2 + m_pixelsX/2;
        y = sin(s_nLisYCoef*t+s_nLisYOffset) * m_pixelsY/2 + m_pixelsY/2;
 
        dc.SetPixel(x, y, RGB(255,80,255));
    }

    // Do not call CWnd::OnPaint() for painting messages

} 

There is code in CGribbleWnd::OnLButtonDown() that calls Invalidate() to start the whole painting process again, resulting in a new figure being displayed in response to a left mouse click. The code makes 32767 calls to set the pixels involved, so it won't respond too quickly to your mouse clicks, but is not too bad.  As the figures get more complex, its kind of neat to watch 'em fill in.

Probably the only other thing to mention is the nifty cursor. There are two cursors defined in the resource editor, and OnCreate loads and uses the second one, kind of a StarCraft-like arrow. Maintaining this cursor requires handling one last message:

void CGribbleWnd::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized) 
{
    CWnd::OnActivate(nState, pWndOther, bMinimized);
    
    if(nState != WA_INACTIVE ) 
    {
        SetCursor(m_hCursor);
        ShowCursor(true);
    }
}

One last added handler not mentioned yet is the OnKeyDown, which is used to exit via DestroyWindow() when the escape key is pressed.

Summary

So there you have it - a standard CWnd with six message handlers added (some of these quite simple) and you have a nice full screen window to play graphics on. There are colors you can play with, and before leaving the Lissajou theme you might want to try different starting coefficients etc. The only graphic call here is SetPixel(), which doesn't require any nasty GDI objects or SelectObject() calls, so you can play safely, without fear of depleting your resources.

The Lissajou figures are a classic bit of "easy" graphing stuff. Maybe some of the more mathematically minded among us have some other cool formulae to share. That would be great - I've tried to make the OnPaint() more or less self-contained with the use of local static vars rather than class members, and if you stick to this convention you can pretty much submit fun new Gribble concepts just by pasting your OnPaint() code into a message and posting it. Perhaps someone will be able to discover exactly what gribble should look like.

Happy Gribbling

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