Click here to Skip to main content
15,860,972 members
Articles / Desktop Programming / Win32

Guide to Win32 Memory DC

Rate me:
Please Sign up or sign in to vote.
4.98/5 (82 votes)
21 Jul 2011CPOL15 min read 190.1K   11.5K   163   38
Guide to creating and using Memory Device Contexts (DC) in Win32.

Single Buffer Painting Screen shot

Double Buffer Painting Screen shot

Introduction

Eons ago in computer time, I left-off with an article called Guide to Win32 Paint for Intermediates. That article described the basics of the WM_PAINT message, as well as how to use most of the different types of Win32 Device Contexts (DC). This article will describe the Memory DC. The Metafile DC will still be ignored in this article.

My previous articles have been written with WTL. I still prefer to do Win32 development with WTL. However, the demonstration source code provided will be raw C/C++ Win32 calls and window management. This is to simplify the code that you are looking at, and eliminate a code dependency.

Background

Nine years ago, I started up a series on Win32 paint tutorials and indicated in the first article that it was the first in a series of five. I had completely forgotten that intention until I re-read the first article. I thought about revisiting this topic and finishing the series, but I quickly dismissed the thought thinking that too much time has passed and people are moving on to other technologies like .NET, DirectX, and OpenGL.

I am still surprised to see how often these basic tutorials are visited and downloaded. I then noticed myself looking up basic information for other topics in development. I have recently done some GUI work with custom controls in Java. I was surprised to see how similar the process was to Win32 painting. (While proof-reading, I decided that I get surprised a lot.)

What I have realized is that not much has actually changed in the last 9 years. There are new libraries that make interacting with multimedia easier than the basic Win32 APIs, however the concepts are the same. Also, some technologies such as DirectX may have a high learning curve to get up and running and gain the benefits of the extra power from the framework that may not even be necessary for a simple UI painting that a developer could want to help their application stand out from all of the similar programs.

I have decided that I will continue to document the usage, capabilities, and my experience with the Win32 API calls, primarily GDI, but there is so much potential that exists, and not enough context to help people apply it to their programs to create amazing things.

There are many other technologies for generating graphics that exist for Windows to create two dimensional vector-based and raster-based images: GDI+, DirectDraw 2D, DirectWrite. I would like to explore some of those technologies in the future. For now, let's continue on with the venerable GDI API.

Memory Device Context

The memory DC is an important concept and tool in Win32 development. Most other DC types are directly related to a window, or some device such as a printer on the system. The memory DC is an object that exists only in memory. Some of the common uses for Memory DCs are to store intermediate images for display composition; load Bitmaps and other image types from files; create back-buffers for double and triple buffer painting to reduce flicker with animation.

Obtaining a Memory Device Context

There is only one way to create a Memory Device Context:

C++
HDC CreateCompatibleDC(
    __in HDC hdc          // Handle to an existing DC
);

The memory DC that is created will be compatible with the device which the hdc value passed in represents. This means that raster operations like BitBlt and StretchBlt can be used to transfer the image in memory DC over to the same device hdc represents.

When you are using Memory DCs for devices other than the display, be aware the device must support raster operations for it to be useful. The call to create may succeed, but attempting to use the resulting memory DC will fail. "Don't they all support raster operations?" Not necessarily, think of pen-based plotters. Meta-files are another device that is not useful with memory DCs. The meta-file is used to record GDI actions, and its playback is not rasterized. Regardless of device, you can determine if the device you are working with supports raster operations by calling GetDeviceCaps and requesting the support flags for RASTERCAPS.

If hdc is NULL, then a memory DC will be created that is compatible to the application's current display screen. One caveat to be aware of when this type of memory DC is created is that the memory DC is attached to the thread which created the memory DC. When the thread is destroyed, the memory DC will be destroyed as well.

When you are finished with the memory DC, use this function to release the resource:

C++
BOOL DeleteDC(
    __in HDC hdc          // Handle to the DC, in this case, memory DC
);

Make the Memory Device Context Useful

If you were to start using the new memory DC immediately after you call CreateCompatibleDC, you will most likely be disappointed. The memory DC is initialized with a mono-chromatic 1x1 pixel bitmap by default. If you are after a single pixel that can be black or white, you have exactly what you need. For everyone else, the next step will be to allocate a buffer for the memory DC to write to.

