Figure 1   HOOK

Hook.h


 #include "resource.h"
 
 class CApp : public CWinApp {
 public:
    CApp();
    virtual BOOL InitInstance();
    afx_msg void OnAppAbout();
    DECLARE_MESSAGE_MAP()
 };
Hook.cpp

 // 
 #include "StdAfx.h"
 #include "Hook.h"
 #include "MainFrm.h"
 #include "TraceWin.h"
 .
 .
 .
BOOL CApp::InitInstance()
 {
    // Create main frame window (don't use doc/view stuff)
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
       return FALSE;
    pMainFrame->ShowWindow(m_nCmdShow);
    pMainFrame->UpdateWindow();
    m_pMainWnd = pMainFrame;
    return TRUE;
 }
MainFrm.h

 #include "MsgHook.h"
 
 //////////////////
 // Msg hook to spy on mouse messages
 //
 class CKbdMsgHook : public CMsgHook {
    DECLARE_DYNAMIC(CKbdMsgHook);
    virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp); 
 };
 
 //////////////////
 // Standard main frame
 //
 class CMainFrame : public CFrameWnd {
 public:
    CMouseMsgHook  m_mouseMsgHook;   // mouse message hook
    CKbdMsgHook    m_kbdMsgHook;     // keyboard message hook
    CAllMsgHook    m_allMsgHook;     // all message hook
    CMainFrame();
    virtual ~CMainFrame();
 protected:
    DECLARE_DYNAMIC(CMainFrame)
    CStatusBar  m_wndStatusBar;
    CToolBar    m_wndToolBar;
    DECLARE_MESSAGE_MAP()
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnHookKbd();
    afx_msg void OnUpdateHookKbd(CCmdUI* pCmdUI);
     ...
 };
MainFrm.cpp

 // Note: edited to show only KBD hook
 #include "StdAfx.h"
 #include "Hook.h"
 #include "MainFrm.h"
 #include "Debug.h"
 
 IMPLEMENT_DYNAMIC(CMainFrame, CFrameWnd)
 BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
 •
 •
 •
   ON_COMMAND(ID_HOOK_KBD,    OnHookKbd)
    ON_UPDATE_COMMAND_UI(ID_HOOK_KBD,   OnUpdateHookKbd)
 END_MESSAGE_MAP()
 •
 •
 •
////////////////////////////////////////////////////////////////
 // Command and UI handlers for hook/unhook commands
 //
 void CMainFrame::OnHookKbd() 
 {
    m_kbdMsgHook.HookWindow(m_kbdMsgHook.IsHooked() ? NULL : this);
 }
 void CMainFrame::OnUpdateHookKbd(CCmdUI* pCmdUI) 
 {
    pCmdUI->SetCheck(m_kbdMsgHook.IsHooked());
 }
 
 //////////////////
 // CKbdMsgHook spies on keyboard messages,
 //
 LRESULT CKbdMsgHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
 {
    if (WM_KEYFIRST <= msg && msg <= WM_KEYLAST) {
       TRACE("CKbdMsgHook::%s\n", DbgName(msg));
    }
    return CMsgHook::WindowProc(msg, wp, lp); // Important!!
 }
 IMPLEMENT_DYNAMIC(CKbdMsgHook, CMsgHook);
MsgHook.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // CMsgHook is a generic class for hooking another window's messages.
 
 #include "StdAfx.h"
 #include "MsgHook.h"
 #include "Debug.h"
 
 //////////////////
 // The message hook map is derived from CMapPtrToPtr, which associates
 // a pointer with another pointer. It maps an HWND to a CMsgHook, like
 // the way MFC's internal maps map HWND's to CWnd's. The first hook
 // attached to a window is stored in the map; all other hooks for that
 // window are then chained via CMsgHook::m_pNext.
 //
 class CMsgHookMap : private CMapPtrToPtr {
 public:
    CMsgHookMap();
    ~CMsgHookMap();
    static CMsgHookMap& GetHookMap();
    void Add(HWND hwnd, CMsgHook* pMsgHook);
    void Remove(CMsgHook* pMsgHook);
    void RemoveAll(HWND hwnd);
    CMsgHook* Lookup(HWND hwnd);
 };
 
 // This trick is used so the hook map isn't
 // instantiated until someone actually requests it.
 //
 #define  theHookMap  (CMsgHookMap::GetHookMap())
 
 IMPLEMENT_DYNAMIC(CMsgHook, CWnd);
 
 CMsgHook::CMsgHook()
 {
    m_pNext = NULL;
    m_pOldWndProc = NULL;   
    m_pWndHooked  = NULL;
 }
 
 CMsgHook::~CMsgHook()
 {
    ASSERT(m_pWndHooked==NULL);      // can't destroy while still hooked!
    ASSERT(m_pOldWndProc==NULL);
 }
 
 //////////////////
 // Hook a window.
 // This installs a new window proc that directs messages to the CMsgHook.
 // pWnd=NULL to remove.
 //
 BOOL CMsgHook::HookWindow(CWnd* pWnd)
 {
    if (pWnd) {
       // Hook the window
       ASSERT(m_pWndHooked==NULL);
       TRACE("%s::HookWindow(%s)\n",
          GetRuntimeClass()->m_lpszClassName, DbgName(pWnd));
       HWND hwnd = pWnd->m_hWnd;
       ASSERT(hwnd && ::IsWindow(hwnd));
       theHookMap.Add(hwnd, this);         // Add to map of hooks
 
    } else {
       // Unhook the window
       ASSERT(m_pWndHooked!=NULL);
       TRACE("%s::HookWindow(NULL) [unhook 0x%04x]\n",
          GetRuntimeClass()->m_lpszClassName, m_pWndHooked->GetSafeHwnd());
       theHookMap.Remove(this);            // Remove from map
       m_pOldWndProc = NULL;
    }
    m_pWndHooked = pWnd;
    return TRUE;
 }
 
 //////////////////
 // Window proc-like virtual function which specific CMsgHooks will
 // override to do stuff. Default passes the message to the next hook; 
 // the last hook passes the message to the original window.
 // You MUST call this at the end of your WindowProc if you want the real
 // window to get the message. This is just like CWnd::WindowProc, except that
 // a CMsgHook is not a window.
 //
 LRESULT CMsgHook::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
 {
    ASSERT(m_pOldWndProc);
    return m_pNext ? m_pNext->WindowProc(msg, wp, lp) :   
       ::CallWindowProc(m_pOldWndProc, m_pWndHooked->m_hWnd, msg, wp, lp);
 }
 
 //////////////////
 // Like calling base class WindowProc, but with no args, so individual
 // message handlers can do the default thing. Like CWnd::Default
 //
 LRESULT CMsgHook::Default()
 {
    // MFC stores current MSG in thread state
    MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
    // Note: must explicitly call CMsgHook::WindowProc to avoid infinte
    // recursion on virtual function
    return CMsgHook::WindowProc(curMsg.message, curMsg.wParam, curMsg.lParam);
 }
 
 //////////////////
 // Subclassed window proc for message hooks. Replaces AfxWndProc (or whatever
 // else was there before.)
 //
 LRESULT CALLBACK
 HookWndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
 {
 #ifdef _USRDLL
    // If this is a DLL, need to set up MFC state
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
 #endif
 
    // Set up MFC message state just in case anyone wants it
    // This is just like AfxCallWindowProc, but we can't use that because
    // a CMsgHook is not a CWnd.
    //
    MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
    MSG  oldMsg = curMsg;   // save for nesting
    curMsg.hwnd    = hwnd;
    curMsg.message = msg;
    curMsg.wParam  = wp;
    curMsg.lParam  = lp;
 
    // Get hook object for this window. Get from hook map
    CMsgHook* pMsgHook = theHookMap.Lookup(hwnd);
    ASSERT(pMsgHook);
 
    LRESULT lr;
    if (msg==WM_NCDESTROY) {
       // Window is being destroyed: unhook all hooks (for this window)
       // and pass msg to orginal window proc
       //
       WNDPROC wndproc = pMsgHook->m_pOldWndProc;
       theHookMap.RemoveAll(hwnd);
       lr = ::CallWindowProc(wndproc, hwnd, msg, wp, lp);
 
    } else {
       // pass to msg hook
       lr = pMsgHook->WindowProc(msg, wp, lp);
    }
    curMsg = oldMsg;        // pop state
    return lr;
 }
 
 CMsgHookMap::CMsgHookMap()
 {
 }
 
 CMsgHookMap::~CMsgHookMap()
 {
    ASSERT(IsEmpty());   // all hooks should be removed!  
 }
 
 //////////////////
 // Get the one and only global hook map
 // 
 CMsgHookMap& CMsgHookMap::GetHookMap()
 {
    // By creating theMap here, C++ doesn't instantiate it until/unless
    // it's ever used! This is a good trick to use in C++, to
    // instantiate/initialize a static object the first time it's used.
    //
    static CMsgHookMap theMap;
    return theMap;
 }
 
 /////////////////
 // Add hook to map; i.e., associate hook with window
 //
 void CMsgHookMap::Add(HWND hwnd, CMsgHook* pMsgHook)
 {
    ASSERT(hwnd && ::IsWindow(hwnd));
 
    // Add to front of list
    pMsgHook->m_pNext = Lookup(hwnd);
    SetAt(hwnd, pMsgHook);
    
    if (pMsgHook->m_pNext==NULL) {
       // If this is the first hook added, subclass the window
       pMsgHook->m_pOldWndProc = 
          (WNDPROC)SetWindowLong(hwnd, GWL_WNDPROC, (DWORD)HookWndProc);
 
    } else {
       // just copy wndproc from next hook
       pMsgHook->m_pOldWndProc = pMsgHook->m_pNext->m_pOldWndProc;
    }
    ASSERT(pMsgHook->m_pOldWndProc);
 }
 
 //////////////////
 // Remove hook from map
 //
 void CMsgHookMap::Remove(CMsgHook* pUnHook)
 {
    HWND hwnd = pUnHook->m_pWndHooked->GetSafeHwnd();
    ASSERT(hwnd && ::IsWindow(hwnd));
 
    CMsgHook* pHook = Lookup(hwnd);
    ASSERT(pHook);
    if (pHook==pUnHook) {
       // hook to remove is the one in the hash table: replace w/next
       if (pHook->m_pNext)
          SetAt(hwnd, pHook->m_pNext);
       else {
          // This is the last hook for this window: restore wnd proc
          RemoveKey(hwnd);
          SetWindowLong(hwnd, GWL_WNDPROC, (DWORD)pHook->m_pOldWndProc);
       }
    } else {
       // Hook to remove is in the middle: just remove from linked list
       while (pHook->m_pNext!=pUnHook)
          pHook = pHook->m_pNext;
       ASSERT(pHook && pHook->m_pNext==pUnHook);
       pHook->m_pNext = pUnHook->m_pNext;
    }
 }
 
 //////////////////
 // Remove all the hooks for a window
 //
 void CMsgHookMap::RemoveAll(HWND hwnd)
 {
    CMsgHook* pMsgHook;
    while ((pMsgHook = Lookup(hwnd))!=NULL)
       pMsgHook->HookWindow(NULL);   // (unhook)
 }
 
 /////////////////
 // Find first hook associate with window
 //
 CMsgHook* CMsgHookMap::Lookup(HWND hwnd)
 {
    CMsgHook* pFound = NULL;
    if (!CMapPtrToPtr::Lookup(hwnd, (void*&)pFound))
       return NULL;
    ASSERT_KINDOF(CMsgHook, pFound);
    return pFound;
 }
