Introduction

Gribble1 outlined the basics of using a CWnd in full screen mode.
Gribble2 experiments with the BitBlt and
StretchBlt GDI functions, revisits WM_PAINT and
WM_ERASEBKGND, and discovers the WM_SYNCPAINT message along the way.

The Gribble2 project

There are a few changes in the mechanics of the Gribble2
CWnd, some of which arise from the desire to do some blitting
operations, some just for convenience. Gribble2, like Gribble1, is a
stock off the shelf VC6 App Wizard generated MFC based exe project, with
no Doc/View support. A Gribble menu item 'Go' is handled in CGribble2App
to create the gribble window:

As in Gribble1, Gribble2 registers its own window class to provide
the OS with information on how windows of this class should be
maintained. Again, style of CS_OWNDC is used, and the
CS_BYTEALIGNCLIENT style is added - this will help the
efficiency of the BitBlt calls later. (At least, thats what
the documentation says - in practice, I haven't noticed a difference
with or without this, or the CS_BYTEALIGNWINDOW style. It
may be that working with a full screen window makes this
superfluous.)

Gribble1 maintained its own cursor by loading the resource in its
OnCreate method and overriding CWnd::OnActivate() to
maintain it. The call to AfxRegisterWndClass here tells the
OS what cursor to associate with this class of window, though of course
the cursor will only be valid while the app is running. Doing this
provides much cleaner mouse activation.

Gribble1 passed a 0 for the background brush parameter and handled background
paints with its own member CBrush. Gribble2
assigns a system brush loaded with GetStockObject to the
window class itself. This now means that OnEraseBkgnd
doesn't need to explicitly paint the background. The default window
procedure will use this brush to fill the background if we don't do it.

Lastly, now an icon is supplied for the window. The gribble window
has no title bar, but this icon will display when you hit Alt+Tab to
switch between apps - which allows you to differentiate between it and
the Gribble2 application.

Setup and Cleanup

One of the quirks of coding windows wrapper classes is that you often
deal with two levels of creation and destruction. The class itself can
live through many create/destroy cycles of its underlying window, so the
initialization and cleanup one would normally associate with the
constructor and destructor of a C++ class often gets moved to the
OnCreate and OnDestroy message handlers.

Gribble2 follows this pattern:

int CGribbleWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CWnd::OnCreate(lpCreateStruct) == -1)
return -1;
try {
// since this window has its own DC
// we can stash it away...
// Don't just store a CDC pointer returned
// by CWnd::GetDC!
m_zeroTrap = m_ScreenDC.Attach( ::GetDC(m_hWnd));
Grb2Fn_InitGribble2Stuff();
// save the taskbar...
m_ShellTrayHwnd = ::FindWindow(_T("Shell_TrayWnd"),
NULL);
if(m_ShellTrayHwnd != NULL) {
// ... will we need to set topmost?
LONG tb_style = GetWindowLong(m_ShellTrayHwnd,
GWL_EXSTYLE);
if(!(tb_style & WS_EX_TOPMOST)) {
m_ShellTrayHwnd = NULL; // not to worry...
}
}
// Go full screen. SetWindowPos
// is more effective
// than MoveWindow at obscuring task bar -
// but suppress WM_ERASEBKGND
SetWindowPos(&wndTopMost, 0,0,m_pixelsX, m_pixelsY,
SWP_SHOWWINDOW | SWP_DEFERERASE );
/****************************************************//*WARNING DO _NOT_ STICK A BREAK POINT IN THIS AREA *//****************************************************/// this creates a memory device context compatible
// with our display
m_zeroTrap = m_QuadrantsDC.CreateCompatibleDC(&m_ScreenDC);
// this creates a bitmap compatible with our display
m_zeroTrap = m_QuadrantsBitmap.CreateCompatibleBitmap(
&m_ScreenDC, m_pixelsX, m_pixelsY);
// note that we don't bother saving the
// old bitmap - while their is
// a default bitmap associated with a newly created
// device context, it is not
// really an object that is currently selected
// into the DC, and there's
// no point in saving it for restoration.
(void)m_QuadrantsDC.SelectObject(m_QuadrantsBitmap);
// repeat the above for the Extra DC that will
// aid in reflecting image regions
m_zeroTrap = m_GribbleDC.CreateCompatibleDC(&m_ScreenDC);
m_zeroTrap = m_GribbleBitmap.CreateCompatibleBitmap(
&m_ScreenDC, m_pixelsX, m_pixelsY);
(void)m_GribbleDC.SelectObject(m_GribbleBitmap);
}
catch(DWORD) {
ValidateRect(&m_ScreenRect);
CString strMsg;
m_zeroTrap.GetMessage(strMsg);
// bad place for a messagebox
//MessageBox(strMsg, "Error");
TRACE(_T("Error in OnCreate: %s\n"), strMsg);
return FALSE;
}
// used as clip rgn in QuadrantsDC if desired
m_QuadCircleRgn.CreateEllipticRgn(m_Q1PointTopLeft.x,
m_Q1PointTopLeft.y,
m_Q1PointTopLeft.x+m_QuadSize*2,
m_Q1PointTopLeft.y+m_QuadSize*2);
// Ok - enough stuff available for OnEraseBkgnd to
// do its stuff, so send the WM_SYNCPAINT message
SendMessage(WM_SYNCPAINT, 0,0 );
// ok - we've forced our window topmost, without altering the
// state of the system tray/taskbar thingy -
// but if we leave our window topmost, other windows
// will be unshowable on this monitor - so lets
// get rid of the topmost-ness. Its work here is done...
SetWindowPos(&wndNoTopMost, 0,0, m_pixelsX,
m_pixelsY, /* SWP_SHOWWINDOW |*/ SWP_DEFERERASE );
/*****************************************************//******** END WARNING ( NO BREAK POINTS ) ************//*****************************************************/
Grb2Fn_DrawSomethingToGribbleDC();
return0;
}

First, OnCreate sets up a CDC member object with a handle to the
windows device context - this is unusual for a Windows program. Most
windows do not have their own device context. When they need to draw to
the screen, they call GetDC and are given a DC from the system cache.
They then should call ReleaseDC so that the DC goes back into the cache
for other programs to use. In our case, we registered the window class
with the CS_OWNDC style. This means that the device context
is not taken from the system cache, and is permanently available for our
use. Even if it wasn't stored it in our own CDC class, accessing the DC
through GetDC would be more efficient, as the system would
be able to supply the owned DC more quickly than if it had to go to the
cache, but attaching it to a member CDC object makes the setup of the
second and third device contexts simpler.

Next, Grb2Fn_InitGribble2Stuff is called to set up
some screen metrics and initialize the variables that control the
patterns that will be drawn.

You'll see a call to FindWindow that's used to get a handle to the
system tray window, aka Windows 95 task bar. This is the beginning of a
whole bunch of jumping through hoops to try to make sure that the
gribble window is truly full screen at least long enough for the initial screen
capture to the background DCs to be free of the taskbar. Gribble1 didn't go to such lengths,
and while it usually worked in clearing the whole screen, there were
times when it didn't - leaving the taskbar on screen while important
gribbling was being performed. Gribble2 needs to be more careful,
because the first time OnEraseBkgnd is called, whatever is on the
screen will be blitted to the background DCs.

Instead of a simple call to MoveWindow, Gribble2 uses
SetWindowPos. The call to SetWindowPos uses
the &wndTopMost value, and in doing so makes the window the very topmost
window in the system. The call to SetWindowPos can cause the WM_SYNCPAINT (and hence
WM_ERASEBKGND) messages to be sent. The SWP_DEFERERASE flag is used here so
that doesn't happen - the secondary device contexts and
bitmaps need to be set up before OnEraseBkgnd can operate
properly.