This is the simplest method to create a buffer for a memory DC, and it is the only method I will cover in this article:

C++
HBITMAP CreateCompatibleBitmap(
    __in HDC hdc,           // Handle to the DC
    __in int nWidth,        // Desired width of the bitmap in pixels
    __in int nHeight        // Desired height of the bitmap in pixels
);

This will create something called a Device-Dependent Bitmap (DDB). That is a bitmap that is only compatible for the current device (display, printer, etc...), and therefore can only be selected into a DC that is compatible with this device. With the newly created bitmap, you can associate it with the memory DC with a call to SelectObject.

The other type of bitmap is a Device-Independent Bitmap (DIB). DIBs provide more advanced access to the bitmap resource, and are quite a bit more complicated to setup and initialize properly. They are general use bitmap objects that are independent of the bit format required for any device. When you want direct pixel access to your bitmap, a DIB is the way to go. Because I want to focus on memory DCs, I will save the DIB topic for a separate article.

Here is what the sequence of commands should look like to create a memory DC with a buffer to write into:

C++
// Create a memory DC and Bitmap.

HDC hDC ...     // existing DC from a window or call to BeginPaint
int width = 400;
int height= 300;

HDC     hMemDC = ::CreateCompatibleDC(hDC);
HBITMAP hBmp   = ::CreateCompatibleBitmap(hDC, width, height);
::SelectObject(hMemDC, hBmp);

Avoid a Common Mistake

Before we get too far away from code where I showed you what you need to start running, I want to make sure you are holding this new pair of scissors safely. Do not use a Memory DC with a call to CreateCompatibleBitmap.

C++
...
// You may be tempted to do this; DON'T:
HDC     hMemDC = ::CreateCompatibleDC(hDC);
                                     // DON'T DO THIS
                                     //       |
                                     //       V
HBITMAP hBmp   = ::CreateCompatibleBitmap(hMemDC, width, height);
...
// TIP: Try to use the same DC to create
//      the Bitmap which you used to create the memory DC.

Remember the part about how The memory DC is initialized with a mono-chromatic 1x1 pixel bitmap by default?! You should, I bolded it above. If you create a bitmap to be compatible with the memory DC, you will get a mono-chromatic bitmap of the size you requested. Usually, it is easy to recognize this mistake when you see the results on the screen. The trouble is, it may not paint to the screen only as black and white. It may use the System Foreground color and System Background color instead. This makes the issue a little more difficult to recognize, especially if you are compositing multiple bitmaps together before you BitBlt everything to the screen.

HBITMAP: There can be only one...

There can be only one type of each GDI object selected into any type of DC at a time. The memory DC is unique, because it is the only type of DC that is possible to use an HBITMAP with a call to ::SelectObject. Unlike other GDI object types, the HBITMAP can only be selected into one DC at a time. Therefore, if you are using the same bitmap with multiple memory DCs, be sure to save the original HGDIOBJ pushed-out from the memory DC when you select your bitmap into the DC. Otherwise, your attempt to select the bitmap into a second memory DC will fail.

C++
// Standard setup for creating memory DC and compatible bitmap buffer.
HDC     hMemDC = ::CreateCompatibleDC(hDC);
HBITMAP hBmp   = ::CreateCompatibleBitmap(hDC, width, height);
::SelectObject(hMemDC, hBmp);

...
    
// Another memory DC is required later for something spectacular.
// (Note: The same errors will occur with the following code  
//        even if you are only attempting something medicore at best.)
HDC     hMemDC2 = ::CreateCompatibleDC(hDC);

// Attempt to select the original bitmap that is currently held by hMemDC.
::SelectObject(hMemDC2, hBmp);   // <- No Bueno: Call fails, returns NULL.

The following code shows the proper handling of a single bitmap with multiple memory DCs.

C++
// Standard setup, getting pretty good with this part.
HDC     hMemDC = ::CreateCompatibleDC(hDC);
HDC     hMemDC2= ::CreateCompatibleDC(hDC);
HBITMAP hBmp   = ::CreateCompatibleBitmap(hDC, width, height);

// Save off the object that is pushed out by selecting the new bitmap.
HGDIOBJ hOldBmp= ::SelectObject(hMemDC, hBmp);