Debug.h

 #ifdef _DEBUG
 
 //////////////////
 // Implements TRACEFN macro. Don't ever use directly, just use TRACEFN
 //
 class CTraceFn {
 private:
    static int  nIndent; // current indent level
    friend void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...);
 public:
    CTraceFn()  { nIndent++; }
    ~CTraceFn() { nIndent--; }
 };
 
 // NOTE: YOU MUST NOT USE TRACEFN IN A ONE-LINE IF STATEMENT!
 // This will fail:
 //
 // if (foo)
 //    TRACEFN(...)
 //
 // Instead, you must enclose the TRACE in squiggle-brackets
 //
 // if (foo) {
 //    TRACEFN(...)
 // }
 //
 #define TRACEFN CTraceFn __fooble; TRACE
 
 // Goodies to get names of things.
 extern CString sDbgName(CWnd* pWnd); // get name of window
 extern CString sDbgName(UINT uMsg);  // get name of WM_ message
 
 #ifdef REFIID
 
 struct DBGINTERFACENAME {
    const IID* piid;  // ptr to GUID
    LPCSTR name;      // human-readable name of interface
 };
 
 // Change this to whatever interfaces you want to track. Default = none
 extern DBGINTERFACENAME* _pDbgInterfaceNames; 
 extern CString sDbgName(REFIID iid);   // get name of COM interface
 
 #endif // REFIID
 
 #else // Not _DEBUG
 #define sDbgName(x)  CString()
 #define TRACEFN TRACE
 #endif
 
 // Macro casts to LPCTSTR for use with TRACE/printf/CString::Format
 #define DbgName(x) (LPCTSTR)sDbgName(x)
 
