Figure 3   MSJSuperView Zooming Members

float m_fZoomScale
A float used to store the current zoom level. 1.00 is 100 percent and .50 is 50 percent zoom.
CSize m_szOrigTotalDev
The original device coordinates stored in a CSize data member. This data member is used to calculate the new viewport settings (see OnPrepareDC).
CSize m_szOrigPageDev
Stores the original device coordinates before a zoom for the page scroll amount.
CSize m_szOrigLineDev
Stores the device coordinates before a zoom for the scroll amount of a line.
SetZoomLevel/GetZoomLevel
Accessor member functions that store/retrieve the argument in m_fZoomScale.
ZoomIn
Zooms in on a certain point by a given zoom factor. This function uses the zoom factor to update m_fZoomScale.
ZoomOut
Zooms out from a given point. Updates the m_fZoomScale accordingly.
OnPrepareDC
CScrollView derivative that is the heart of the zooming implementation. This function takes the calculations stored in the data members and updated via UpdateViewport and calls SetWindowExt/SetViewportExt in the current drawing CDC to implement the zooming by manipulating the window extent and viewport extent.
RecalcBars
Used to recalculate the position and size of the scroll bars.
SetScrollSizes
A CScrollView override that initializes the mapping mode and the position of the scroll bars.
CenterOnLogicalPoint
Centers the view on a logical point (useful in zooming to make sure that you don't always zoom into the right corner).
GetLogicalCenterPoint
Calculates the center of the view for zooming. This function is used if a point to zoom in on is not specified.
ViewDPtoLP/ViewLPtoDP
Convenience functions for converting logical and device coordinates using the new mapping modes and viewport.
UpdateViewport
Updates the data members that store the viewport, which will be set in OnPrepareDC.


Figure 4   MSJSuperView Zooming Implementation


 // Declaration of MSJSuperView for Visual Developer Column.
 // Scot Wingo and George Shepherd
 
 class MSJSuperView : public CScrollView
 {
     DECLARE_DYNAMIC(MSJSuperView);
 public:
 // Construction    
 protected:
     MSJSuperView(); // protected constructor used by dynamic creation
     
 // Operations
 public:
     // Overridden CScrollView member functions
 
     void   SetScrollSizes(int nMapMode, SIZE sizeTotal, 
                           const SIZE& sizePage = sizeDefault,
                           const SIZE& sizeLine = sizeDefault);
 
     //For centering.
     CPoint GetLogicalCenterPoint(void);
     void   CenterOnLogicalPoint(CPoint ptCenter);
     
     // Zooming functions
     float GetZoomLevel() {return m_fZoomScale;};
     void  ZoomIn    (CPoint *point = NULL, float delta = 1.25);
     void  ZoomOut   (CPoint *point = NULL, float delta = 1.25);
     
     // Zooming utility functions
     void      ViewDPtoLP (LPPOINT lpPoints, int nCount = 1);
     void      ViewLPtoDP (LPPOINT lpPoints, int nCount = 1);
     void      ClientToDevice(CPoint &point);
 
     //Panning member functions.
     BOOL DoScroll(int nDirection, int nSize);
     void MySetCursor(int nCursor);
 
 #ifdef _DEBUG
     void AssertValid() const;
 #endif //_DEBUG
 
 // Overrideables
 protected:
     // Override this to get zoom scale change notifications.
     virtual void ZoomLevelChanged(void) {};
     virtual void SetZoomLevel(float);
 
 // Implementation
 protected:
     virtual ~MSJSuperView();
 
     virtual void OnDraw(CDC* pDC) = 0; // Pass on Pure Virtual from CScrollView
     virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL);
     
      // Printing support
     virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
 
 protected:
     void      PersistRatio(const CSize &orig, CSize &dest, CPoint &remainder);
     void      ReCalcBars(void);
     void      UpdateViewport(CPoint * ptNewCenter);
     
     // Private member variables
     CSize     m_szOrigTotalDev;  // Original total size in device units
     CSize     m_szOrigPageDev;   // Original page scroll size in device units
     CSize     m_szOrigLineDev;   // Original line scroll size in device units
     float     m_fZoomScale;      // Current ratio between device/logical units
 
     // Mouse Wheel members
     BOOL m_bMouseWheelDrag;
     int m_nMouseWheelTimer;
     CPoint m_ptMouseWheelOrg;
     MSJMouseWheelOriginWnd * m_pWndDrag;
     
 public:
     // Generated message map functions
     //{{AFX_MSG(SECZoomView)
     afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint );
     afx_msg void OnMButtonDown(UINT nFlags, CPoint point);
     afx_msg void OnMButtonUp(UINT nFlags, CPoint point);
     afx_msg void OnMouseMove(UINT nFlags, CPoint point);
     afx_msg void OnTimer(UINT nIDEvent);
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP();
 };
 
 void MSJSuperView::SetZoomLevel(float fNewScale)
 {
     m_fZoomScale = fNewScale;
 
 }
 void MSJSuperView::ZoomIn (
     CPoint *point,   // point in logical coordinates
     float  delta)    // scale factor
 {
     SetZoomLevel(m_fZoomScale + delta);
     UpdateViewport(point);
     return;
 } 
 
 //    ZoomOut
 void MSJSuperView::ZoomOut (
     CPoint *point,   // point in logical coordinates
     float  delta)    // scale factor
 {
     // Decrease the zoom scale.
     SetZoomLevel(m_fZoomScale - delta);
     UpdateViewport(point);
     
     return;
 } 
 
 //    CenterOnLogicalPoint
 //    Same as CScrollView::CenterOnPoint, but for log point.
 void MSJSuperView::CenterOnLogicalPoint(CPoint pt)
 {
     ViewLPtoDP(&pt);
     ClientToDevice(pt);
     CScrollView::CenterOnPoint(pt);
 } 
 
 //    GetLogicalCenterPoint
 CPoint MSJSuperView::GetLogicalCenterPoint (void)  
 {
     CPoint pt;
     CRect rect;
 
     GetClientRect(&rect);
     pt.x = (rect.Width()  / 2);
     pt.y = (rect.Height() / 2);
 
     ViewDPtoLP(&pt);
     return pt;
 } 
 
 //    ViewDPtoLP. Same as DPtoLP, but uses Client DC.
 void MSJSuperView::ViewDPtoLP (LPPOINT lpPoints, int nCount)
 {
     CWindowDC dc(this);
     OnPrepareDC(&dc);
     dc.DPtoLP(lpPoints, nCount);
 } 
 
 //    Same as LPtoDP, but uses Client DC
 void MSJSuperView::ViewLPtoDP (LPPOINT lpPoints, int nCount)
 {
     CWindowDC dc(this);
     OnPrepareDC(&dc);
     dc.LPtoDP(lpPoints, nCount);
 } 
 
 //    UpdateViewport
 //    Called after the scale factor has changed, calculates center if needed,
 //    then updates the viewport and updates the scroll bars.
 void MSJSuperView::UpdateViewport(CPoint * ptNewCenter)
 {
     CPoint ptCenter;
 
     if (!ptNewCenter)
         ptCenter = GetLogicalCenterPoint();
     else 
         ptCenter = *ptNewCenter;
     
     // Modify the Viewport extent
     m_totalDev.cx = (int) ((float) m_szOrigTotalDev.cx * m_fZoomScale);
     m_totalDev.cy = (int) ((float) m_szOrigTotalDev.cy * m_fZoomScale);
     ReCalcBars();
     
     // Set the current center point.
     CenterOnLogicalPoint(ptCenter);
     
     // Notify the class that a new zoom scale was done
     ZoomLevelChanged();
     return;
 }
 
 void MSJSuperView::SetScrollSizes (int nMapMode, SIZE sizeTotal,
                                    const SIZE& sizePage /* = sizeDefault */,
                                   const SIZE& sizeLine /* = sizeDefault */) 
 {
 
     // Set up the defaults
     if (sizeTotal.cx == 0 && sizeTotal.cy == 0){
         sizeTotal.cx = 1;
         sizeTotal.cy = 1;
     }
     
     m_nMapMode    = MM_ANISOTROPIC; // mandatory for arbitrary scaling
     m_totalLog    = sizeTotal;
     
     // Setup default Viewport extent to be conversion of Window extent
     // into device units.
     
     //BLOCK for DC
     {
     CWindowDC dc(NULL);
     dc.SetMapMode(m_nMapMode);
     
     // total size
     m_totalDev = m_totalLog;
     dc.LPtoDP((LPPOINT)&m_totalDev);
     } // Release DC here
     
     m_szOrigTotalDev = m_totalDev;
     m_szOrigPageDev  = sizePage;
     m_szOrigLineDev  = sizeLine;
     ReCalcBars();
     
     ZoomLevelChanged(); //Notify app that there's a new zoom level 1.0f.   
 }
 
 //    OnPrepareDC. Does all the work for MSJSuperView.
 
 void MSJSuperView::OnPrepareDC ( CDC* pDC, CPrintInfo* pInfo)
 {
     pDC->SetMapMode(m_nMapMode);
     pDC->SetWindowExt(m_totalLog);  //Set up the logical window
 
     //Now figure out the origin for the viewport, depends on
     //This code is from CSCrollView
     CPoint ptVpOrg;
     pDC->SetViewportExt(m_totalDev); // in device coordinates
     
     // by default shift viewport origin in negative direction of scroll
     ASSERT(pDC->GetWindowOrg() == CPoint(0,0));
     ptVpOrg = -GetDeviceScrollPosition();
     
     // Set the new viewport origin, call CView for printing behavior
     pDC->SetViewportOrg(ptVpOrg);
     CView::OnPrepareDC(pDC, pInfo);
 } 
 
 //    MSJSuperView::ReCalcBars
 //    Since we're changing the viewport around, we'll need to modify the 
 //    scrollbars where CScrollView just sets them up at start of day and scrolls.
 
 void MSJSuperView::ReCalcBars (void)
 {
     {  // BLOCK for DC
       CWindowDC dc(NULL);
       dc.SetMapMode(m_nMapMode);
 
       // Calculate new device units for scrollbar
       // Start with original logical units from SetScrollPos
       m_pageDev = m_szOrigPageDev;
       dc.LPtoDP((LPPOINT)&m_pageDev);
       m_lineDev = m_szOrigLineDev;
       dc.LPtoDP((LPPOINT)&m_lineDev);
    } // Free DC
 
    // Make sure of the range
    if (m_pageDev.cy < 0)  m_pageDev.cy = -m_pageDev.cy;
    if (m_lineDev.cy < 0)  m_lineDev.cy = -m_lineDev.cy;
 
    // If none specified - use one tenth, Just like CScrollView
      
    if (m_pageDev.cx == 0) m_pageDev.cx = m_totalDev.cx / 10;
    if (m_pageDev.cy == 0) m_pageDev.cy = m_totalDev.cy / 10;
    if (m_lineDev.cx == 0) m_lineDev.cx = m_pageDev.cx  / 10;
    if (m_lineDev.cy == 0) m_lineDev.cy = m_pageDev.cy  / 10;
 
    // Now update the scrollbars
    if (m_hWnd != NULL) {
       UpdateBars();
       Invalidate(TRUE); // Zoom scale changed, redraw all
    }
 }