...

// Select the original bitmap into this DC.
// 1) Free the original memory DCs hold on the bitmap.
::SelectObject(hMemDC, hOldBmp);  // <- returns same handle as hBmp.

// 2) The bitmap is available to be selected into any compatible memory DC.
::SelectObject(hMemDC2, hBmp);

There is a possible shortcut that you can use to release a memory DC's hold on a bitmap. This shortcut is valid only if you no longer need the original memory DC but you want to keep the bitmap object around. If you delete the memory DC, this will release the hold on the bitmap and there is no further work to do. You no longer have to restore the original bitmap handle into the memory DC to release the bitmap handle.

C++
// Standard memDC/bitmap setup
...

::DeleteDC(hMemDC);

// hBmp is now available to be selected into a different memory DC if desired.
::SelectObject(hMemDC2, hBmp);

The previous shortcut is actually a practice that would be a good habit to use all of the time. The HBITMAP cannot be deleted while it is selected into a memory DC. Therefore, if you attempt to call ::DeleteObject on your bitmap, without first restoring the original bitmap into the memory DC, or deleting the memory DC, you will have a GDI resource leak.

C++
// Standard memDC/bitmap setup
...

::DeleteObject(hBmp); // <- Call fails, hBmp is still held by hMemDC.
                      //    A resource leak will occur.
                      //    ( Pfft, Who cares? isn't that why you splurged 
                      //      and bought 24 GB of RAM for your PC anyway?)
::DeleteDC(hMemDC);

To avoid this situation entirely, a good practice is to release the memory DCs first. If you desire to destroy the bitmap at this time as well, there are no issues with freeing them at this point.

C++
// Standard memDC/bitmap setup
...

// Simply swap the order of clean up from the example above.
// Always free the memory DC first, then the bitmap to avoid common resource leaks.
::DeleteDC(hMemDC);
::DeleteObject(hBmp);   // No resource leak.
                      // ( Looks like you will have to find some 
                      //   other way to frivolously waste resources.)

There is one situation that you have to just accept the facts, and write all of the code in a specific order for all of the APIs to work properly; you want to keep the memory DC and free the bitmap. To do this, the original bitmap will need to be selected into the memory DC, then the desired bitmap can be destroyed.

C++
// Standard memDC/bitmap setup
...

// Kill the bitmap, keep the memory DC 
// Make sure you save the original bitmap from the memory DC:
HGDIOBJ hOldBmp= ::SelectObject(hMemDC, hBmp);

// Select the original bitmap back into the memory DC.
// This frees the BITMAP object to be destroyed.
// Note: It does not have to be the original bitmap that gets selected back into hMemDC.
//       It only has to be a compatible bitmap different than hBmp.
::SelectObject(hMemDC, hOldBmp);
::DeleteObject(hBmp);   

// hMemDC is still valid at this point, and can accept another comaptible bitmap.
// If no other bitmap is selected into the memory DC, you are back to the 
// mono-chromatic 1x1 pixel bitmap.

A Few Helpful Functions

After I reviewed my previous articles, I noticed a few functions that I have not mentioned yet that are very useful. This seems like a good place to introduce them. These functions can be used with any type of DC, they are not exclusive to memory DCs. MSDN indicates for the SelectObject method:

"An application should always replace a new object with the original, default object after it has finished drawing with the new object.

Most times, you can get away with ignoring this and forget you ever saw it. However, if you are subclassing or creating custom controls and playing with the Windows messages that paint the controls (WM_ERASEBKGND, WM_CTLCOLOR_XXX, WM_PRINT ...), you will most likely want to follow this suggestion. Following this suggestion becomes very painful when you are swapping between multiple brushes, pens, regions, fonts, and modes. You have a very colorful picture to paint.

However, your code will look something like this:

C++
// Setup paint for first layer.
HGDIOBJ hOldBrush = ::SelectObject(hDC, hBrush);
HGDIOBJ hOldPen   = ::SelectObject(hDC, hPen);
HGDIOBJ hOldFont  = ::SelectObject(hDC, hFont);
HGDIOBJ hOldMan   = ::SelectObject(hDC, hBmp);

// ... Paint a motley display

