HOWTO: Perform Rubber Band Selection Without ROP Codes (314394)



The information in this article applies to:

  • Microsoft Platform Software Development Kit (SDK) 1.0
  • Microsoft Windows XP Professional
  • the operating system: Microsoft Windows XP 64-Bit Edition

This article was previously published under Q314394

SUMMARY

There are times when it is not desirable to use raster operation (ROP) codes to perform a rubber band selection. This article describes an alternative approach to using a rubber band selection that does not use ROP codes.

MORE INFORMATION

The following code captures and replaces the edges of the rubber band selection rectangle rather than using ROP codes to invert and restore them.

The code is written to retrieve a selection rectangle from the user in response to a WM_LBUTTONDOWN message. You can left-click and hold the mouse button while dragging the cursor to adjust the size of the selection area.
typedef struct {
    HDC     hdcMem;     // For capturing/restoring edge bitmaps
    HBRUSH  hbr;        // For painting the selection rectangle
    HBITMAP hbmLeft;    // Holds the original bits of left side
    HBITMAP hbmTop;     // Holds the original bits of top side
    HBITMAP hbmRight;   // Holds the original bits of right side
    HBITMAP hbmBottom;  // Holds the original bits of bottom side
    int     iThickness; // Thickness of the edge border
} RUBBERBANDINFO, *LPRUBBERBANDINFO;


/**** NormalizeRect() *******************************************\ 
*                                                                *
* Makes sure that the top and left values of a rectangle are     *
* lower than the values of the right and bottom.                 *
*                                                                *
\****************************************************************/ 
void NormalizeRect (LPRECT prc)
{
    int temp;

    // Swap left and right.
    if (prc->right < prc->left) {
        temp       = prc->right;
        prc->right = prc->left;
        prc->left  = temp;
    }
     
    // Swap top and bottom.
    if (prc->bottom < prc->top) {
        temp        = prc->bottom;
        prc->bottom = prc->top;
        prc->top    = temp;
    }
}

/**** RBInitialize() ********************************************\ 
*                                                                *
*  Initializes a RUBBERBANDINFO structure in preparation for     *
*  rubber banding.                                               *
*                                                                *
\****************************************************************/ 
LPRUBBERBANDINFO RBInitialize(HDC hdcScr, int iThickness)
{
    LPRUBBERBANDINFO lprbi;
    LOGBRUSH lb = {BS_PATTERN8X8, 0, HS_BDIAGONAL};
    HBITMAP hbmBrush;
    HDC hdcBrush;
    int x,y;

    // Limit thickness to 1 to 8 pixels.
    if ((iThickness > 8) || (iThickness < 1))
        return FALSE;

    // Allocate space for the structure.
    lprbi = (LPRUBBERBANDINFO)GlobalAlloc(GPTR, sizeof(RUBBERBANDINFO));
    if (!lprbi)
        return NULL;

    // Make an interesting 8x8 bitmap pattern (diagonal red/white 
    // stripes) to create a brush with.
    hbmBrush = CreateCompatibleBitmap(hdcScr, 8, 8);
    hdcBrush = CreateCompatibleDC(hdcScr);
    SelectObject(hdcBrush, hbmBrush);
    for (x=0; x<8; x++) {
        for (y=0; y<8; y++) {
            if (((x+y)/4) & 1)
                SetPixel(hdcBrush, x,y, RGB(255,255,255));  // White
            else
                SetPixel(hdcBrush, x,y, RGB(255,0,0));      // Red
        }
    }
    DeleteDC(hdcBrush); // Clean up

    // Create a brush from our pattern bitmap.
    lb.lbHatch = (DWORD)hbmBrush;
    lprbi->hbr = CreateBrushIndirect(&lb);
    DeleteObject(hbmBrush);  // Clean up

    // Store the thickness of the edges of the rubber band rectangle.
    lprbi->iThickness = iThickness;

    // Create bitmaps to store the original contents of the screen
    // that will be covered by the edges of the rubber band rectangle.
    lprbi->hbmLeft   = CreateCompatibleBitmap(hdcScr, iThickness, GetSystemMetrics(SM_CYVIRTUALSCREEN));
    lprbi->hbmRight  = CreateCompatibleBitmap(hdcScr, iThickness, GetSystemMetrics(SM_CYVIRTUALSCREEN));
    lprbi->hbmTop    = CreateCompatibleBitmap(hdcScr, GetSystemMetrics(SM_CXVIRTUALSCREEN), iThickness);
    lprbi->hbmBottom = CreateCompatibleBitmap(hdcScr, GetSystemMetrics(SM_CXVIRTUALSCREEN), iThickness);

    // Create an HDC to use to capture and display our edge bitmaps.
    lprbi->hdcMem    = CreateCompatibleDC(hdcScr);

    return lprbi;
}