Debug.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // General purpose debugging utilities
 // 
 #include "StdAfx.h"
 #include "Debug.h"
 #include <afxpriv.h> // for MFC WM_ messages
 
 #ifdef _DEBUG
 
 int CTraceFn::nIndent=-1;      // current indent level
 
 #define _countof(array) (sizeof(array)/sizeof(array[0]))
 
 ////////////////
 // These functions are copied from dumpout.cpp in the MFC source,
 // with my modification to do indented TRACEing
 //
 void AFXAPI AfxDump(const CObject* pOb)
 {
    afxDump << pOb;
 }
 
 void AFX_CDECL AfxTrace(LPCTSTR lpszFormat, ...)
 {
 #ifdef _DEBUG // all AfxTrace output is controlled by afxTraceEnabled
    if (!afxTraceEnabled)
       return;
 #endif
    va_list args;
    va_start(args, lpszFormat);
    int nBuf;
    TCHAR szBuffer[512];
 
    nBuf = _vstprintf(szBuffer, lpszFormat, args);
    ASSERT(nBuf < _countof(szBuffer));
 
    // PD: Here are my added lines to do the indenting. Search
    // for newlines and insert prefix before each one. Yawn.
    //
    static BOOL bStartNewLine = TRUE;
    char* nextline;
    for (char* start = szBuffer; *start; start=nextline+1) {
       if (bStartNewLine) {
          if ((afxTraceFlags & traceMultiApp) && (AfxGetApp() != NULL))
             afxDump << AfxGetApp()->m_pszExeName << ": ";
          afxDump << CString(' ',CTraceFn::nIndent);
          bStartNewLine = FALSE;
       }
       nextline = strchr(start, '\n');
       if (nextline) {
          *nextline = 0; // terminate string at newline
          bStartNewLine = TRUE;
       }
       afxDump << start;
       if (!nextline)
          break;
       afxDump << "\n"; // the one I terminated
    }
    va_end(args);
 }
 
 //////////////////
 // Get window name in the form classname[HWND,title]
 // Searches all the parents for a window with a title.
 //
 CString sDbgName(CWnd* pWnd)
 {
    CString sTitle;
    HWND hwnd = pWnd->GetSafeHwnd();
    if (hwnd==NULL)
       sTitle = "NULL";
    else if (!::IsWindow(hwnd))
       sTitle = "[bad window]";
    else {
       sTitle = "[no title]";
       for (CWnd* pw = pWnd; pw; pw = pw->GetParent()) {
          if (pw->GetWindowTextLength() > 0) {
             pw->GetWindowText(sTitle);
             break;
          }
       }
    }
    CString s;
    s.Format("%s[0x%04x,\"%s\"]", 
       pWnd ? pWnd->GetRuntimeClass()->m_lpszClassName : "NULL",
       hwnd, (LPCTSTR)sTitle);
    return s;
 }
 
 struct {
    UINT     msg;
    LPCTSTR  name;
 } MsgData[] = {
    { WM_CREATE,_T("WM_CREATE") },
    { WM_DESTROY,_T("WM_DESTROY") },
    .
    . // (about 220 messages)
    .
    { WM_QUEUE_SENTINEL,_T("*WM_QUEUE_SENTINEL") },
    { 0,NULL }
 };
 
 ////////////////
 // This class is basically just an array of 1024 strings,
 // the names of each WM_ message. Constructor initializes it.
 //
 
 class CWndMsgMap {
    static LPCTSTR Names[];          // array of WM_ message names
 public:
    CWndMsgMap();                    // constructor initializes them
    CString GetMsgName(UINT msg);    // get name of message
 };
 LPCTSTR CWndMsgMap::Names[WM_USER]; // name of each WM_ message
 
 
 
 //////////////////
 // Initialize array from sparse data
 //
 CWndMsgMap::CWndMsgMap()
 {
    // copy sparse MsgData into table
    memset(Names, 0, sizeof(Names));
    for (int i=0; MsgData[i].msg; i++)     
       Names[MsgData[i].msg] = MsgData[i].name;
 }
 
 ////////////////
 // Get the name of a WM_ message
 //
 CString CWndMsgMap::GetMsgName(UINT msg)
 {
    CString name;
    if (msg>=WM_USER)
       name.Format("WM_USER+%d", msg-WM_USER);
    else if (Names[msg])
       name = Names[msg];
    else
       name.Format("0x%04x", msg);
    return name;
 }
 
 //////////////////
 // Get name of WM_ message.
 //
 CString sDbgName(UINT uMsg)
 {
    static CWndMsgMap wndMsgMap; // instantiate 1st time called
    return wndMsgMap.GetMsgName(uMsg);
 }
 
 #endif // DEBUG

Figure 5   DIBVIEW

DibView.cpp


 #include "StdAfx.h"
 #include "DibView.h"
 #include "MainFrm.h"
 #include "Doc.h"
 #include "View.h"
 #include "TraceWin.h"
 
 .
 .
 .

 BOOL CApp::InitInstance()
 {
 #ifdef _MDI
    AddDocTemplate(new CMultiDocTemplate(IDR_MYDOCTYPE,
       RUNTIME_CLASS(CDIBDoc),
       RUNTIME_CLASS(CMDIChildWnd),
       RUNTIME_CLASS(CDIBView)));
 #else
    AddDocTemplate(new CSingleDocTemplate(IDR_MAINFRAME,
       RUNTIME_CLASS(CDIBDoc),
       RUNTIME_CLASS(CMainFrame),
       RUNTIME_CLASS(CDIBView)));
 #endif
 
    CCommandLineInfo cmdInfo;
    ParseCommandLine(cmdInfo);
 
 #ifdef _MDI
    // create main MDI Frame window
    CMainFrame* pMainFrame = new CMainFrame;
    if (!pMainFrame->LoadFrame(IDR_MAINFRAME))
       return FALSE;
    m_pMainWnd = pMainFrame;
 
    // Parse command line. Since this is a read-only viewer,
    // don't allow FileNew
    if (cmdInfo.m_nShellCommand!=CCommandLineInfo::FileNew && 
       !ProcessShellCommand(cmdInfo))
       return FALSE;
 
    // The main window has been initialized, so show and update it.
    pMainFrame->ShowWindow(m_nCmdShow);
    pMainFrame->UpdateWindow();
 
 #else // SDI app
    if (!ProcessShellCommand(cmdInfo))
       return FALSE;
 #endif
 
    return TRUE;
 }
MainFrm.h

 #include "PalHook.h"
 
 #ifdef _MDI
 #define CBaseFrameWnd CMDIFrameWnd
 #else
 #define CBaseFrameWnd CFrameWnd
 #endif
 
 ////////////////
 // Palette-handling main frame window
 //
 class CMainFrame : public CBaseFrameWnd {
 public:
    CMainFrame();
    virtual ~CMainFrame();
 protected:
    DECLARE_DYNCREATE(CMainFrame)
    CPalMsgHandler m_palMsgHandler;  // handles palette messages
    CStatusBar     m_wndStatusBar;   // status bar
    CToolBar       m_wndToolBar;     // tool (button) bar
 
    DECLARE_MESSAGE_MAP()
    afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
 };
 
MainFrm.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 #include "StdAfx.h"
 #include "DibView.h"
 #include "MainFrm.h"
 
 .
 .
 .

 int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
 {
 •
 •
 •
   DragAcceptFiles(TRUE);
 
    // Install palette handler.
    // Mainframe doesn't draw, only views--so palette is NULL.
    //
    m_palMsgHandler.Install(this, NULL);
 
    return 0;
 }
Doc.h

 #include "dib.h"
 
 //////////////////
 // Document class just holds a DIB
 //
 class CDIBDoc : public CDocument {
 protected:
    DECLARE_DYNCREATE(CDIBDoc)
    CDIBDoc();
    CDib m_dib;    // the DIB
    DECLARE_MESSAGE_MAP()
 public:
    virtual ~CDIBDoc();
    virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);
    virtual void DeleteContents();
    CDib* GetDIB() { return &m_dib; }
 };
Doc.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this program works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 #include "StdAfx.h"
 #include "DibView.h"
 #include "Doc.h"
 
 .
 .
 .

 BOOL CDIBDoc::OnOpenDocument(LPCTSTR lpszPathName)
 {
    DeleteContents();
    return m_dib.Load(lpszPathName);
 }
 
 void CDIBDoc::DeleteContents()
 {
    m_dib.DeleteObject();
 }
View.h

 #include "PalHook.h"
 
 //////////////////
 // DIB view class. A scroll view that draws DIBs and realizes palettes.
 //
 class CDIBView : public CScrollView {
 public:
    DECLARE_DYNCREATE(CDIBView)
    CDIBView();
    virtual ~CDIBView();
 
    CDIBDoc* GetDocument()  { return (CDIBDoc*)m_pDocument; }
    CDib* GetDIB();
    void SetZoom(int iZoom);
 
 protected:
    static CFont g_font; // current display font, global for all views
    CPalMsgHandler m_palMsgHandler;  // handles palette messages
    CRect m_rcDIB;       // bitmap rectangle (zoomed)
    CRect m_rcText;      // text rectangle to format BITMAPINFO
    int   m_iZoom;       // current zoom factor
    BOOL  m_bSized;      // first-time sized or not?
    BOOL  m_bUseDrawDib; // whether to use DrawDib for drawing
 
    void DrawBITMAPHEADER(CDC& dc, CDib* pDIB, CRect& rc, UINT nFormat=0);
    void UpdateScrollSizes();
 
    virtual void OnDraw(CDC* pDC);
    virtual void OnInitialUpdate();
    virtual void OnPrint(CDC* pDC, CPrintInfo* pInfo);
    virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
 
    DECLARE_MESSAGE_MAP()
    afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct);
    afx_msg void OnZoom(UINT nID);
    afx_msg void OnUpdateZoom(CCmdUI* pCmdUI);
    afx_msg void OnUpdatePrint(CCmdUI* pCmdUI);
    afx_msg void OnLButtonDblClk(UINT nFlags, CPoint point);
    afx_msg void OnRButtonDblClk(UINT nFlags, CPoint point);
    afx_msg void OnFontChange(UINT nID);
    afx_msg void OnDither();
    afx_msg void OnUpdateDither(CCmdUI* pCmdUI);
    afx_msg void OnSizeToFit();
 };