Armed with the screen metrics set in
Grb2Fn_InitGribble2Stuff, OnCreate then sets up the second and third
device contexts. These will be used to do the 'background' drawing. The
calls to CreateCompatibleDC set up device contexts of the
same type as our screens device context (now held in
m_ScreenDC). The new device contexts don't duplicate any GDI objects that may be
selected into the source DC, but do provide defaults. However, the
default bitmap is not what we want for the upcoming
BitBlt/StretchBlt exitement, so the code creates bitmaps
compatible with our screens device context then selects those bitmap into the newly created 'compatible'
DCs. As innocent as this looks, we've now
set things up so that images existing on one DC can be rapidly blitted
to the other, laying the groundwork for some very smooth graphic display
updates.

Next, one circular region is created. This will be selected as the
clipping region to the quadrants DC later if the user wants to view the output
that way.

Finally, the call to SendMessage(WM_SYNCPAINT,0,0) will trigger the first
WM_ERASEBKGND (after a WM_NCPAINT) and OnEraseBkgnd will do its stuff. This is
where the initial blits of the erased screen will take place. The windows
documentation is a tad vague on what exactly a WM_SYNCPAINT message is
for. I think of it as a 'hey Windows, do your thing like we just got
rolled over by some other window' - i.e. a convenient way to get a proper stream
of painting messages for a window, rather than trying to fake it by sending or
posting paint messages or calling handlers directly. The documentation states
that this message is for Win98 and above, but this OnCreate seems to work ok in
Win95 as well.

As a side note, its interesting to see what the OS sends to a window when its
invalidated by another window:

Windows 98 sends the following to the gribble window when another window
moves over it, or at least why my Win98 Spy utility shows as being sent:

(The lines with 'S' indicate sent messages - the 'R' lines show the
return, and the 'P' stands for a posted message - windows normally 'posts'
WM_PAINT messages, giving them a lower priority in the input queue.)

Note that on NT, the paint messages are nested in the WM_SYNCPAINT processing,
which would indicate that they come from the windows default procedure, not the OS. So the
WM_SYNCPAINT message, on NT at least, has the effect of reducing the inter-thread communication
involved with this painting message sequence.

Also, I had thought that WM_ERASEBKGND messages were only sent as a side
effect of processing WM_PAINT messages, but it looks as if this is not true -
more thoughts on this below.

Now, where was I. Oh yes - another call to SetWindowPos is made to remove the
topmost property - this is important! Without this, our window would
obscure all the other applications on this monitor. Also, you might want
to avoid setting breakpoints in this code between the two calls to
SetWindowPos. Especially if you are working with a single
monitor machine. Trust me on this one. Gets a bit annoying. If you do want to do
some spelunking here, at least make sure your Task Manager is set to be 'Always
on Top'. And if you do get stuck here, kill VC, not Gribble - trying to kill the
gribble window or the Gribble2.exe will result in a message box to the effect
that TM can't kill a process that is being debugged, and this message box won't
be visible. Like I say, gets a bit annoying.

This might seem like a lot of effort to go to just to keep the task
bar off the screen - and in fact, it doesn't work 100% of the time - if
you launch VC from a non-primary monitor and are using breakpoints in
OnCreate, you might end up with the taskbar showing. Which isn't so bad,
really. What I'm really trying to avoid is having a bitmap of the
taskbar on the screen (annoying).

Finally, I call a function to draw something to the secondary
background DC (the gribble DC), and we're done.

Cleanup goes thus wise:

void CGribbleWnd::OnDestroy()
{
CWnd::OnDestroy();
// Cleanup compatibles
m_QuadrantsDC.DeleteDC();
m_GribbleDC.DeleteDC();
m_QuadrantsBitmap.DeleteObject();
m_GribbleBitmap.DeleteObject();
m_QuadCircleRgn.DeleteObject();
// detatch the HDC we got from ::GetDC.
// Note that since we are using a private DC there is
// no need to Release the DC - it does not
// come from the DC cache, unless we ask for one
// from the cache by using GetDCEx with the
// DCX_CACHE flag set.
m_ScreenDC.Detach();
m_bErased = false;
// if necessary, restore the topmost property of
// the taskbar...
if(m_ShellTrayHwnd != NULL) {
TRACE(_T("Setting tray window to top\n"));
::SetWindowPos(m_ShellTrayHwnd, HWND_TOPMOST,
0,0,0,0, SWP_NOMOVE);
}
}

