Figure 4    CTabCtrlWithDisable

DisabTab.h

////////////////////////////////////////////////////////////////
// 1998 Microsoft Systems Journal. 
// If this code works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
// Compiles with Visual C++ 5.0 on Windows 95
// See DisabTab.cpp
// 
class CTabCtrlWithDisable : public CTabCtrl {
   DECLARE_DYNAMIC(CTabCtrlWithDisable)
public:
   CTabCtrlWithDisable();
   virtual ~CTabCtrlWithDisable();

   // functions you must implement/call
   virtual  BOOL IsTabEnabled(int iTab) = 0;          // you must override
   BOOL     TranslatePropSheetMsg(MSG* pMsg);         // call from prop sheet
   BOOL     SubclassDlgItem(UINT nID, CWnd* pParent); // non-virtual override

   // helpers
   int      NextEnabledTab(int iTab, BOOL bWrap);  // get next enabled tab
   int      PrevEnabledTab(int iTab, BOOL bWrap);  // get prev enabled tab
   BOOL     SetActiveTab(UINT iNewTab);            // set tab (fail if disabled)

protected:
   DECLARE_MESSAGE_MAP()
   afx_msg  void OnSelChanging(NMHDR* pNmh, LRESULT* pRes);

   // MFC overrides
   virtual  BOOL PreTranslateMessage(MSG* pMsg);
   virtual  void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);

   // override to draw text only; eg, colored text or different font
   virtual  void OnDrawText(CDC& dc, CRect rc, CString sText, BOOL bDisabled);
};


 DisabTab.cpp