/**** RBDrawBorder() ********************************************\ 
*                                                                *
*  Draws the border (edges) of the current selection region      *  
*  as indicated by the lprc parameter with the attributes        * 
*  specified in the RUBBERBANDINFO.                              *
*                                                                *
\****************************************************************/ 
BOOL RBDrawBorder(HDC hdcScr, LPRUBBERBANDINFO lprbi, LPRECT lprc)
{
    HBRUSH hbrOld;
    POINT pt;
    int iCounter; 

    // Make sure we were not passed NULLs.
    if (!lprbi || !lprc || !hdcScr)
        return FALSE;

    // This is used to offset the brush origin to
    // make a quick-and-dirty animated selection border.
    iCounter = (GetTickCount() >> 5) & 7; 
    
    hbrOld = (HBRUSH)SelectObject(hdcScr, lprbi->hbr);

    // Capture the part of the screen that our edges will overwrite.
    SelectObject(lprbi->hdcMem, lprbi->hbmLeft);
    BitBlt(lprbi->hdcMem, 0,0, lprbi->iThickness, lprc->bottom-lprc->top, hdcScr, lprc->left, lprc->top, SRCCOPY);
    
    SelectObject(lprbi->hdcMem, lprbi->hbmRight);
    BitBlt(lprbi->hdcMem, 0,0, lprbi->iThickness, lprc->bottom-lprc->top, hdcScr, lprc->right-lprbi->iThickness, lprc->top, SRCCOPY);

    SelectObject(lprbi->hdcMem, lprbi->hbmTop);
    BitBlt(lprbi->hdcMem, 0,0, lprc->right-lprc->left, lprbi->iThickness, hdcScr, lprc->left, lprc->top, SRCCOPY);

    SelectObject(lprbi->hdcMem, lprbi->hbmBottom);
    BitBlt(lprbi->hdcMem, 0,0, lprc->right-lprc->left, lprbi->iThickness, hdcScr, lprc->left, lprc->bottom-lprbi->iThickness, SRCCOPY);
                      
    // Set the brush origin to get an animated edge pattern during dragging.
    SetBrushOrgEx(hdcScr, iCounter, iCounter, &pt);

    // Draw the sides of the rectangle.
    PatBlt(hdcScr, lprc->left, lprc->top, lprbi->iThickness, lprc->bottom-lprc->top, PATCOPY); // Left side
    PatBlt(hdcScr, lprc->right-lprbi->iThickness, lprc->top, lprbi->iThickness, lprc->bottom-lprc->top, PATCOPY);   // Right side
    PatBlt(hdcScr, lprc->left, lprc->top, lprc->right-lprc->left, lprbi->iThickness, PATCOPY); // Top side
    PatBlt(hdcScr, lprc->left, lprc->bottom-lprbi->iThickness, lprc->right-lprc->left, lprbi->iThickness, PATCOPY); // Bottom side

    // Restore the previous brush.
    SelectObject(hdcScr, hbrOld);

    // Restore the previous brush origin.
    SetBrushOrgEx(hdcScr, pt.x, pt.y, NULL);

    return TRUE;
}

/**** RBRestoreBorder() *****************************************\ 
*                                                                *
*  Restore the original screen contents that were overwritten by *  
*  the edges of our selection rectangle                          * 
*                                                                *
\****************************************************************/ 
BOOL RBRestoreBorder(HDC hdcScr, LPRUBBERBANDINFO lprbi, LPRECT lprc)
{
    // Make sure we were not passed NULLs.
    if (!lprbi || !lprc || !hdcScr)
        return FALSE;

    SelectObject(lprbi->hdcMem, lprbi->hbmLeft);
    BitBlt(hdcScr, lprc->left, lprc->top, lprbi->iThickness, lprc->bottom-lprc->top, lprbi->hdcMem, 0,0, SRCCOPY);
    
    SelectObject(lprbi->hdcMem, lprbi->hbmRight);
    BitBlt(hdcScr, lprc->right-lprbi->iThickness, lprc->top, lprbi->iThickness, lprc->bottom-lprc->top, lprbi->hdcMem, 0,0, SRCCOPY);

    SelectObject(lprbi->hdcMem, lprbi->hbmTop);
    BitBlt(hdcScr, lprc->left, lprc->top, lprc->right-lprc->left, lprbi->iThickness, lprbi->hdcMem, 0,0, SRCCOPY);

    SelectObject(lprbi->hdcMem, lprbi->hbmBottom);
    BitBlt(hdcScr, lprc->left, lprc->bottom-lprbi->iThickness, lprc->right-lprc->left, lprbi->iThickness, lprbi->hdcMem, 0,0, SRCCOPY);

    return TRUE;
}