Notice that we don't need to select the bitmaps out of their
respective DCs - bitmaps are different from most 'selectable' objects in
this regard, so our cleanup becomes quite simple. Just delete the DCs,
bitmaps, and region we created in OnCreate.

We don't need to delete the main DC, as it is part of the window -
we'll just detach it so that the destructor of the CDC object doesn't
get confused. Also, and this is important to note if your new to
this device context stuff, we don't need to call
ReleaseDC.

You'll read a lot of texts that discuss device
contexts that will tell you that you handle WM_PAINT messages by
calling BeginPaint (which calls GetDC), doing your painting, and
calling EndPaint (which calls ReleaseDC). If you are doing your
rendering outside of the context of a WM_PAINT message, you can call
GetDC and ReleaseDC. The point made,
correctly, is that when GetDC is called in these cases you
receive a DC from the system cache, and it is a resource to be repected
and replaced when you are done. Not releasing a DC taken from the system
cache is a serious no-no, and can cause resource depletion system wide.
However, this gribble window has its own device context associated with
it. It doesn't come from the system cache. In fact, you should see very
little impact on the GDI resources bar in the Win98 Resource Meter
utility while running Gribble2.exe. But you don't want to add this
OWN_DC style to all your windows and controls. A device context is a
conglomerate of a whole pile of stuff, some of which (e.g. fonts) can
take up a lot of memory. The point I'm trying to make here is that the
gribble window is departing from convention, but that this is intended
to be a special full screen window, and I hope I'm describing the rules
well enough that you can see why I'm breaking them.

Finally, if the taskbar was 'always on top' when we created the
window, our first call to SetWindowPos would have robbed it
of its topmost status - so we'll be nice and restore that. If we don't,
after Gribble2.exe exits, the taskbar will appear on screen, but the
user will be able to obscure it with other windows, which may not be the
way the system was when we found it. If m_ShellTrayWnd is
NULL, that means the taskbar didn't have the topmost style bit set when
we checked in OnCreate, so no wurries.

OnEraseBkgnd

There seem to be differing philosophies about the proper use of the WM_ERASEBKGND
and WM_PAINT handlers in windows programming. The only
thread offered so far for the Gribble1 applies to this - why would we
have two different messages sent to our window for what is essentially
the same task? And why write code in both handlers when we could conceivably do all the work in one?

It happens in our gribble windows case that this setup is a very
convenient one. But lets take a quick look at what all this 'painting'
stuff is on about first in order to understand why.

The windows OS knows about all the windows that have been created. It
knows when they are sleeping, it knows when they're awake, it knows if
they've been bad or good, etc. More importantly, it will take action if
they become invalid. A window can become invalid (or, perhaps more to
the point, a region or rectangle of a window can become invalid) when we
explicitly make it so by calling InvalidateRect or InvalidateRgn, or
when another window invalidates all or part of the window by appearing
over it and moving or closing.

In the case where we invalidate the window explicitly we have some
degree of control over whether the WM_ERASEBKGND message will be sent
to our application, though a Boolean parameter in the
InvalidateRect and InvalidateRgn calls.
Actually, this parameter is more of a hint - if other regions are
slated for background erasure, the WM_ERASEBKGND message will be sent
when the BeginPaint message is called. We'll be able to examine the
Boolean fErase flag in the PAINTSTRUCT filled in by the call to
BeginPaint after it returns.

This is the normal procedure for a windows application processing the
WM_PAINT message, as noted above. The call to BeginPaint
also returns the device context handle, validates the update region, and
hides the caret (if necessary) while painting is being carried out. A
call to EndPaint restores the caret, if one was hidden by
BeginPaint, and releases the device context.