//////////////////////////////////////////////////////////////// // CTabCtrlWithDisable 1998 Microsoft Systems Journal. // If this program works, it was written by Paul DiLascia. // If not, I don't know who wrote it. // // CTabCtrlWithDisable implements a CTabCtrl with tabs that you can disable. #include "StdAfx.h" #include "DisabTab.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif IMPLEMENT_DYNAMIC(CTabCtrlWithDisable, CTabCtrl) BEGIN_MESSAGE_MAP(CTabCtrlWithDisable, CTabCtrl) ON_NOTIFY_REFLECT(TCN_SELCHANGING, OnSelChanging) END_MESSAGE_MAP() CTabCtrlWithDisable::CTabCtrlWithDisable() { } CTabCtrlWithDisable::~CTabCtrlWithDisable() { } ////////////////// // Subclass the tab control: also make owner-draw // CTabCtrlWithDisable::SubclassDlgItem(UINT nID, CWnd* pParent) { if (!CTabCtrl::SubclassDlgItem(nID, pParent)) return FALSE; ModifyStyle(0, TCS_OWNERDRAWFIXED); // If first tab is disabled, go to next enabled tab if (!IsTabEnabled(0)) { int iTab = NextEnabledTab(0, TRUE); SetActiveTab(iTab); } return TRUE; } ////////////////// // Draw the tab: mimic SysTabControl32, except use gray if tab is disabled // void CTabCtrlWithDisable::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { DRAWITEMSTRUCT& ds = *lpDrawItemStruct; int iItem = ds.itemID; // Get tab item info char text[128]; TCITEM tci; tci.mask = TCIF_TEXT; tci.pszText = text; tci.cchTextMax = sizeof(text); GetItem(iItem, &tci); // use draw item DC CDC dc; dc.Attach(ds.hDC); // calculate text rectangle and color CRect rc = ds.rcItem; rc += CPoint(1,4); // ?? by trial and error // draw the text OnDrawText(dc, rc, text, !IsTabEnabled(iItem)); dc.Detach(); } ////////////////// // Draw tab text. You can override to use different color/font. // void CTabCtrlWithDisable::OnDrawText(CDC& dc, CRect rc, CString sText, BOOL bDisabled) { dc.SetTextColor(GetSysColor(bDisabled ? COLOR_3DHILIGHT : COLOR_BTNTEXT)); dc.DrawText(sText, &rc, DT_CENTER|DT_VCENTER); if (bDisabled) { // disabled: draw again shifted northwest for shadow effect rc += CPoint(-1,-1); dc.SetTextColor(GetSysColor(COLOR_GRAYTEXT)); dc.DrawText(sText, &rc, DT_CENTER|DT_VCENTER); } } ////////////////// // Selection is changing: disallow if tab is disabled // void CTabCtrlWithDisable::OnSelChanging(NMHDR* pnmh, LRESULT* pRes) { TRACE("CTabCtrlWithDisable::OnSelChanging\n"); // Figure out index of new tab we are about to go to, as opposed // to the current one we're at. Believe it or not, Windows doesn't // pass this info // TC_HITTESTINFO htinfo; GetCursorPos(&htinfo.pt); ScreenToClient(&htinfo.pt); int iNewTab = HitTest(&htinfo); if (iNewTab >= 0 && !IsTabEnabled(iNewTab)) *pRes = TRUE; // tab disabled: prevent selection } ////////////////// // Trap arrow-left key to skip disabled tabs. // This is the only way to know where we're coming from--ie from // arrow-left (prev) or arrow-right (next). // BOOL CTabCtrlWithDisable::PreTranslateMessage(MSG* pMsg) { if (pMsg->message == WM_KEYDOWN && (pMsg->wParam == VK_LEFT || pMsg->wParam == VK_RIGHT)) { int iNewTab = (pMsg->wParam == VK_LEFT) ? PrevEnabledTab(GetCurSel(), FALSE) : NextEnabledTab(GetCurSel(), FALSE); if (iNewTab >= 0) SetActiveTab(iNewTab); return TRUE; } return CTabCtrl::PreTranslateMessage(pMsg); } //////////////// // Translate parent property sheet message. Translates Control-Tab and // Control-Shift-Tab keys. These are normally handled by the property // sheet, so you must call this function from your prop sheet's // PreTranslateMessage function. // BOOL CTabCtrlWithDisable::TranslatePropSheetMsg(MSG* pMsg) { WPARAM key = pMsg->wParam; if (pMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_CONTROL) < 0 && (key == VK_TAB || key == VK_PRIOR || key == VK_NEXT)) { int iNewTab = (key==VK_PRIOR || GetAsyncKeyState(VK_SHIFT) < 0) ? PrevEnabledTab(GetCurSel(), TRUE) : NextEnabledTab(GetCurSel(), TRUE); if (iNewTab >= 0) SetActiveTab(iNewTab); return TRUE; } return FALSE; } ////////////////// // Helper to set the active page, when moving backwards (left-arrow and // Control-Shift-Tab). Must simulate Windows messages to tell parent I // am changing the tab; SetCurSel does not do this!! // // In normal operation, this fn will always succeed, because I don't call it // unless I already know IsTabEnabled() = TRUE; but if you call SetActiveTab // with a random value, it could fail. // BOOL CTabCtrlWithDisable::SetActiveTab(UINT iNewTab) { TRACE("CTabCtrlWithDisable::SetActiveTab\n"); // send the parent TCN_SELCHANGING NMHDR nmh; nmh.hwndFrom = m_hWnd; nmh.idFrom = GetDlgCtrlID(); nmh.code = TCN_SELCHANGING; if (GetParent()->SendMessage(WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh) >=0) { // OK to change: set the new tab SetCurSel(iNewTab); // send parent TCN_SELCHANGE nmh.code = TCN_SELCHANGE; GetParent()->SendMessage(WM_NOTIFY, nmh.idFrom, (LPARAM)&nmh); return TRUE; } return FALSE; } ///////////////// // Return the index of the next enabled tab after a given index, or -1 if none // (0 = first tab). // If bWrap is TRUE, wrap from beginning to end; otherwise stop at zero. // int CTabCtrlWithDisable::NextEnabledTab(int iCurrentTab, BOOL bWrap) { int nTabs = GetItemCount(); for (int iTab = iCurrentTab+1; iTab != iCurrentTab; iTab++) { if (iTab >= nTabs) { if (!bWrap) return -1; iTab = 0; } if (IsTabEnabled(iTab)) { return iTab; } } return -1; } ///////////////// // Return the index of the previous enabled tab before a given index, or -1. // (0 = first tab). // If bWrap is TRUE, wrap from beginning to end; otherwise stop at zero. // int CTabCtrlWithDisable::PrevEnabledTab(int iCurrentTab, BOOL bWrap) { for (int iTab = iCurrentTab-1; iTab != iCurrentTab; iTab--) { if (iTab < 0) { if (!bWrap) return -1; iTab = GetItemCount() - 1; } if (IsTabEnabled(iTab)) { return iTab; } } return -1; }


Figure 5     TabApp

TabApp.h

////////////////////////////////////////////////////////////////
// TABAPP Copyright 1998 Microsoft Systems Journal. 
// If this program works, it was written by Paul DiLascia.
// If not, I don't know who wrote it.
//
// See TABAPP.CPP for description of program.
// 
#include "resource.h"

class CApp : public CWinApp {
public:
   CApp();
   virtual BOOL InitInstance();
};


 TabApp.cpp