::SelectObject(hDC, hOldBrush);
::SelectObject(hDC, hOldPen);
::SelectObject(hDC, hOldFont);
::SelectObject(hDC, hOldMan);

Take the previous segment of code and intersperse other DCs used for compositing, function calls that may not play nice the way you are trying, and a couple hundred lines of paint code, things start to become painful to restore your DCs to the state they were given to you. Not to mention each time you call select object for a new DC, you need to come up with a new name for the old GDI object you will have to restore.

A Pair of Functions to Help Ease the Pain(t)

C++
// Take a snap-shot of the current state of the DC
  int SaveDC(
    __in HDC hdc,           // Handle to the DC
);

// Restore the DC to a previously saved state
int RestoreDC(
    __in HDC hdc,           // Handle to the DC
    __in int nSavedDC       // Saved state claim ticket
);

This pair of functions behaves similar to Push (SaveDC) and Pop (RestoreDC) behavior from a stack. The implementation itself is a stack. Each time you call SaveDC, another snap-shot of the DC configuration is saved on the stack. SaveDC will return an int that refers to that call of SaveDC. The return value is a cookie or a context ID.

When you call RestoreDC to return back to a previous configuration, pass in the appropriate context ID and the DC will be returned to the state of the DC when you took the original snap-shot. You can call SaveDC multiple times. You do not have to call RestoreDC the same number of times. All of the saved snap-shots will be popped off the stack to reach the specified context ID you send in to RestoreDC.

SaveDC and RestoreDC Have Many Uses

C++
// Parents are leaving you home alone for the weekend
// Take a snap-shot of the house
int houseBeforeMotherLeft = ::SaveDC(hDC);

// Start texting, get the word out "Party at my place"
// Get your cash and friend's older brother to buy "refreshments" for the party
::SelectObject(hDC, hBrush);
::SelectObject(hDC, hPen);
::SelectObject(hDC, hFont);
::SelectObject(hDC, hBmp);

// Get the party started, 
...
// And oh yeah, the word spread a little too far, But what a party 
...

// Cleanup is a snap, Mother will never know.
::RestoreDC(hDC, houseBeforeMomLeft);

// Parents return
return;

// Note: The previous scenario would be very creepy if you are 40 years old  
//       and live in your mom's basement, rather than are a highschool teenager.

With a call to RestoreDC, only the state of the DC is restored. You will still be responsible to clean up any GDI objects that were created, otherwise you will have resource leaks in your application. If you call SaveDC many times, and never call RestoreDC, the saved states will persist until the specified DC is released.

Demonstration

I created a small demonstration application that shows many of the ways that a memory DC can be used. The memory DC is used to cache a bitmap to eliminate a repetitive chunk of paint code that produces the same image each time the paint routine is called. (Note: The application does not cache all of the images possible in order to still be able to demonstrate the value of a back-buffer.) The application uses a memory DC to show how a double-buffer paint process is implemented. I also demonstrate the process of compositing multiple bitmaps to create a single final image.

When I began writing the demonstration app, I was only after showing how a double-buffer paint scheme is implemented. That is a common use for memory DCs. However, when I was done with that phase, I wanted to make the flicker more apparent. I could get most of the flicker to disappear by making the window smaller on my screen. So I added a few random rectangles to the paint process, as well as uses of Gradient Fill and Alpha Blend.

What Does It Do?

The application will display two separate sets of random shapes. One side will have rectangles, the other side ellipses. A wiper will slide across the screen from left to right. As it slides to the right, the right-side area will be reduced and less of the random shapes will be visible for this side. The left side will enlarge revealing more of the random shapes. When the wiper slides all of the way to the right, only the original left group of shapes will remain. The wiper will then start on the left-side again, and a new set of random shapes will be revealed on the left-side of the wiper.

Interaction

There are a few interactive commands to change the behavior of the demonstration.

  • Left button down: Pause the animation.
  • Mouse drag with left button: Move the wiper side-to-side.
  • Right click: Toggle between double-buffer and direct-buffer painting.
  • Resize: The cached resources will be regenerated to match the screen.

Clipping Regions

To paint both halves of the display, I created two Win32 Regions, one for the left and one for the right. One at a time, I set each region as the Clipping Region for the DC which was drawn too, and all of the shapes for that region were drawn. After painting both sets of shapes, with the two clipping regions set, this is what the image would look like:

The image buffer after the two clipped halves are painted.

The Wiper

Ironically, the wiper was the first concept for the demo. I had the idea of an animation of a gleaming highlight on a control. Somewhere along the way, it turned into what it is now; and to top it all off, I have optimized this portion of the demo by pre-generating the image mask for the wiper and storing it in a cached bitmap. This allowed me to demonstrate another use for the memory DC. Don't worry, with all of the shape drawing performed each frame, there is still plenty of flicker for the direct-paint method.

I learned a lot developing the wiper. Unfortunately, most of it was not related to memory DCs in any form at all. Therefore, I must save this story for a different article. I will say that the final solution for the effect I was going for, went against my intuition. I then found myself researching deeper into GradientFill and AlphaBlend. I also have decided that there were no easy to find articles or samples that clearly explained how to use these functions beyond the most obvious usage. So I think I may have found my next topic.

The wiper that slides across the screen will appear in two colors to indicate the method for updating the display:

  • CodeProject Green: Double-buffer by painting to a memory DC back-buffer.
  • CodeProject Orange: Direct-buffer paint on the display device.

Here is a close up sample of the wiper blended on top of both sets of shapes for the final appearance of the demonstration app.

The image buffer after the two clipped halves are painted.

About the Code

As I mentioned earlier in the article, the program is written using standard C/C++ access to Win32 API calls. I have included the project as a VS2008 project. For VS2010, you should be able to import and convert the project. For earlier versions of Visual Studio, you really should upgrade if possible. I have separated most of the code that was written for demonstration into a separate file from the generated project called MemDcUsage.cpp.

I have encapsulated all of the demonstration code in the article:: namespace to help people unfamiliar with Win32 GDI be able to distinguish the functions that I wrote from the API calls. I have tried to reference all of the Win32 API calls with the Global namespace operator, such as ::CreateCompatibleDC(...). Hopefully I did not miss any. Here is a sample from the main window procedure calling the functions written for the article:

C++
...
// Handle Window Size changes.
case WM_SIZE:
case WM_SIZING:
    // Flush the back buffer, 
    // The size of the image that needs to be painted has changed.
    article::FlushBackBuffer();

    // Allow the default processing to take care of the rest.
    return DefWindowProc(hWnd, message, wParam, lParam);
    
case WM_PAINT:
{
    hdc = ::BeginPaint(hWnd, &ps);

    article::PaintAnimation(hWnd, hdc);

    ::EndPaint(hWnd, &ps);

    break;
}
...

There are probably not any functions or files that you will be able to take away from the demonstration and pop into your own applications. However, there are plenty of code snippets that you should be able to morph into something useful in the context of your application. These chunks of code I have marked like this //!!! as well as a short comment that describes what you should be able to take from this.

C++
//!!! Save DC / Restore DC Sample
int ctx = ::SaveDC(hDC);

// Draw the set of rectangles.
::SelectObject(hBufferDC, g_hPenBlack);
::SelectObject(hBufferDC, ::GetStockObject(WHITE_BRUSH));

// Draw Right Side:
DrawShapes(hBufferDC, hRightRgn, width, height, g_group1, g_isGroup1Active);
// Draw Left Side:
DrawShapes(hBufferDC, hLeftRgn, width, height, g_group2, !g_isGroup1Active);

::RestoreDC(hDC, ctx);

History

  • July 21, 2011: Fixed corrupted download files.
  • July 16, 2011: Added overlooked details regarding HBITMAP and resource management.
  • July 10, 2011: Original release.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
United States United States
I am a software architect and I have been developing software for nearly two decades. Over the years I have learned to value maintainable solutions first. This has allowed me to adapt my projects to meet the challenges that inevitably appear during development. I use the most beneficial short-term achievements to drive the software I develop towards a long-term vision.

C++ is my strongest language. However, I have also used x86 ASM, ARM ASM, C, C#, JAVA, Python, and JavaScript to solve programming problems. I have worked in a variety of industries throughout my career, which include:
• Manufacturing
• Consumer Products
• Virtualization
• Computer Infrastructure Management
• DoD Contracting