WM_ERASEBKGND will also be sent to our app by the system in among the message
sequence used to tell a window to paint itself when invalidated by another
window or through an explicit WM_SYNCPAINT message. To wit, WM_NCPAINT,
WM_ERASEBKGND, and WM_PAINT (posted).

As it turns out, this is a Good Thing, as shown in the code
below:

BOOL CGribbleWnd::OnEraseBkgnd(CDC* pDC)
{
// the first version of this window used its
// own brush to erase the background
// - this version passes a background
// brush to AfxRegisterWndClass in OnGribbleGo,
// so we can
// concentrate on other stuff.
//TRACE(_T("Inside OnEraseBkgnd...\n"));
// Lets assure ourselves that the HDC OnEraseBkgnd
// is handing us is the same as our private one -
// if so we can assume that
// the MFC is respecting our privacy in this regard.
VERIFY(pDC->m_hDC == m_ScreenDC.m_hDC);
if(!m_bErased) {
// here, we only actually erase once,
// at the start.
// Since the window does have its own brush,
// returning 0 would probably have
// the same effect, but we want to do some init.
int ret = CWnd::OnEraseBkgnd(&m_ScreenDC);
// blt to the compat... in effect,
// clear our working DC as well
try {
// wipe both working DCs - allows for testing
// of new tricks etc...
// Win9x users - comment out the call to
// m_GribbleDC for an interesting effect!
m_zeroTrap = m_QuadrantsDC.BitBlt(0, 0,
m_pixelsX,m_pixelsY, &m_ScreenDC, 0, 0,
SRCCOPY);
m_zeroTrap = m_GribbleDC.BitBlt(0, 0,
m_pixelsX,m_pixelsY, &m_ScreenDC, 0, 0,
SRCCOPY);
}
catch(DWORD) {
ValidateRect(&m_ScreenRect);
CString strMsg;
m_zeroTrap.GetMessage(strMsg);
MessageBox(strMsg, "Error");
return FALSE;
}
// select special clip region, if desired
if(m_bUseCircle) {
m_QuadrantsDC.SelectClipRgn(&m_QuadCircleRgn,
RGN_COPY);
}
m_bErased = true;
return ret;
}
else {
// only erase the background if another window is
// being dragged over us -
// better yet, just blit the screen bitmap -
// and our window just stays the way it is -
// very smooth!
if(GetForegroundWindow() != this) {
m_ScreenDC.BitBlt(0, 0, m_pixelsX,m_pixelsY,
&m_QuadrantsDC, 0, 0, SRCCOPY );
}
returntrue;
}
}

So, what gives here? Well, the first time we enter this function
(indirectly by way of the SendMessage(WM_SYNCPAINT,0,0)
call in OnCreate) we erase the background by calling
CWnd::OnEraseBknd. Actually, we could just return false and
gain the same result (the default window proc will use the class
background brush to erase the background), but we want to do one last
bit of setup here. After the return from CWnd::OnEraseBknd,
we want to copy the newly blotted out screen to our background device
contexts. Now we can work with a clean slate as it were.

Note that its also in this 'one time only' processing that the
clipping region of the primary background DC (what I call the
quadrants DC) is set to the circular region set up in OnCreate, if that
Boolean is set.

During the periods in which the gribble window has focus, painting will be
triggered by calls to InvalidateRect with a value of
FALSE for the bErase parameter, so
OnEraseBkgnd shouldn't fire if we call
BeginPaint. But, as noted above, the WM_SYNCPAINT type
message sequence that occurs when the gribble window is invalidated by
another can send us here as well, and I make a call to
GetForegroundWindow to determine (almost a given) if we are
indeed dealing with forces beyond our control. (Note that not all
windows can use such a simple test - it definitely helps to be a full
screen window!) All OnEraseBkgnd needs to do in this
situation is blit the primary background DC bitmap to the screen DC and
we're done! Our window's invalid region is updated with the absolute
minimum of flicker and other kafuffle of that nature. This is
beautifully smooth. Try it, you'll like it!