Figure 5   MSJSuperView Panning Implementation


 BOOL MSJSuperView::DoScroll(int nDirection, int nSize)
 
 {
     int xOrig, x, xMax;
     int yOrig, y, yMax;
 
     CScrollBar* pBar;
     DWORD dwStyle = GetStyle();
     //If no scroll bars, don't do anything
 
     pBar = GetScrollBarCtrl(SB_VERT);
     if ((pBar != NULL && !pBar->IsWindowEnabled()) ||
     (pBar == NULL && !(dwStyle & WS_VSCROLL)))
         nSize = 0;
     
     pBar = GetScrollBarCtrl(SB_HORZ);
     if ((pBar != NULL && !pBar->IsWindowEnabled()) ||
     (pBar == NULL && !(dwStyle & WS_HSCROLL)))
         nSize = 0;
     
     // Adjust current x position based on scroll bar constraints.
     xOrig = x = GetScrollPos(SB_HORZ);
     
     xMax = GetScrollLimit(SB_HORZ);
     if (nDirection == MSJ_LEFT || nDirection == MSJ_RIGHT)
     {
         if (nDirection == MSJ_LEFT)
             x -= nSize;
         else 
             x += nSize;
 
         //Sanity checks.
         if (x < 0)
             x = 0;
         else if (x > xMax)
             x = xMax;
     }
     
     // Adjust current y position based on scroll bar constraints.
     yOrig = y = GetScrollPos(SB_VERT);
     yMax = GetScrollLimit(SB_VERT);
    
     if (nDirection == MSJ_UP ||nDirection == MSJ_DOWN)
     {
         if (nDirection == MSJ_UP)
             y -= nSize;
         else
             y += nSize;    
 
         //Sanity checks.
         if (y < 0)
             y = 0;
         else if (y > yMax)
             y = yMax;
     }
 
     // If nothing changed, just return, no work to do.
     if (x == xOrig && y == yOrig)
         return FALSE;
     
     //Now do the scroll and update the scrollbars.
     if (x != xOrig)
         SetScrollPos(SB_HORZ, x);
     if (y != yOrig)
         SetScrollPos(SB_VERT, y);
     ScrollWindow(-(x-xOrig), -(y-yOrig));
     
     UpdateWindow();
 
     return TRUE;
 }