//////////////////////////////////////////////////////////////// // TabApp Copyright 1998 Microsoft Systems Journal. // If this program works, it was written by Paul DiLascia. // If not, I don't know who wrote it. // // TabApp illustrates how to use CTabCtrlWithDisable to // implement a tab control with disabled tabs. #include "stdafx.h" #include "TabApp.h" #include "DisabTab.h" #include "TraceWin.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////// // My tab control: page 3 is disabled class CMyTabCtrl : public CTabCtrlWithDisable { public: virtual BOOL IsTabEnabled(int iTab) { return iTab!=2; // Only tab 3 is disabled } }; //////////////////////////////////////////////////////////////// // My Property Sheet. Overrides to use CMyTabCtrl // class CMyPropSheet : public CPropertySheet { public: CMyPropSheet() : CPropertySheet("My Property Sheet") { } virtual BOOL OnInitDialog(); virtual BOOL PreTranslateMessage(MSG* pMsg); protected: CMyTabCtrl m_tabCtrl; }; ////////////////// // Initialize propery sheet: subclass the tab control // BOOL CMyPropSheet::OnInitDialog() { BOOL bRet = CPropertySheet::OnInitDialog(); // get HWND of tab control and subclass it HWND hWndTab = (HWND)SendMessage(PSM_GETTABCONTROL); m_tabCtrl.SubclassDlgItem(::GetDlgCtrlID(hWndTab), this); return bRet; } ////////////////// // PreTranslateMessage: call special tab control fn to maybe translate // BOOL CMyPropSheet::PreTranslateMessage(MSG* pMsg) { return m_tabCtrl.TranslatePropSheetMsg(pMsg) ? TRUE : CPropertySheet::PreTranslateMessage(pMsg); } CApp theApp; CApp::CApp() { } BOOL CApp::InitInstance() { // SetRegistryKey(_T("MSJ")); // Save settings in registry "MSJ", not INI file // LoadStdProfileSettings(); // Load standard INI file options (including MRU) CPropertyPage p1(IDD_MYPAGE1); CPropertyPage p2(IDD_MYPAGE2); CPropertyPage p3(IDD_MYPAGE3); CPropertyPage p4(IDD_MYPAGE4); CPropertyPage p5(IDD_MYPAGE5); CMyPropSheet ps; ps.AddPage(&p1); ps.AddPage(&p2); ps.AddPage(&p3); ps.AddPage(&p4); ps.AddPage(&p5); ps.DoModal(); // Since the dialog has been closed, return FALSE to exit the app // return FALSE; }


Figure 7   CstaticLink

StatLink.h


 ////////////////////////////////////////////////////////////////
 // 1997 Microsoft Systems Journal
 // If this code works, it was written by Paul DiLascia.
 // If not, I don't know who wrote it.
 //
 // CStaticLink implements a static control that's a hyperlink
 // to any file on your desktop or web. You can use it in dialog boxes
 // to create hyperlinks to web sites. When clicked, opens the file/URL
 //
 #ifndef _STATLINK_H
 #define _STATLINK_H
 
 #include "HyprLink.h"
 
 class CStaticLink : public CStatic {
 public:
 
    // (same as December 1997 column)
    .
    . 
    . 
   // Cursor used when mouse is on a link--you can set, or
    // it will default to the standard hand with pointing finger.
    // This is global, so it's the same for all links.
    static HCURSOR  g_hCursorLink;
 
 protected:
    // message handlers
    DECLARE_MESSAGE_MAP()
    afx_msg BOOL   OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message);
 };
 
 #endif _STATLINK_H


 StatLink.cpp

//////////////////////////////////////////////////////////////// // 1997 Microsoft Systems Journal // If this code works, it was written by Paul DiLascia. // If not, I don't know who wrote it. // // CStaticLink implements a static control that's a hyperlink // to any file on your desktop or web. You can use it in dialog boxes // to create hyperlinks to web sites. When clicked, opens the file/URL // #include "StdAfx.h" #include "StatLink.h" // (same as December 1997 column) • • • HCURSOR CStaticLink::g_hCursorLink = NULL; IMPLEMENT_DYNAMIC(CStaticLink, CStatic) BEGIN_MESSAGE_MAP(CStaticLink, CStatic) • • • ON_WM_SETCURSOR() END_MESSAGE_MAP() • • • ////////////////// // Set "hand" cursor to cue user that this is a link. If app has not set // g_hCursorLink, then try to get the cursor from winhlp32.exe, // resource 106, which is a pointing finger. This is a bit of a kludge, // but it works. // BOOL CStaticLink::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { if (g_hCursorLink == NULL) { static bTriedOnce = FALSE; if (!bTriedOnce) { CString windir; GetWindowsDirectory(windir.GetBuffer(MAX_PATH), MAX_PATH); windir.ReleaseBuffer(); windir += _T("\\winhlp32.exe"); HMODULE hModule = LoadLibrary(windir); if (hModule) { g_hCursorLink = CopyCursor(::LoadCursor(hModule, MAKEINTRESOURCE(106))); } FreeLibrary(hModule); bTriedOnce = TRUE; } } if (g_hCursorLink) { ::SetCursor(g_hCursorLink); return TRUE; } return FALSE; }