OnPaint

And now, ladies and gents, the lovely and talented OnPaint.

void CGribbleWnd::OnPaint()
{
//CPaintDC dc(this);
// thanks, we already got one...
// since we are using a private device context,
// and not calling BeginPaint
// (by way of CPaintDC), we are responsible
// for validating the affected
// area - if we don't do this, the system will
// continue to send us WM_PAINT
// messages. Also, this seems to work only
// if we validate the whole screen,
// even though we called InvalidateRect on a
// smaller portion.
ValidateRect(&m_ScreenRect);
// if we're not just painting because some nasty
// window is dancing the Macarena over us...
if(GetForegroundWindow() == this) {
// call whatever nifty paint function you got...
// the gribble DC is drawn to at start and on
// left click
Grb2Fn_BlitGribbleToQuadrantsDC();
}
// ...then transfer the artwork wholesale
// to our screens DC
try {
m_zeroTrap = m_ScreenDC.BitBlt(0, 0,
m_pixelsX,m_pixelsY, &m_QuadrantsDC,
0, 0, SRCCOPY );
}
catch(DWORD) {
ValidateRect(&m_ScreenRect);
CString strMsg;
m_zeroTrap.GetMessage(strMsg);
MessageBox(strMsg, "Error");
}
if(GetFocus()==this) {
Sleep(m_nSpeed);
InvalidateRect(&m_KaleideRect, FALSE);
}
// Do not call CWnd::OnPaint()
// for painting messages
}

Points of note - firstly, we don' need no stinking don't
need to call BeginPaint. There's no caret to hide and we
have our own DC to play with, thanks.

We do, however, need to validate our window. Note that you can
sort-of-kind-of get away with not calling ValidateRect here, but it
means that the OS will continually harass your application with WM_PAINT
messages. Not a good thing. Even though WM_PAINT messages are typically
posted to the thread and have a low priority, having a surplus of them
in the threads input queue will make it difficult for other messages to
get through. The call to Sleep helps a bit, since when the thread wakes
up the important messages tend to get the respect they deserve, but its
still good advice to validate your window post haste inside a WM_PAINT
handler.

OnEraseBkgnd will not be called, not because we set bErase
to false when we invalidated, but because we're not using BeginPaint. (The
CPaintDC object whose creation is commented out would have caused BeginPaint to
be called).

Next, we call a method that draws whatever updates we need to the
primary background compatible DC (set up in OnCreate and cleared in the
initial call to OnEraseBkgnd) and then blit that to the
screen DC.

However, its still nice to determine, as we did in OnEraseBknd,
whether we're being asked to paint something new (our own
InvalidateRect) or in response to some VB based bloated
cow of an application slobbering all over our real estate - so I put the
call in to GetForegroundWindow here as well. Actually making the
call to BitBlt in this situation may be overkill. If a
WM_PAINT message arrives when the gribble window doesn't have focus,
chances are that OnEraseBkgnd has done the blit work. Note
that calling ValidateRect inside OnEraseBkgnd
in this situation will not suppress the WM_PAINT message posted at the
end of the WM_SYNCPAINT type message flow.

Lastly, if we have focus, a call to Sleep allows the user to slow things
down (to a max of 1 second, given the property dialogs restrictions) and
we call InvalidateRect to start again. You might want to change
this to use a timer, which is the normal way of things for screen savers
and the like. Using sleep makes the app less responsive, but
invalidating here is convenient - if we validate the rect when we lose
focus, or in response to a right click, or an exception handler, we stop
the process without further ado.

Gribble me this, Blitman!

So, what's all this blitting stuff going to accomplish? Nice of you
to ask. Bloody amazing you're still reading at this point,
actually Well, originally my idea was to make a
kaleidoscope. Really. But making a realistic kaleidoscope requires
intelligence the ability to rotate images in
non-trivial ways, and all the good rotation transforms are only
available on NT, not Windows 9x, so I settled for... um... well,
whatever. Call it a Gribeidoscope I guess...