Figure 7   MSJMouseWheelOriginWnd Declaration


 // Declaration and implementation of MSJMouseWheelOriginWnd.
 
 //Predeclaration for global helper function.
 void MSJDrawTransparentBitmap(CDC* pDC, CBitmap* pBitmap, int xStart,
     int yStart, COLORREF cTransparentColor);
 
 //Window class to help us with drawing of cursor.
 class MSJMouseWheelOriginWnd: public CWnd
 {
 public:
     MSJMouseWheelOriginWnd(int nID);
     BOOL CreateWnd(CWnd* pParent);
     CBitmap m_bmOrigin;
     CSize m_size;
 
     // Generated message map functions
 protected:
     //{{AFX_MSG(CGXDragLineWnd)
     afx_msg void OnPaint();
     //}}AFX_MSG
     DECLARE_MESSAGE_MAP()
 };
 
 MSJMouseWheelOriginWnd::MSJMouseWheelOriginWnd(int nID)
 {
 
     m_bmOrigin.LoadBitmap(nID);
 }
 
 BOOL MSJMouseWheelOriginWnd::CreateWnd(CWnd* pParent)
 {
     if (!CreateEx(0, AfxRegisterWndClass(CS_SAVEBITS), NULL, 
                   WS_CLIPSIBLINGS|WS_CHILD, 0, 0, 1, 1, 
                   pParent->GetSafeHwnd(), NULL))
     {
         TRACE0("Failed to create window in CreateWnd\n");
         ASSERT(0);
         return FALSE;
     }
 
     return TRUE;
 }
 
 BEGIN_MESSAGE_MAP(MSJMouseWheelOriginWnd, CWnd)
     //{{AFX_MSG_MAP(MSJMouseWheelOriginWnd)
     ON_WM_PAINT()
     //}}AFX_MSG_MAP
 END_MESSAGE_MAP()
 
 void MSJMouseWheelOriginWnd::OnPaint()
 {
     CPaintDC dc(this); // device context for painting
 
     CRect rect;
     GetClientRect(rect);
 
     // choose bitmap for the header
     CBitmap* pBitmap = &m_bmOrigin;
     CDC* pDC = &dc;
 
     if (pBitmap)
     {
         // Bitmap size
         BITMAP     bm;
         pBitmap->GetObject(sizeof(BITMAP), (LPSTR)&bm);
 
         CPoint     ptSize;
         ptSize.x = bm.bmWidth;        // Get width of bitmap
         ptSize.y = bm.bmHeight;       // Get height of bitmap
         pDC->DPtoLP(&ptSize, 1);      // Convert from device to logical points
 
         // Draw bitmap
         if (rect.Width() >= ptSize.x && rect.Height() >= ptSize.x)
         {
             // must have at least the first bitmap loaded before calling DrawItem
             ASSERT(pBitmap->m_hObject != NULL);     // required
 
             int x = rect.left + max(1, (rect.Width()-ptSize.x)/2);
             int y = rect.top + max(1, (rect.Height()-ptSize.y)/2);
 
             MSJDrawTransparentBitmap(pDC, // The destination DC.
                 pBitmap,                  // The bitmap to be drawn.
                 x,                        // X coordinate.
                 y,                        // Y coordinate.
                 RGB(255,0,0));            // The color for transparent
                                           // pixels (white grey).
 
         }
     }
 }
 
 // MSJDrawTransparentBitmap
 // This function lets you draw transparent bitmaps. The parameter 
 // cTransparentColor specifies the color for transparent pixels. All pixels in 
 // the bitmap which should be transparent should be marked with this color.
 // The function was copied and adapated from knowledge base article Q79212
 // Title: Drawing Transparent Bitmaps
 
 void MSJDrawTransparentBitmap(CDC* pDC, CBitmap* pBitmap, int xStart,
                              int yStart, COLORREF cTransparentColor)
 {
    CBitmap    bmAndBack, bmAndObject, bmAndMem, bmSave;
    CDC        dcMem, dcBack, dcObject, dcTemp, dcSave;
 
    dcTemp.CreateCompatibleDC(pDC);
    dcTemp.SelectObject(pBitmap);          // Select the bitmap
 
 
    BITMAP     bm;
    pBitmap->GetObject(sizeof(BITMAP), (LPSTR)&bm);
 
    CPoint     ptSize;
    ptSize.x = bm.bmWidth;                // Get width of bitmap
    ptSize.y = bm.bmHeight;               // Get height of bitmap
    dcTemp.DPtoLP(&ptSize, 1);            // Convert from device
                                          // to logical points
 
    // Create some DCs to hold temporary data.
    dcBack.CreateCompatibleDC(pDC);
    dcObject.CreateCompatibleDC(pDC);
    dcMem.CreateCompatibleDC(pDC);
    dcSave.CreateCompatibleDC(pDC);
 
    // Create a bitmap for each DC. DCs are required for a number of GDI functions.
 
    // Monochrome DC
    bmAndBack.CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
 
    // Monochrome DC
    bmAndObject.CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
 
    bmAndMem.CreateCompatibleBitmap(pDC, ptSize.x, ptSize.y);
    bmSave.CreateCompatibleBitmap(pDC, ptSize.x, ptSize.y);
 
    // Each DC must select a bitmap object to store pixel data.
    CBitmap* pbmBackOld   = dcBack.SelectObject(&bmAndBack);
    CBitmap* pbmObjectOld = dcObject.SelectObject(&bmAndObject);
    CBitmap* pbmMemOld    = dcMem.SelectObject(&bmAndMem);
    CBitmap* pbmSaveOld   = dcSave.SelectObject(&bmSave);
 
    // Set proper mapping mode.
    dcTemp.SetMapMode(pDC->GetMapMode());
 
    // Save the bitmap sent here, because it will be overwritten.
    dcSave.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCCOPY);
 
    // Set the background color of the source DC to the color
    // contained in the parts of the bitmap that should be transparent
    COLORREF cColor = dcTemp.SetBkColor(cTransparentColor);
 
    // Create the object mask for the bitmap by performing a BitBlt
    // from the source bitmap to a monochrome bitmap.
    dcObject.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCCOPY);
 
    // Set the background color of the source DC back to the original
    // color.
    dcTemp.SetBkColor(cColor);
 
    // Create the inverse of the object mask.
    dcBack.BitBlt(0, 0, ptSize.x, ptSize.y, &dcObject, 0, 0, NOTSRCCOPY);
 
    // Copy the background of the main DC to the destination.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, pDC, xStart, yStart, SRCCOPY);
 
    // Mask out the places where the bitmap will be placed.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, &dcObject, 0, 0, SRCAND);
 
    // Mask out the transparent colored pixels on the bitmap.
    dcTemp.BitBlt(0, 0, ptSize.x, ptSize.y, &dcBack, 0, 0, SRCAND);
 
    // XOR the bitmap with the background on the destination DC.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCPAINT);
 
    // Copy the destination to the screen.
    pDC->BitBlt(xStart, yStart, ptSize.x, ptSize.y, &dcMem, 0, 0, SRCCOPY);
 
    // Place the original bitmap back into the bitmap sent here.
    dcTemp.BitBlt(0, 0, ptSize.x, ptSize.y, &dcSave, 0, 0, SRCCOPY);
 
    // Reset the memory bitmaps.
    dcBack.SelectObject(pbmBackOld);
    dcObject.SelectObject(pbmObjectOld);
    dcMem.SelectObject(pbmMemOld);
    dcSave.SelectObject(pbmSaveOld);
 
    // Memory DCs and Bitmap objects will be deleted automatically
 }