View.cpp

 #include "StdAfx.h"
 #include "DibView.h"
 #include "Doc.h"
 #include "View.h"
 #include "Debug.h"
 #include "FontUI.h"
 #include <afxpriv.h>
 
 const TCHAR SETTINGS[] = _T("Settings");
 
 IMPLEMENT_DYNCREATE(CDIBView, CScrollView)
 
 BEGIN_MESSAGE_MAP(CDIBView, CScrollView)
    ON_WM_LBUTTONDBLCLK()
    ON_WM_RBUTTONDBLCLK()
    ON_COMMAND(ID_FILE_PRINT,        OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, OnFilePrint)
    ON_COMMAND(ID_VIEW_DITHER,       OnDither)
    ON_COMMAND(ID_VIEW_SIZE_TO_FIT,  OnSizeToFit)
    ON_COMMAND_RANGE(ID_VIEW_FONT,   ID_VIEW_FONT_BIGGER, OnFontChange)
    ON_COMMAND_RANGE(ID_ZOOM_4TH,    ID_ZOOM_4X,          OnZoom)
    ON_UPDATE_COMMAND_UI(ID_VIEW_DITHER,                  OnUpdateDither)
    ON_UPDATE_COMMAND_UI_RANGE(ID_ZOOM_4TH, ID_ZOOM_4X,   OnUpdateZoom)
    ON_UPDATE_COMMAND_UI(ID_FILE_PRINT,                   OnUpdatePrint)
    ON_UPDATE_COMMAND_UI(ID_FILE_PRINT_DIRECT,            OnUpdatePrint)
 END_MESSAGE_MAP()
 
 CFont CDIBView::g_font;
 
 CDIBView::CDIBView()
 {
    if (!g_font.m_hObject) {
       // Restore font from profile
       if (!CFontUI().GetProfileFont(SETTINGS, "Font", g_font)) {
          // Use 8pt Courier (monospace) default
          g_font.CreatePointFont(100,"Courier");
       }
    }
    m_iZoom=0;
    m_bSized=FALSE;
    m_bUseDrawDib = AfxGetApp()->GetProfileInt(SETTINGS, "Dither", 1);
 }
 
 CDIBView::~CDIBView()
 {
    CFontUI().WriteProfileFont(SETTINGS, "Font", g_font);
    AfxGetApp()->WriteProfileInt(SETTINGS, "Dither", m_bUseDrawDib);
 }
 
 CDib* CDIBView::GetDIB()
 {
    CDIBDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    CDib* pDIB = pDoc->GetDIB();
    ASSERT_VALID(pDIB);
    return pDIB->m_hObject ? pDIB : NULL;
 }
 
 //////////////////
 // Initial update: set scroll sizes.
 //
 void CDIBView::OnInitialUpdate()
 {
    TRACEFN("CDIBView::OnInitialUpdate\n");
    CScrollView::OnInitialUpdate();
 
    // Compute size/position of text rectangle
    UpdateScrollSizes();
 
    CDib* pDIB = GetDIB();
    if (pDIB) {
       if (!m_palMsgHandler.IsHooked())
          m_palMsgHandler.Install(this, pDIB->GetPalette());
 
       // The following line is required because MFC does not send
       // WM_INITIALUPDATE through normal channels. Only realize in
       // foreground if I have the focus (could be updating all views
       // from OnFontChange)
       //
       m_palMsgHandler.DoRealizePalette(m_hWnd==::GetFocus());
       
       if (!m_bSized) {
          // size window perfectly around frame
          GetParentFrame()->RecalcLayout();
          OnSizeToFit();
          m_bSized=TRUE;
       }
    }
 }
 
 //////////////////
 // Compute scroll sizes based on new font/image
 //
 void CDIBView::UpdateScrollSizes()
 {
    m_rcDIB.SetRectEmpty();
    m_rcText.SetRectEmpty();
 
    CDib* pDIB = GetDIB();
    if (!pDIB) {
       SetScrollSizes(MM_TEXT, CSize(100,100));
       return;
    }
 
    // Adjust DIB rectangle by current zoom factor
    m_rcDIB = CRect(CPoint(0,0), pDIB->GetSize());
    if (m_iZoom>0) {
       m_rcDIB.right  <<= m_iZoom;
       m_rcDIB.bottom <<= m_iZoom;
    } else if (m_iZoom<0) {
       m_rcDIB.right  >>= -m_iZoom;
       m_rcDIB.bottom >>= -m_iZoom;     
    }
 
    // Compute text rectangle
    CClientDC dc(this);
    int w = m_rcDIB.Width();
    int h = m_rcDIB.Height();
    m_rcText.SetRect(0, h, w, h);
    DrawBITMAPHEADER(dc, pDIB, m_rcText, DT_CALCRECT);
 
    // Total width is max of bitmap, text
    if (w > m_rcText.right)
       m_rcText.right = w;
 
    SetScrollSizes(MM_TEXT, CSize(m_rcText.right, m_rcText.bottom));
 }
 
 //////////////////
 // Draw the bitmap. Be careful to specify foreground/background.
 // Following bitmap, display fields in BITMAPINFOHEADER
 //
 void CDIBView::OnDraw(CDC* pDC)
 {
    CDib* pDIB = GetDIB();
    if (pDIB) {
       pDIB->Draw(*pDC, &m_rcDIB, NULL, m_bUseDrawDib);
       DrawBITMAPHEADER(*pDC, pDIB, m_rcText);
    }
 }
 
 //////////////////
 // Helper fn to draw the formatted BITMAPINFOHEADER text.
 // Because Windows is so brain damaged, this requires manually
 // outputting each line with TabbedTextOut. DrawText is what I would ideally
 // use here, but Windows doesn't let you use DT_CALCRECT and DT_TABSTOP at
 // the same time. I need both, because I'm setting a custom tab stop to make
 // my text align, but also need to calculate the size of the text in advance
 // so I can set my scroll ranges.
 //
 void CDIBView::DrawBITMAPHEADER(CDC& dc, CDib* pDIB,
    CRect& rc, UINT nFormat)
 {
    DIBSECTION ds;
    VERIFY(pDIB->GetObject(sizeof(ds), &ds)==sizeof(ds));
    const BITMAPINFOHEADER& bmi = ds.dsBmih;
 
    // Format BITMAPINFOHEADER into a string
    CString sCompression="none";
    if (bmi.biCompression)
       sCompression.Format("0x%04x",bmi.biCompression);
    CString buf, text = "BITMAPINFOHEADER:\n";
    buf.Format(" biSize\t= %ld\n",         bmi.biSize);         text += buf;
    buf.Format(" biWidth\t= %ld\n",        bmi.biWidth);        text += buf;
    buf.Format(" biHeight\t= %ld\n",       bmi.biHeight);       text += buf;
    buf.Format(" biPlanes\t= %d\n",        bmi.biPlanes);       text += buf;
    buf.Format(" biBitCount\t= %d\n",      bmi.biBitCount);     text += buf;
    buf.Format(" biCompression\t= %s\n",   sCompression);       text += buf;
    buf.Format(" biSizeImage\t= %ld\n",    bmi.biSizeImage);    text += buf;
    buf.Format(" biXPelsPerMeter\t= %ld\n",bmi.biXPelsPerMeter);text += buf;
    buf.Format(" biYPelsPerMeter\t= %ld\n",bmi.biYPelsPerMeter);text += buf;
    buf.Format(" biClrUsed\t= %ld\n",      bmi.biClrUsed);      text += buf;
    buf.Format(" biClrImportant\t= %ld",   bmi.biClrImportant); text += buf;
 
    // Now draw it using current font
    CFont *pOldFont = dc.SelectObject(&g_font);  // select my font
    DRAWTEXTPARAMS dtp;
    memset(&dtp,0,sizeof(dtp));
    dtp.cbSize = sizeof(dtp); // size of struct
    dtp.iTabLength = 20;      // avg 20 chars per tab
    DrawTextEx(dc, (TCHAR*)(LPCTSTR)text, -1, &rc,
       nFormat|DT_EXPANDTABS|DT_TABSTOP, &dtp);
    dc.SelectObject(pOldFont);
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Standard printing stuff
 //
 BOOL CDIBView::OnPreparePrinting(CPrintInfo* pInfo)
 {
    PRINTDLG& pd = pInfo->m_pPD->m_pd;
    pd.Flags |= PD_NOPAGENUMS;
    if (!DoPreparePrinting(pInfo))
       return FALSE;
    if (!GetDeviceCaps(pd.hDC, RASTERCAPS) & RC_BITBLT) {
       MessageBox("Sorry, this printer does not support bitmaps.",
          "DIBVIEW",MB_OK);
       return FALSE;
    }
    return TRUE;
 }
 
 //////////////////
 // Print the bitmap. This would be the same as drawing it, but must scale
 // the size to be true WYSIWYG
 //
 void CDIBView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
 {
    CDib* pDIB = GetDIB();
    if (!pDIB)
       return;
 
    // save stuff
    CRect rcDIB  = m_rcDIB;
    CRect rcText = m_rcText;
 
    // Convert display resolution to printer resolution
    CClientDC dcScreen(this);
    CDC& dcPrinter = *pDC;
    CSize sz = pDIB->GetSize();
    int w = MulDiv(sz.cx, dcPrinter.GetDeviceCaps(LOGPIXELSX),
       dcScreen.GetDeviceCaps(LOGPIXELSX));
    int h = MulDiv(sz.cy, dcPrinter.GetDeviceCaps(LOGPIXELSY),
       dcScreen.GetDeviceCaps(LOGPIXELSY));
 
    // Compute new image, text rectangles
    m_rcDIB.SetRect(0,0,w,h);            // use new rectangle
    m_rcText.SetRect(0, h, w, h);        // approx
 
    // Convert font to printer device units
    CFontUI fui;
    int pts = fui.GetFontPointSize(g_font, dcScreen);
    fui.SetFontPointSize(g_font, dcPrinter, pts);
    DrawBITMAPHEADER(dcPrinter, pDIB, m_rcText, DT_CALCRECT);
 
    // Draw with new rects and font
    OnDraw(pDC);
 
    // Restore everthing to original
    fui.SetFontPointSize(g_font, dcScreen, pts);
    m_rcDIB  = rcDIB;
    m_rcText = rcText;
 }
 
 void CDIBView::OnUpdatePrint(CCmdUI* pCmdUI)
 {
    // Only enable printing if I have a DIB (for SDI app)
    pCmdUI->Enable(GetDIB()!=NULL);
 }
 
 //////////////////
 // Dithering command
 //
 void CDIBView::OnDither()
 {
    m_bUseDrawDib = ! m_bUseDrawDib;
    Invalidate();
 }
 
 void CDIBView::OnUpdateDither(CCmdUI* pCmdUI)
 {
    pCmdUI->SetCheck(m_bUseDrawDib);
 }
 
 //////////////////
 // Double-clicking zooms the image: left button=bigger; right=smaller.
 // Double-clicking in the text area invokes Font dialog.
 //
 void CDIBView::OnLButtonDblClk(UINT nFlags, CPoint pt)
 {
    pt += GetScrollPosition();
    if (m_rcDIB.PtInRect(pt))
       SetZoom(m_iZoom + 1);
    else if (m_rcText.PtInRect(pt))
       OnFontChange(ID_VIEW_FONT);
 }
 
 void CDIBView::OnRButtonDblClk(UINT nFlags, CPoint pt)
 {
    pt += GetScrollPosition();
    if (m_rcDIB.PtInRect(pt))
       SetZoom(m_iZoom - 1);
    else if (m_rcText.PtInRect(pt))
       OnFontChange(ID_VIEW_FONT);
 }
 
 //////////////////
 // Zoom (magnify) command
 //
 void CDIBView::OnZoom(UINT nID)
 {
    SetZoom(m_iZoom = nID-ID_ZOOM_NORMAL);
 }
 void CDIBView::OnUpdateZoom(CCmdUI* pCmdUI)
 {
    pCmdUI->SetCheck(((int)pCmdUI->m_nID)==ID_ZOOM_NORMAL+m_iZoom);
 }
 
 void CDIBView::SetZoom(int iZoom)
 {
    if (-2<=iZoom && iZoom<=2) {
       m_iZoom = iZoom;
       UpdateScrollSizes();
       Invalidate();
    }
 }
 
 //////////////////
 // Szie To Fit command
 //
 void CDIBView::OnSizeToFit()
 {
    ResizeParentToFit(FALSE);
 
    // MFC might have grown the window off the screen--I'll fix it
    CRect rc, rcMax;
    CFrameWnd* pFrame = GetParentFrame();
    ASSERT_VALID(pFrame);
    pFrame->GetWindowRect(&rc);
    CWnd* pGrandParent = pFrame->GetParent();
    if (pGrandParent) {
       // use top level window as maximum rectangle
       pGrandParent->ScreenToClient(&rc);
       pGrandParent->GetClientRect(&rcMax);
    } else {
       // use whole screen as maximum rectangle
       rcMax.SetRect(0,0,GetSystemMetrics(SM_CXSCREEN),
          GetSystemMetrics(SM_CYSCREEN));
    }
    BOOL bTooBig = FALSE;
    if (rc.bottom > rcMax.bottom) {
       rc.bottom = rcMax.bottom;
       rc.right += GetSystemMetrics(SM_CXVSCROLL) +
          GetSystemMetrics(SM_CXBORDER);
       bTooBig = TRUE;
    }
    if (rc.right > rcMax.right) {
       rc.right = rcMax.right;
       bTooBig = TRUE;
    }
    if (bTooBig) {
       pFrame->SetWindowPos(NULL, 0, 0, rc.Width(), rc.Height(),
          SWP_NOMOVE|SWP_NOZORDER|SWP_NOACTIVATE);
    }
 }
 
 ////////////////////////////////////////////////////////////////
 // Handle font change (bigger/smaller/dialog)
 //
 void CDIBView::OnFontChange(UINT nID)
 {
    CFontUI fui;
    if (fui.OnChangeFont(g_font,
       nID==ID_VIEW_FONT_BIGGER ? 1 : nID==ID_VIEW_FONT_SMALLER ? -1 : 0,
       this,
       CF_SCREENFONTS|CF_FORCEFONTEXIST)) {
 
       // For all views to recompute scroll sizes and repaint
       GetTopLevelFrame()->SendMessageToDescendants(WM_INITIALUPDATE);
    }
 }
Dib.h

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this code works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 // See dib.cpp
 
 // global functions for ordinary CBitmap too
 //
 extern CSize GetBitmapSize(CBitmap* pBitmap);
 extern BOOL  DrawBitmap(CDC& dc, CBitmap* pBitmap,
    const CRect* rcDst=NULL, const CRect* rcSrc=NULL);
 
 ////////////////
 // CDib implements Device Independent Bitmaps as a form of CBitmap. 
 //
 class CDib : public CBitmap {
 protected:
    DECLARE_DYNAMIC(CDib)
    BITMAP   m_bm;    // stored for speed
    CPalette m_pal;   // palette
    HDRAWDIB m_hdd;   // for DrawDib
 
 public:
    CDib();
    ~CDib();
 
    CSize GetSize() { return CSize(m_bm.bmWidth, m_bm.bmHeight); }
    BOOL Attach(HGDIOBJ hbm);
    BOOL Load(LPCTSTR szPathName);
    BOOL Load(HINSTANCE hInst, LPCTSTR lpResourceName);
    BOOL Load(HINSTANCE hInst, UINT uID)
       { return Load(hInst, MAKEINTRESOURCE(uID)); }
 
    // Universal Draw function can use DrawDib or not.
    BOOL Draw(CDC& dc, const CRect* rcDst=NULL, const CRect* rcSrc=NULL,
       BOOL bUseDrawDib=TRUE, CPalette* pPal=NULL, BOOL bForeground=FALSE);
 
    BOOL DeleteObject();
    BOOL CreatePalette(CPalette& pal);
    CPalette* GetPalette()  { return &m_pal; }
 
    UINT GetColorTable(RGBQUAD* colorTab, UINT nColors);
 };
Dib.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 //
 // CDib - Device Independent Bitmap.
 // This implementation draws bitmaps using normal Win32 API functions,
 // not DrawDib. CDib is derived from CBitmap, so you can use it with
 // any other MFC functions that use bitmaps.
 //
 #include "StdAfx.h"
 #include "Dib.h"
 
 #ifdef _DEBUG
 #define new DEBUG_NEW
 #undef THIS_FILE
 static char THIS_FILE[] = __FILE__;
 #endif
 
 const  int     MAXPALCOLORS = 256;
 
 IMPLEMENT_DYNAMIC(CDib, CObject)
 
 CDib::CDib()
 {
    memset(&m_bm, 0, sizeof(m_bm));
    m_hdd = NULL;
 }
 
 CDib::~CDib()
 {
    DeleteObject();
 }
 
 //////////////////
 // Delete Object. Delete DIB and palette.
 //
 BOOL CDib::DeleteObject()
 {
    m_pal.DeleteObject();
    if (m_hdd) {
       DrawDibClose(m_hdd);
       m_hdd = NULL;
    }
    memset(&m_bm, 0, sizeof(m_bm));
    return CBitmap::DeleteObject();
 }
 
 //////////////////
 // Read DIB from file.
 //
 BOOL CDib::Load(LPCTSTR lpszPathName)
 {
    return Attach(::LoadImage(NULL, lpszPathName, IMAGE_BITMAP, 0, 0,
       LR_LOADFROMFILE | LR_CREATEDIBSECTION | LR_DEFAULTSIZE));
 }
 
 //////////////////
 // Load bitmap resource. Never tested.
 //
 BOOL CDib::Load(HINSTANCE hInst, LPCTSTR lpResourceName)
 {
    return Attach(::LoadImage(hInst, lpResourceName, IMAGE_BITMAP, 0, 0,
       LR_CREATEDIBSECTION | LR_DEFAULTSIZE));
 }
 
 //////////////////
 // Attach is just like the CGdiObject version,
 // except it also creates the palette
 //
 BOOL CDib::Attach(HGDIOBJ hbm)
 {
    if (CBitmap::Attach(hbm)) {
       if (!GetBitmap(&m_bm))        // load BITMAP for speed
          return FALSE;
       m_pal.DeleteObject();         // in case one is already there
       return CreatePalette(m_pal);  // create palette
    }
    return FALSE;  
 }
 
 //////////////////
 // Get size (width, height) of bitmap.
 // extern fn works for ordinary CBitmap objects.
 //
 CSize GetBitmapSize(CBitmap* pBitmap)
 {
    BITMAP bm;
    return pBitmap->GetBitmap(&bm) ?
       CSize(bm.bmWidth, bm.bmHeight) : CSize(0,0);
 }
 
 //////////////////
 // You can use this static function to draw ordinary
 // CBitmaps as well as CDibs
 //
 BOOL DrawBitmap(CDC& dc, CBitmap* pBitmap,
    const CRect* rcDst, const CRect* rcSrc)
 {
    // Compute rectangles where NULL specified
    CRect rc;
    if (!rcSrc) {
       // if no source rect, use whole bitmap
       rc = CRect(CPoint(0,0), GetBitmapSize(pBitmap));
       rcSrc = &rc;
    }
    if (!rcDst) {
       // if no destination rect, use source
       rcDst=rcSrc;
    }
 
    // Create memory DC
    CDC memdc;
    memdc.CreateCompatibleDC(&dc);
    CBitmap* pOldBm = memdc.SelectObject(pBitmap);
 
    // Blast bits from memory DC to target DC.
    // Use StretchBlt if size is different.
    //
    BOOL bRet = FALSE;
    if (rcDst->Size()==rcSrc->Size()) {
       bRet = dc.BitBlt(rcDst->left, rcDst->top, 
          rcDst->Width(), rcDst->Height(),
          &memdc, rcSrc->left, rcSrc->top, SRCCOPY);
    } else {
       dc.SetStretchBltMode(COLORONCOLOR);
       bRet = dc.StretchBlt(rcDst->left, rcDst->top, rcDst->Width(),
          rcDst->Height(), &memdc, rcSrc->left, rcSrc->top, rcSrc->Width(),
          rcSrc->Height(), SRCCOPY);
    }
    memdc.SelectObject(pOldBm);
 
    return bRet;
 }
 
 ////////////////////////////////////////////////////////////////
 // Draw DIB on caller's DC. Does stretching from source to destination
 // rectangles. Generally, you can let the following default to zero/NULL:
 //
 //    bUseDrawDib = whether to use use DrawDib, default TRUE
 //    pPal        = palette, default=NULL, (use DIB's palette)
 //    bForeground = realize in foreground (default FALSE)
 //
 // If you are handling palette messages, you should use bForeground=FALSE,
 // since you will realize the foreground palette in WM_QUERYNEWPALETTE.
 //
 BOOL CDib::Draw(CDC& dc, const CRect* rcDst, const CRect* rcSrc,
    BOOL bUseDrawDib, CPalette* pPal, BOOL bForeground)
 {
    if (!m_hObject)
       return FALSE;
 
    // Select, realize palette
    if (pPal==NULL)            // no palette specified:
       pPal = GetPalette();    // use default
    CPalette* pOldPal = dc.SelectPalette(pPal, !bForeground);
    dc.RealizePalette();
 
    BOOL bRet = FALSE;
    if (bUseDrawDib) {
       // Compute rectangles where NULL specified
       //
       CRect rc(0,0,-1,-1); // default for DrawDibDraw
       if (!rcSrc)
          rcSrc = &rc;
       if (!rcDst)
          rcDst=rcSrc;
       if (!m_hdd)
          VERIFY(m_hdd = DrawDibOpen());
 
       // Get BITMAPINFOHEADER/color table. I copy into stack object each time.
       // This doesn't seem to slow things down visibly.
       //
       DIBSECTION ds;
       VERIFY(GetObject(sizeof(ds), &ds)==sizeof(ds));
       char buf[sizeof(BITMAPINFOHEADER) + MAXPALCOLORS*sizeof(RGBQUAD)];
       BITMAPINFOHEADER& bmih = *(BITMAPINFOHEADER*)buf;
       RGBQUAD* colors = (RGBQUAD*)(&bmih+1);
       memcpy(&bmih, &ds.dsBmih, sizeof(bmih));
       GetColorTable(colors, MAXPALCOLORS);
 
       // Let DrawDib do the work!
       bRet = DrawDibDraw(m_hdd, dc,
          rcDst->left, rcDst->top, rcDst->Width(), rcDst->Height(),
          &bmih,         // ptr to BITMAPINFOHEADER + colors
          m_bm.bmBits,   // bits in memory
          rcSrc->left, rcSrc->top, rcSrc->Width(), rcSrc->Height(),
          bForeground ? 0 : DDF_BACKGROUNDPAL);
 
    } else {
       // use normal draw function
       bRet = DrawBitmap(dc, this, rcDst, rcSrc);
    }
    if (pOldPal)
       dc.SelectPalette(pOldPal, TRUE);
    return bRet;
 }
 
 #define PALVERSION 0x300   // magic number for LOGPALETTE
 
 //////////////////
 // Create the palette. Use halftone palette for hi-color bitmaps.
 //
 BOOL CDib::CreatePalette(CPalette& pal)
 { 
    // should not already have palette
    ASSERT(pal.m_hObject==NULL);
 
    BOOL bRet = FALSE;
    RGBQUAD* colors = new RGBQUAD[MAXPALCOLORS];
    UINT nColors = GetColorTable(colors, MAXPALCOLORS);
    if (nColors > 0) {
       // Allocate memory for logical palette 
       int len = sizeof(LOGPALETTE) + sizeof(PALETTEENTRY) * nColors;
       LOGPALETTE* pLogPal = (LOGPALETTE*)new char[len];
       if (!pLogPal)
          return NULL;
 
       // set version and number of palette entries
       pLogPal->palVersion = PALVERSION;
       pLogPal->palNumEntries = nColors;
 
       // copy color entries 
       for (UINT i = 0; i < nColors; i++) {
          pLogPal->palPalEntry[i].peRed   = colors[i].rgbRed;
          pLogPal->palPalEntry[i].peGreen = colors[i].rgbGreen;
          pLogPal->palPalEntry[i].peBlue  = colors[i].rgbBlue;
          pLogPal->palPalEntry[i].peFlags = 0;
       }
 
       // create the palette and destroy LOGPAL
       bRet = pal.CreatePalette(pLogPal);
       delete [] (char*)pLogPal;
    } else {
       CWindowDC dcScreen(NULL);
       bRet = pal.CreateHalftonePalette(&dcScreen);
    }
    delete colors;
    return bRet;
 }
 
 //////////////////
 // Helper to get color table. Does all the mem DC voodoo.
 //
 UINT CDib::GetColorTable(RGBQUAD* colorTab, UINT nColors)
 {
    CWindowDC dcScreen(NULL);
    CDC memdc;
    memdc.CreateCompatibleDC(&dcScreen);
    CBitmap* pOldBm = memdc.SelectObject(this);
    nColors = GetDIBColorTable(memdc, 0, nColors, colorTab);
    memdc.SelectObject(pOldBm);
    return nColors;
 }
FontUI.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 //
 // CFontUI handles the user interface for changing font sizes, as well
 // as saving/restoring font info in the application profile.
 //
 #include "StdAfx.h"
 #include "FontUI.h"
 
 CFontUI::CFontUI()
 {
    m_nFontPtSizeMin = 4;
    m_nFontPtSizeMax = 120;
 }
 
 CFontUI::~CFontUI()
 {
 }
 
 //////////////////
 // Get font point size. Convert device units to points.
 // There are 72 points per inch.
 //
 int CFontUI::GetFontPointSize(CFont& font, CDC& dc)
 {
    LOGFONT lf;
    font.GetLogFont(&lf);
    return MulDiv(-lf.lfHeight, 72, dc.GetDeviceCaps(LOGPIXELSY)); 
 }
 
 //////////////////
 // Set font point size. Convert points to device units
 // There are 72 points per inch.
 //
 BOOL CFontUI::SetFontPointSize(CFont& font, CDC& dc, int pts)
 {
    LOGFONT lf;
    font.GetLogFont(&lf);
    lf.lfHeight = MulDiv(-pts, dc.GetDeviceCaps(LOGPIXELSY), 72);
    font.DeleteObject();
    return font.CreateFontIndirect(&lf);   
 }
 
 //////////////////
 // Main UI function.
 //    op = 0  ==> run common font dialog
 //    op < 0  ==> font size smaller
 //    op < 0  ==> font size bigger
 //
 // Returns BOOL, whether changed or not, and CFont has new font.
 //
 BOOL CFontUI::OnChangeFont(CFont& font, int op, CWnd* pWnd, DWORD dwFlags)
 {
    ASSERT(font.m_hObject);
    if (op==0) {
       // Run common font dialog
       LOGFONT logfont;
       font.GetLogFont(&logfont);
       CFontDialog dlg(&logfont, dwFlags, NULL, pWnd);
       dlg.m_cf.nSizeMin = m_nFontPtSizeMin;
       dlg.m_cf.nSizeMax = m_nFontPtSizeMax;
       if (dlg.DoModal() != IDOK)
          return FALSE;
       // Change the font
       font.DeleteObject();
       return font.CreateFontIndirect(&logfont);
 
    }
    // Grow or shrink
    CWindowDC dc(NULL);                   // use screen DC
    int pts = GetFontPointSize(font, dc); // get point size
    pts = GrowFontSize(pts, op);          // grow (or shrink)
    if (pts < m_nFontPtSizeMin  || pts > m_nFontPtSizeMax)
       return FALSE;
    return SetFontPointSize(font, dc, pts);
 }
 
 //////////////////
 // Increment or decrement font point size based on current size.
 // Algorithm:
 // If point size is
 //       <= 12    incr = 1 pts
 //       10..32   incr = 2 pts
 //       32..48   incr = 4 pts
 //       >= 48    incr = 8 pts
 // Derived classes can override this virtual function to change this.
 // Return zero to disallow changing size.
 //
 int CFontUI::GrowFontSize(int ptSize, int dir)
 {
    int incr = ptSize <= 12 ? 1 : ptSize < 32 ? 2 : ptSize < 48 ? 4 : 8;
    ptSize += dir>0 ? incr : -incr;;
    return ptSize;
 }
 
 //////////////////
 // Create font from info in application profile. Reads info in the form
 // facename,ptsize,weight,italic
 //
 BOOL CFontUI::GetProfileFont(LPCTSTR lpszKey, LPCTSTR lpszVal, CFont& font,
    CDC* pDC)
 {
    CWinApp *pApp = AfxGetApp();
    ASSERT_VALID(pApp);
    CString s = pApp->GetProfileString(lpszKey, lpszVal);
    if (s.IsEmpty())
       return FALSE;
 
    LOGFONT lf;
    memset(&lf, 0, sizeof(LOGFONT));
    lf.lfCharSet = DEFAULT_CHARSET;
    int bItalic;
    int iPtSize;
 
    // scanf is overkill, but I'm lazy
    if (sscanf((LPCTSTR)s, "%[a-zA-Z ],%d,%d,%d",
       lf.lfFaceName, &iPtSize, &lf.lfWeight, &bItalic) != 4)
       return FALSE;
    lf.lfHeight = MulDiv(-iPtSize,   // convert ptsize to logical units
       ::GetDeviceCaps(pDC ? pDC->m_hDC : ::GetDC(NULL), LOGPIXELSY), 72);
    lf.lfItalic = bItalic;  // because lf.lfItalic is a BYTE
    font.DeleteObject();    // bye 
    return font.CreateFontIndirect(&lf);
 }
 
 //////////////////
 // Write font to app profile in the form "facename,ptsize,weight,italic"
 //
 BOOL CFontUI::WriteProfileFont(LPCTSTR lpszKey, LPCTSTR lpszVal, CFont& font,
    CDC* pDC)
 {
    CWinApp *pApp = AfxGetApp();
    ASSERT_VALID(pApp);
    LOGFONT lf;
    font.GetLogFont(&lf);
    int iPtSize = MulDiv(-lf.lfHeight, 72,
       ::GetDeviceCaps(pDC ? pDC->m_hDC : ::GetDC(NULL), LOGPIXELSY));
    CString s;
    s.Format("%s,%d,%d,%d", lf.lfFaceName, iPtSize, lf.lfWeight, lf.lfItalic);
    return pApp->WriteProfileString(lpszKey, lpszVal, s);
 }
MsgHook.h

 Same as for HOOK
MsgHook.cpp

 Same as for HOOK
PalHook.h

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 // If this code works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 #ifndef _PALMSGHOOK_H
 #define _PALMSGHOOK_H
 
 #include "MsgHook.h"
 
 //////////////////
 // Generic palette message handler makes handling palette messages easy.
 // To use:
 //
 // * Instaniate a CPalMsgHandler in your main frame and
 //   every CWnd class that needs to realize palettes (e.g., your view).
 // * Call Install to install.
 // * Call DoRealizePalette(TRUE) from your view's OnInitialUpdate fn.
 //
 class CPalMsgHandler : public CMsgHook {
 protected:
    CPalette* m_pPalette; // ptr to palette
 
    DECLARE_DYNAMIC(CPalMsgHandler);
 
    // These are similar to, but NOT the same as the equivalent CWnd fns.
    // Rarely, if ever need to override.
    //
    virtual LRESULT WindowProc(UINT msg, WPARAM wp, LPARAM lp); 
    virtual void OnPaletteChanged(CWnd* pFocusWnd);
    virtual BOOL OnQueryNewPalette();
    virtual void OnSetFocus(CWnd* pOldWnd);
 
    // Override this if you realize your palette some other way
    // (not by having a ptr to a CPalette).
    //
    virtual int  DoRealizePalette(BOOL bForeground);
 
 public:
    CPalMsgHandler();
    ~CPalMsgHandler();
 
    // Get/Set palette obj
    CPalette* GetPalette()           { return m_pPalette; }
    void SetPalette(CPalette* pPal)  { m_pPalette = pPal; }
 
    // Call this to install the palette handler
    BOOL Install(CWnd* pWnd, CPalette* pPal) {
       m_pPalette = pPal;
       return HookWindow(pWnd);
    }
 };
 
 #endif
PalHook.cpp

 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal. 
 //
 #include "StdAfx.h"
 #include "PalHook.h"
 #include "Debug.h"
 
 IMPLEMENT_DYNAMIC(CPalMsgHandler, CMsgHook);
 
 CPalMsgHandler::CPalMsgHandler()
 {
    m_pPalette = NULL;
 }
 
 CPalMsgHandler::~CPalMsgHandler()
 {
 }
 
 //////////////////
 // Message handler handles palette-related messages
 //
 LRESULT CPalMsgHandler::WindowProc(UINT msg, WPARAM wp, LPARAM lp)
 {
    ASSERT_VALID(m_pWndHooked);
 
    switch (msg) {
    case WM_PALETTECHANGED:
       OnPaletteChanged(CWnd::FromHandle((HWND)wp));
       return 0;
    case WM_QUERYNEWPALETTE:
       return OnQueryNewPalette();
    case WM_SETFOCUS:
       OnSetFocus(CWnd::FromHandle((HWND)wp));
       return 0;
    }
    return CMsgHook::WindowProc(msg, wp, lp);
 }
 
 //////////////////
 // Handle WM_PALETTECHANGED
 //
 void CPalMsgHandler::OnPaletteChanged(CWnd* pFocusWnd)
 {
    ASSERT(m_pWndHooked);
    CWnd& wnd = *m_pWndHooked;
    TRACEFN("CPalMsgHandler::OnPaletteChanged for %s [from %s]\n", 
       DbgName(&wnd), DbgName(pFocusWnd));
 
    if (pFocusWnd->GetSafeHwnd() != wnd.m_hWnd) {
       if (DoRealizePalette(FALSE)==0) {
          if (wnd.GetParent()==NULL) {
             // I'm the top-level frame: Broadcast to children
             // (only MFC permanent CWnd's!)
             //
             const MSG& curMsg = AfxGetThreadState()->m_lastSentMsg;
             wnd.SendMessageToDescendants(WM_PALETTECHANGED,
                curMsg.wParam, curMsg.lParam);
          }
       }
    } else {
       // I'm the window that triggered the WM_PALETTECHANGED
       // in the first place: ignore it
       //
       TRACE("[It's me, don't realize palette.]\n");
    }
 }
 
 //////////////////
 // Handle WM_QUERYNEWPALETTE
 //
 BOOL CPalMsgHandler::OnQueryNewPalette()
 {
    ASSERT(m_pWndHooked);
    CWnd& wnd = *m_pWndHooked;
    TRACEFN("CPalMsgHandler::OnQueryNewPalette for %s\n", DbgName(&wnd));
 
    if (DoRealizePalette(TRUE)==0) { // realize in foreground
       // No colors changed: if this is the top-level frame,
       // give active view a chance to realize itself
       //
       if (wnd.GetParent()==NULL) {
          ASSERT_KINDOF(CFrameWnd, &wnd);
          CWnd* pView = ((CFrameWnd&)wnd).GetActiveFrame()->GetActiveView();
          if (pView) 
             pView->SendMessage(WM_QUERYNEWPALETTE);
       }
    }
    return TRUE;
 }
 
 //////////////////
 // Handle WM_SETFOCUS
 //
 void CPalMsgHandler::OnSetFocus(CWnd* pOldWnd) 
 {
    ASSERT(m_pWndHooked);
    CWnd& wnd = *m_pWndHooked;
    TRACEFN("CPalMsgHandler::OnSetFocus for %s\n", DbgName(&wnd));
    wnd.SetForegroundWindow();    // Windows likes this
    DoRealizePalette(TRUE);       // realize in foreground
    Default();                    // let app handle focus message too
 }
 
 /////////////////
 // Function to actually realize the palette.
 // Override this to do different kind of palette realization; e.g.,
 // DrawDib instead of setting the CPalette.
 //
 int CPalMsgHandler::DoRealizePalette(BOOL bForeground)
 {
    if (!m_pPalette || !m_pPalette->m_hObject)
       return 0;
 
    ASSERT(m_pWndHooked);
    CWnd& wnd = *m_pWndHooked;
    TRACEFN("CPalMsgHandler::DoRealizePalette(%s) for %s\n",
       bForeground ? "foreground" : "background", DbgName(&wnd));
 
    CClientDC dc(&wnd);
    CPalette* pOldPal = dc.SelectPalette(m_pPalette, !bForeground);
    int nColorsChanged = dc.RealizePalette();
    if (pOldPal)
       dc.SelectPalette(pOldPal, TRUE);
    if (nColorsChanged > 0)
       wnd.Invalidate(FALSE); // repaint
    TRACE("[%d colors changed]\n", nColorsChanged);
    return nColorsChanged;
 }
Debug.h

 Same as for Debug
Debug.cpp

 Same as for Debug

Figure 12   MFC Goodies Described in the Article

DrawBitmap A function to draw a CBitmap on any device context, possibly with stretching. [Dib.h, Dib.cpp]
CDib A class to display device-independent bitmaps (DIBs). Uses Win32 API and Video for Windows DrawDib for dithering. [Dib.h, Dib.cpp]
TRACEFN A macro for doing intending TRACE diagnostics. [Debug.h, Debug.cpp]
DbgName, LpDbgName Debugging functions to get the name of a window, WM_ message, or COM interface. [Debug.h, Debug.cpp]
CMsgHook A class to do Windows-style subclassing in MFC. Lets you subclass the same window any number of times. [MsgHook.h, MsgHook.cpp]
CPalMsgHandler A CMsgHook that handles palette messages automatically. Can be used in any app. [PalHook.h, PalHook.cpp]
CFontUI A class for managing the user interface to a font. Has easy functions to increment/decrement the point size, run the common font dialog, and read/write a font spec to the application profile.
[FontUI.h, FontUI.cpp]
HOOK A program that demonstrates the use of CMsgHook.
DIBVIEW A program for viewing DIBs. Comes in SDI or MDI flavors and uses all the above goodies.