All the interesting stuff here takes place in two CGribbleWnd member
functions, Grb2Fn_DrawSomethingToGribbleDC and Grb2Fn_BlitGribbleToQuadrantsDC.

Grb2Fn_DrawSomethingToGribbleDC draws, as the name suggests, a something to
the gribble DC. This is background DC behind the real background DC, which I
call the quadrants DC. Grb2Fn_DrawSomethingToGribbleDC splits the gribble square
into two triangles, and reflects each item drawn by swapping x and y coordinates
and alternately setting each triangle as the clip region for the gribble DC.
This allows reflection on the diagonal dissecting the square, which is
unavailable with the simple flips on the x and y axis available with the
StretchBlt function.

Then, in OnPaint,
Grb2Fn_BlitGribbleToQuadrantsDC is called and, starting at
the top left corner, and performs three StretchBlt calls that copy a
quarter of the gribble square to the top left of the quadrants square
(DC), then reflect that square into the bottom left quadrant, then
reflect that half to the right half, and we have our gribeidosopic
effect.

Grb2Fn_DrawSomethingToGribbleDC is called when the gribble window is first
created, and when the user left clicks in the window. (You'll have to left click
a few times before the gribble gets interesting.)

Misc

There is a properties dialog implemented to allow the user to select the type
of gribble to be used (dots, lines, or triangles) and various other settings.
Current settings are stored in the registry under HKCU\Software\Gribble\Gribble2.

Error handling

I noticed that most GDI calls returned 0 for failure, and that some
(but not all) required a call to GetLastError to determine
the cause of the failure. I got tired of doing error checks on every
call, so I created a small class called CZeroResultTrap
whose sole purpose in life is to grab the last error and throw an
exception if it is assigned a 0. If the GetLastError call returns 0, it will
report a generic message. I leave it in to make the error checking less intrusive, but
don't recommend you rush out and use it in any production apps. Its also
interesting to note that some GDI calls simply don't fail - selecting a
default (stock) object into a screen DC with MM_TEXT mapping mode, for
example, or palette selection, which has no memory requirements.
If you'd like to see where I stole that info See the
article "GDI OBJECTS" in the MSDN for more on this.

Summary

The performance of this gribble graphic is not going to win any
awards, and the math (if you can call it that) is pretty simple. I'm
hoping the article is useful for its discussion of the windows paint
messages and how they can be handled, and perhaps some simple blitting
ideas. I don't think I've exhausted all the issues here, so check for
flames feedback on this article before betting your
salary on these techniques.

About the Author

Comments and Discussions

Enjoyed your article immensely.. I'm handicapped trying to read Microsoft's Visual C++ documentation as English is my first (and only) language. I'm currently trying to write an app that displays graphics and text full screen and was trolling through the various articles on screensavers looking for clues.

Your writing style, informative manner and liberal use of humor were quite entertaining.

The concepts presented gave me a new way of looking at Windoze... You don't have to call "OnPaint" - waycool!

I guess by transition you mean you want to gradually replace the current screen with the new one.

I think some techniques would be easier than others - it would probably be pretty easy to use something like SetDIBitsToDevice or StretchDIBBits in a loop to set progressively larger portions of the new bitmap to the center of the screen for an 'explode in' effect.

You _might_ not even need to blit. These fns are pretty quick.

Timing could be an issue - it is for the Blt fns, which slow down as the size of the dest rect enlarges, regardless (it seems) of the size/shape of the dest DCs clipping region.

I'm not to familiar with PPs transitional effects - what methods have you been trying?

Also, I'm not primaily a 'graphics type guy'. You might get some better answers posting in the VC forum - bound to be someone there who knows how PP does things.

I've printed it out and will give it a proper read tomorrow, but I have to say bravo on your writing style. The careful use of humor certainly kept me paying attention for the first couple of pages (until I became too tired to read any more - not your fault, just a severe lack of sleep).

As I haven't finished reading it I wont vote yet, but it's looking like a five out of five just for the text style!