Figure 8   MSJDrawTransparentBitmap Implementation


 // MSJDrawTransparentBitmap
 
 // The function was copied and adapted from Knowledgebase article Q79212
 // Title: Drawing Transparent Bitmaps
 
 void MSJDrawTransparentBitmap(CDC* pDC, CBitmap* pBitmap, int xStart,
                              int yStart, COLORREF cTransparentColor)
 {
    CBitmap    bmAndBack, bmAndObject, bmAndMem, bmSave;
    CDC        dcMem, dcBack, dcObject, dcTemp, dcSave;
 
    dcTemp.CreateCompatibleDC(pDC);
    dcTemp.SelectObject(pBitmap);   // Select the bitmap
 
    BITMAP     bm;
    pBitmap->GetObject(sizeof(BITMAP), (LPSTR)&bm);
 
    CPoint     ptSize;
    ptSize.x = bm.bmWidth;           // Get width of bitmap
    ptSize.y = bm.bmHeight;          // Get height of bitmap
    dcTemp.DPtoLP(&ptSize, 1);       // Convert from device to logical points
 
    // Create some DCs to hold temporary data.
    dcBack.CreateCompatibleDC(pDC);
    dcObject.CreateCompatibleDC(pDC);
    dcMem.CreateCompatibleDC(pDC);
    dcSave.CreateCompatibleDC(pDC);
 
    // Create a bitmap for each DC. DCs are required for a number of GDI functions.
 
    // Monochrome DC
    bmAndBack.CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
 
    // Monochrome DC
    bmAndObject.CreateBitmap(ptSize.x, ptSize.y, 1, 1, NULL);
 
    bmAndMem.CreateCompatibleBitmap(pDC, ptSize.x, ptSize.y);
    bmSave.CreateCompatibleBitmap(pDC, ptSize.x, ptSize.y);
 
    // Each DC must select a bitmap object to store pixel data.
    CBitmap* pbmBackOld   = dcBack.SelectObject(&bmAndBack);
    CBitmap* pbmObjectOld = dcObject.SelectObject(&bmAndObject);
    CBitmap* pbmMemOld    = dcMem.SelectObject(&bmAndMem);
    CBitmap* pbmSaveOld   = dcSave.SelectObject(&bmSave);
 
    // Set proper mapping mode.
    dcTemp.SetMapMode(pDC->GetMapMode());
 
    // Save the bitmap sent here, because it will be overwritten.
    dcSave.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCCOPY);
 
    // Set the background color of the source DC to the color
    // contained in the parts of the bitmap that should be transparent
    COLORREF cColor = dcTemp.SetBkColor(cTransparentColor);
 
    // Create the object mask for the bitmap by performing a BitBlt
    // from the source bitmap to a monochrome bitmap.
    dcObject.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCCOPY);
 
    // Set the background color of the source DC back to the original color.
    dcTemp.SetBkColor(cColor);
 
    // Create the inverse of the object mask.
    dcBack.BitBlt(0, 0, ptSize.x, ptSize.y, &dcObject, 0, 0, NOTSRCCOPY);
 
    // Copy the background of the main DC to the destination.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, pDC, xStart, yStart, SRCCOPY);
 
    // Mask out the places where the bitmap will be placed.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, &dcObject, 0, 0, SRCAND);
 
    // Mask out the transparent colored pixels on the bitmap.
    dcTemp.BitBlt(0, 0, ptSize.x, ptSize.y, &dcBack, 0, 0, SRCAND);
 
    // XOR the bitmap with the background on the destination DC.
    dcMem.BitBlt(0, 0, ptSize.x, ptSize.y, &dcTemp, 0, 0, SRCPAINT);
 
    // Copy the destination to the screen.
    pDC->BitBlt(xStart, yStart, ptSize.x, ptSize.y, &dcMem, 0, 0, SRCCOPY);
 
    // Place the original bitmap back into the bitmap sent here.
    dcTemp.BitBlt(0, 0, ptSize.x, ptSize.y, &dcSave, 0, 0, SRCCOPY);
 
    // Reset the memory bitmaps.
    dcBack.SelectObject(pbmBackOld);
    dcObject.SelectObject(pbmObjectOld);
    dcMem.SelectObject(pbmMemOld);
    dcSave.SelectObject(pbmSaveOld);
 
    // Memory DCs and Bitmap objects will be deleted automatically
 }