/**** RBClose() *************************************************\ 
*                                                                *
*  Clean up the objects that we created and free up the memory   *  
*  that we allocated.                                            * 
*                                                                *
\****************************************************************/ 
BOOL RBClose(LPRUBBERBANDINFO lprbi)
{
    if (!lprbi)
        return FALSE;
    
    // Clean up the objects that we created.
    DeleteDC(lprbi->hdcMem);
    DeleteObject(lprbi->hbmLeft);
    DeleteObject(lprbi->hbmRight);
    DeleteObject(lprbi->hbmTop);
    DeleteObject(lprbi->hbmBottom);
    DeleteObject(lprbi->hbr);

    GlobalFree(lprbi);
    
    return TRUE;
}

/**** RBTrackMouse() ********************************************\ 
*                                                                *
*  Tracks the mouse movement and draws a selection rectangle     *
*  indicating the area between the initial button down point     *
*  and the current track position. This function exits and       *
*  returns the rectangle when the mouse button is released.      *
*                                                                *
\****************************************************************/ 
BOOL RBTrackMouse (HWND hwnd, POINT pt, LPRECT lprect)
{
    HDC   hdc;
    MSG   msg;
    LPRUBBERBANDINFO lprbi;
    POINT ptOrigin;
    RECT  rcBounds;

    // Make sure our parameters are valid.
    if (!IsWindow(hwnd) || !lprect)
        return FALSE;

    // Get the size of the client area so we can constrain 
    // our selection area.
    GetClientRect(hwnd, &rcBounds);

    // Get a display context to draw in.
    hdc = GetDC(hwnd);
    if (!hdc)
        return FALSE;

    // Initialize rubber banding for our display context.
    lprbi = RBInitialize(hdc, 8);
    if (!lprbi) {
        ReleaseDC(hwnd, hdc);
        return FALSE;
    }

    // Capture mouse movement.
    SetCapture(hwnd);

    // Get mouse coordinates relative to origin of window.
    ptOrigin.x = (short int)GetScrollPos(hwnd,SB_HORZ);
    ptOrigin.y = (short int)GetScrollPos(hwnd,SB_VERT);

    pt.x += ptOrigin.x;
    pt.y += ptOrigin.y;

    // Set the origin for the displayable area.
    SetWindowOrgEx(hdc, ptOrigin.x, ptOrigin.y, NULL);

    // Initialize clip rectangle to the point. 
    lprect->left   = pt.x;
    lprect->top    = pt.y;
    lprect->right  = pt.x;
    lprect->bottom = pt.y;

    // Eat mouse messages until a WM_LBUTTONUP is occurs. Meanwhile
    // continue to draw a rubber banding rectangle and display its dimensions.
    while (TRUE) {
        WaitMessage();
        if (PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST, PM_REMOVE)) {
        
            // Restore the previous border.
            RBRestoreBorder(hdc, lprbi, lprect);
            
            lprect->left   = pt.x;
            lprect->top    = pt.y;
            lprect->right  = (short)LOWORD(msg.lParam) + ptOrigin.x;
            lprect->bottom = (short)HIWORD(msg.lParam) + ptOrigin.y;
            
            NormalizeRect(lprect);

            if (lprect->left < rcBounds.left)     lprect->left = rcBounds.left;
            if (lprect->top  < rcBounds.top)      lprect->top = rcBounds.top;  
            if (lprect->right > rcBounds.right)   lprect->right = rcBounds.right;
            if (lprect->bottom > rcBounds.bottom) lprect->bottom = rcBounds.bottom;

            
            // Draw the current border.
            RBDrawBorder(hdc, lprbi, lprect);
        
            // Bail when the user releases the left mouse button.
            if (msg.message == WM_LBUTTONUP) {
                RBRestoreBorder(hdc, lprbi, lprect);
               break;
            }
        }
        else
            continue;
    }

    ReleaseCapture();
    ReleaseDC(hwnd,hdc);

    RBClose(lprbi);

    return TRUE;
}

/**** GetSelectionArea() ****************************************\ 
*                                                                *
*  This function displays a rubber banding rectangle that        *
*  allows the user to select a rectangle in the client area      *
*  of a window. Called in response to a WM_LBUTTONDOWN.          *
*                                                                *
\****************************************************************/ 
BOOL GetSelectionArea(HWND hWnd, LPRECT lprc)
{
    POINT pt;
    
    // Get the mouse-down point.
    GetCursorPos(&pt);

    // Convert it into client coordinates.
    ScreenToClient(hWnd, &pt);

    // Get a rectangle from the user.
    return RBTrackMouse(hWnd, pt, lprc);    

}

Modification Type:MinorLast Reviewed:4/3/2006
Keywords:kbDSWGDI2003Swept kbGDI kbhowto KB314394 kbAudDeveloper