My experience spans these hardware types and operating systems:
• Desktop
o Windows (Full-stack: GUI, Application, Service, Kernel Driver)
o Linux (Application, Daemon)
• Mobile Devices
o Windows CE / Windows Phone
o Linux
• Embedded Devices
o VxWorks (RTOS)
o Greenhills Linux
o Embedded Windows XP

I am a Mentor and frequent contributor to CodeProject.com with tutorial articles that teach others about the inner workings of the Windows APIs.

I am the creator of an open source project on GitHub called Alchemy[^], which is an open-source compile-time data serialization library.

I maintain my own repository and blog at CodeOfTheDamned.com/[^], because code maintenance does not have to be a living hell.

Comments and Discussions

 
QuestionQuestion on Resource Leaks Pin
Laurie Stearn19-Sep-20 0:13
Laurie Stearn19-Sep-20 0:13 
PraiseThanks! Pin
Kawaoneechan31-May-20 4:58
Kawaoneechan31-May-20 4:58 
QuestionBitmap Image Processing Without WM_PAINT Pin
djshuman24-Apr-19 4:05
djshuman24-Apr-19 4:05 
QuestionBackground and transparent drawing could enhance the article Pin
KarstenK20-Feb-14 7:44
mveKarstenK20-Feb-14 7:44 
AnswerRe: Background and transparent drawing could enhance the article Pin
Paul M Watt20-Feb-14 12:03
mentorPaul M Watt20-Feb-14 12:03 
QuestionMy vote of 5 Pin
kanalbrummer31-Dec-13 5:29
kanalbrummer31-Dec-13 5:29 
AnswerRe: My vote of 5 Pin
Paul M Watt31-Dec-13 16:19
mentorPaul M Watt31-Dec-13 16:19 
GeneralMy vote of 5 Pin
sprice867-Dec-13 11:30
professionalsprice867-Dec-13 11:30 
GeneralMy vote of 5 Pin
tercelfly30-May-13 4:48
tercelfly30-May-13 4:48 
QuestionDelete of bitmap before release of memory DC Pin
douglasjordan30-Sep-12 6:07
douglasjordan30-Sep-12 6:07 
AnswerRe: Delete of bitmap before release of memory DC Pin
Paul M Watt30-Sep-12 18:49
mentorPaul M Watt30-Sep-12 18:49 
GeneralMy vote of 5 Pin
hoseinhero15-Aug-12 5:56
hoseinhero15-Aug-12 5:56 
QuestionCompare 2 MemDC Pin
FISH78621-May-12 8:16
FISH78621-May-12 8:16 
AnswerRe: Compare 2 MemDC Pin
Paul M Watt21-May-12 13:45
mentorPaul M Watt21-May-12 13:45 
GeneralRe: Compare 2 MemDC Pin
FISH78623-May-12 17:57
FISH78623-May-12 17:57 
GeneralMy vote of 5 Pin
questononcista20-May-12 5:07
questononcista20-May-12 5:07 
GeneralMy vote of 5 Pin
amysue26-Dec-11 2:22
amysue26-Dec-11 2:22 
QuestionDemo Issue Pin
Amro Ibrahim13-Aug-11 0:30
Amro Ibrahim13-Aug-11 0:30 
AnswerRe: Demo Issue Pin
Paul M Watt13-Aug-11 18:49
mentorPaul M Watt13-Aug-11 18:49 
GeneralRe: Demo Issue Pin
Amro Ibrahim14-Aug-11 1:10
Amro Ibrahim14-Aug-11 1:10 
QuestionGood article! Pin
Victor Nijegorodov25-Jul-11 22:47
Victor Nijegorodov25-Jul-11 22:47 
AnswerRe: Good article! Pin
Paul M Watt26-Jul-11 18:41
mentorPaul M Watt26-Jul-11 18:41 
QuestionTwo thumbs up! Pin
Doc Lobster22-Jul-11 20:56
Doc Lobster22-Jul-11 20:56 
AnswerRe: Two thumbs up! Pin
Paul M Watt22-Jul-11 21:20
mentorPaul M Watt22-Jul-11 21:20 
NewsCorrection for Corrupted Zip Files [modified] Pin
Paul M Watt20-Jul-11 15:20
mentorPaul M Watt20-Jul-11 15:20 

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

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