Figure 9   OnMButtonDown Implementation


 void MSJSuperView::OnMButtonDown(UINT nFlags, CPoint point) 
 {
     BOOL bCtl = GetKeyState(VK_CONTROL) & 0x8000;
     
     // If the user presses control, don't do anything - verify that 
    // this is a MK_MBUTTON message.
     if (!bCtl && nFlags == MK_MBUTTON)
     {
         GetCursorPos(&point);
         point -= CPoint(15,15);
         SetCursorPos(point.x, point.y);
         ScreenToClient(&point);
         
         m_bMouseWheelDrag = TRUE;
         m_ptMouseWheelOrg = point;
         SetCapture();
         m_nMouseWheelTimer = SetTimer(999, 10, NULL);
 
         m_pWndDrag = new MSJMouseWheelOriginWnd(MSJ_IDB_ORIGIN_ALL);
         m_pWndDrag->CreateWnd(this);
         m_pWndDrag->MoveWindow(CRect(m_ptMouseWheelOrg.x, m_ptMouseWheelOrg.y, 
                                      m_ptMouseWheelOrg.x+30,
                                      m_ptMouseWheelOrg.y+30));
         m_pWndDrag->ShowWindow(SW_SHOW);
 
     }
     else
         CScrollView::OnMButtonDown(nFlags, point);
 }


