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;
}