Figure 10   OnTimer


 void MSJSuperView::OnTimer(UINT nIDEvent) 
 {
     if (nIDEvent == 999 && m_bMouseWheelDrag)
     {
             CPoint ptCur;
             GetCursorPos(&ptCur);
             ScreenToClient(&ptCur);
 
             CPoint pt = ptCur - m_ptMouseWheelOrg;
 
             int direction;
             int nScroll;
 
             if (abs(pt.x) > abs(pt.y))
             {
                     direction = pt.x > 0 ? MSJ_RIGHT : MSJ_LEFT;
                     nScroll = abs(pt.x) /8;
             }
             else
             {
                     direction = pt.y > 0 ? MSJ_DOWN : MSJ_UP;
                     nScroll = abs(pt.y) /8;
             }
 
             if (nScroll > 0)
             {
                     m_pWndDrag->ShowWindow(SW_HIDE);
                     DoScroll(direction, nScroll);
                     m_pWndDrag->MoveWindow(CRect(m_ptMouseWheelOrg.x,
                                                  m_ptMouseWheelOrg.y, 
                                                  m_ptMouseWheelOrg.x+30, 
                                                  m_ptMouseWheelOrg.y+30));
                     m_pWndDrag->ShowWindow(SW_SHOW);
                     m_pWndDrag->UpdateWindow();
             }
 
             if (nScroll == 0 && abs(pt.x) < 5 && abs(pt.y) < 5)
                     direction = -1;
                 
             int nCursor;
             switch (direction)
             {
             case MSJ_RIGHT: nCursor = MSJ_IDC_IMR; break;
             case MSJ_LEFT: nCursor = MSJ_IDC_IML; break;
             case MSJ_UP: nCursor = MSJ_IDC_IMU; break;
             case MSJ_DOWN: nCursor = MSJ_IDC_IMD; break;
             default: nCursor = MSJ_IDC_IMA; break;
             }
         
             MySetCursor(nCursor);
 
     }
     else
             CScrollView::OnTimer(nIDEvent)
 }


Figure 11   OnMButtonUp Implementation


 void MSJSuperView::OnMButtonUp(UINT nFlags, CPoint point) 
 {
     TRACE("MBUTTONUP\n");
     if (m_bMouseWheelDrag)
     {
             delete m_pWndDrag;
             m_bMouseWheelDrag = FALSE;
             ReleaseCapture();
             KillTimer(m_nMouseWheelTimer);
             SetCursor(NULL);
 
             GetCursorPos(&point);
             point += CPoint(15,15);
             SetCursorPos(point.x, point.y);
             ScreenToClient(&point);
     }
     CScrollView::OnMButtonUp(nFlags, point);
 }