Propertysheets embedded in Dialogs

Environment: MSVC++ 5.00, win95

Many have had a problem with propertysheets embedded in dialog-boxes. The
problem is that the system hangs after the sheet loses the focus and the
window is activated.

Below is some sourcecode for propertysheets and pages:
(this is a part of a big project with dialogeditor, reporteditor)
everything is created on the fly: In my example I have deactivated
the OK and CANCEL (ENTER and ESC-key) for my propertypage-dialogs
(override OnOk and OnCancel).

To use the code all you have to do is the following:

  1. Create a dialog-project
  2. Add the two files (CMyPropertySheet.cpp + *.h) to your project
  3. Create a dialog class (in my example you will find PropPage1 and
    PropPage2)

  4. Add the following lines to you OnInitDialog:
        // Create the CMyPropertysheet
        m_propertySheet.m_Rect.left = 100;    // set the position on the screen
        m_propertySheet.m_Rect.top = 100;
        m_propertySheet.m_Rect.right = 500;
        m_propertySheet.m_Rect.bottom = 300;
        m_propertySheet.m_nPages = -1;        // init this membervariable
        m_propertySheet.m_nActPage = 0;       // init this membervariable
        m_propertySheet.m_nCtrlID = 2000;     // control-id in the dialog
    
        // and create it on the screen
        m_propertySheet.Create (WS_VISIBLE | WS_CHILD | WS_TABSTOP,
            m_propertySheet.m_Rect, this, m_propertySheet.m_nCtrlID);
    
        // Now add the dialogs page per page
        TC_ITEM Item;
        Item.mask = TCIF_TEXT;
        CMyPropertyPage *pPropPage;
    
        int nN;
        int nPages = 2;    // in my example I have two pages
        for (nN = 0; nN < nPages; nN++) {
    
            pPropPage = new (CMyPropertyPage);    // new it
    
            // Create the tab and the dialog
            switch (nN) {
            case 0:    // page number 1 - a small example
                Item.pszText = "Page 1";
                pPropPage->m_strTitle = "&Page1";    // or get the title of the dialog                    
                                                     // whatwever you want
                pPropPage->m_pDialogPage = (CMyPropDialog *) &m_propPage1;
                pPropPage->m_pDialogPage->Create (IDD_PROPPAGE1, &m_propertySheet);
                break;
    
            case 1:    // page number 2 - a small example
                Item.pszText = "Page 2";
                pPropPage->m_strTitle = "&Page2";    // or get the title of the dialog         
                        // whatwever you want
                pPropPage->m_pDialogPage = (CMyPropDialog *) &m_propPage2;
                pPropPage->m_pDialogPage->Create (IDD_PROPPAGE2, &m_propertySheet);
                break;
            }
    
            m_propertySheet.InsertItem (nN, &Item);    // this is for CTabWnd
            pPropPage->m_hLocal = NULL;                // nothing is created on the fly
                                                       // important information on delete!
    
            // add it to the array
            m_propertySheet.m_Dialogs.Add (pPropPage);
            m_propertySheet.m_nPages++;    // one more page
    
    
            // the size of CTabWnd is m_rect
            // the size of the dialog is smaller
            pPropPage->m_Rect.top = 30;     // above there must be enough place for the
                                            // tab-control
            pPropPage->m_Rect.left = 10;    // border of 10 units is good
            pPropPage->m_Rect.bottom = m_propertySheet.m_Rect.bottom -
                        m_propertySheet.m_Rect.top - 10;
            pPropPage->m_Rect.right = m_propertySheet.m_Rect.right -
                        m_propertySheet.m_Rect.left - 10;
    
            // Only the 1. page should be active at startup
            if (nN > 0) {
                pPropPage->m_pDialogPage->SetWindowPos(NULL, pPropPage->m_Rect.left,
                                pPropPage->m_Rect.top, 0, 0,
                                SWP_HIDEWINDOW | SWP_NOSIZE);
            }
            else {
                pPropPage->m_pDialogPage->SetWindowPos(NULL, pPropPage->m_Rect.left,
                                pPropPage->m_Rect.top, 0, 0,
                                SWP_SHOWWINDOW | SWP_NOSIZE);
            }
            // the class will handle the change of the tab-control
            // and synchronize activate-deactivate the dialogpages
        }
    

The source code is as follows.


*************** CMyProperty.h ***************


#define    ID_NEXT_FIELD        1
#define    ID_PREV_FIELD    2

////////////////////////////////////////////////////////////////////////////

// Class to handle the pages
class CMyPropertyPage : public CObject {
public:
    DLGTEMPLATE    m_dlgTemplate;    // Templatestructure
    DLGTEMPLATE    *m_pResource;    // I create my dialogs on the fly
                    // so I need a pointer to the memory
    HLOCAL    m_hLocal;        // Memory-Handle
    CString        m_strTitle;
    CMyPropDialog    *m_pDialogPage;    // pointer to the dialog structur
    CRect        m_Rect;        // size of the dialog on the screen

};

////////////////////////////////////////////////////////////////////////////

// Class to handle the TabCtrl
class CMyPropertySheet : public CTabCtrl
{
// Construction
public:
        CMyPropertySheet();

    CRect   m_Rect;             // Rectangle coordinates
    UINT    m_nCtrlID;          // CTrlID in the dialog
    int             m_nPages;   // number of pages
    int             m_nActPage; // Actual page
    // Array of dialogs
    CObArray    m_Dialogs;

// Attributes
public:

// Operations
public:
    int        SetActivePage (int nPage);
    int        SetActivePage (CMyPropDialog* pPage);
    CMyPropDialog    *GetPage (int nPage);
    CMyPropDialog    *GetActivePage (void);
    int        GetPageCount (void);
    BOOL    DispPage (int    nCommand);        // handling pgup and pgdn


        //{{AFX_VIRTUAL(CMyPropertySheet)
    public:
    virtual BOOL PreTranslateMessage(MSG* pMsg);
    protected:
    virtual void PostNcDestroy();
    //}}AFX_VIRTUAL

// Implementation
public:
        virtual ~CMyPropertySheet();

protected:
        //{{AFX_MSG(CMyPropertySheet)
        afx_msg void OnSelchange(NMHDR* pNMHDR, LRESULT* pResult);
        //}}AFX_MSG

        DECLARE_MESSAGE_MAP()
};

////////////////////////////////////////////////////////////////////////////

// CMyPropDialog the dialog

class CMyPropDialog : public CDialog
{
// Konstruction
public:
    CMyPropDialog(CWnd* pParent = NULL);   // Standardconstruction

// Dialogfields
    //{{AFX_DATA(CMyPropDialog)
    // enum { IDD = _UNKNOWN_RESOURCE_ID_ }; I create my  dialogs on the fly

    //}}AFX_DATA


    //{{AFX_VIRTUAL(CMyPropDialog)
    public:
    virtual BOOL PreTranslateMessage(MSG* pMsg);
    virtual BOOL OnCmdMsg(UINT nID, int nCode, void* pExtra,
                AFX_CMDHANDLERINFO* pHandlerInfo);
    protected:
    virtual void DoDataExchange(CDataExchange* pDX);
    virtual void PostNcDestroy();
    //}}AFX_VIRTUAL

// Implementation
protected:

    //{{AFX_MSG(CMyPropDialog)
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};


**************** CMyProperty.cpp *******************

////////////////////////////////////////////////////////////////////////////

// CMyPropertySheet

CMyPropertySheet::CMyPropertySheet()
{
}

CMyPropertySheet::~CMyPropertySheet()
{
}


BEGIN_MESSAGE_MAP(CMyPropertySheet, CTabCtrl)
        //{{AFX_MSG_MAP(CMyPropertySheet)
        ON_NOTIFY_REFLECT(TCN_SELCHANGE, OnSelchange)
        //}}AFX_MSG_MAP
END_MESSAGE_MAP()

// Change the tab on the top
void CMyPropertySheet::OnSelchange(NMHDR* pNMHDR, LRESULT* pResult)
{
    SetActivePage (GetCurSel ());
    *pResult = 0;
}


////////////////////////////////////////////////////////////////////////////

// This page will be the active page
int CMyPropertySheet::SetActivePage (int nPage) {
    CMyPropertyPage*    pPropPage;

    if (nPage < 0 || nPage > m_nPages) nPage = 0;

    // get the actual page
    pPropPage = (CMyPropertyPage *) m_Dialogs.GetAt (m_nActPage);
    // deactevate it
    nGLTemp = pPropPage->m_pDialogPage->SetWindowPos (NULL,
        pPropPage->m_Rect.left, pPropPage->m_Rect.top, 0, 0, SWP_HIDEWINDOW |
        SWP_NOSIZE);

    // get the new page
    pPropPage = (CMyPropertyPage *) m_Dialogs.GetAt (nPage);
    nGLTemp = pPropPage->m_pDialogPage->SetWindowPos (NULL,
        pPropPage->m_Rect.left, pPropPage->m_Rect.top, 0, 0, SWP_SHOWWINDOW |
        SWP_NOSIZE);

    m_nActPage = nPage;

    // set the CTabCtrl-element
    SetCurSel(m_nActPage);

    return TRUE;
}

////////////////////////////////////////////////////////////////////////////

// Set this page as the active page
int CMyPropertySheet::SetActivePage (CMyPropDialog* pPage){
    CMyPropertyPage*    pPropPage;
    int    nPage, nPageFound=0;
    for (nPage = 0; nPage <= m_nPages; nPage++) {
        pPropPage = (CMyPropertyPage *) m_Dialogs.GetAt (nPage);
        if (pPropPage->m_pDialogPage == pPage) {
            nPageFound = nPage;
            break;
        }
    }

    SetActivePage (nPageFound);

    return TRUE;
}
////////////////////////////////////////////////////////////////////////////

// Show the next or previous page
int CMyPropertySheet::DispPage (int nCommand){
    int    nPage;
    nPage = m_nActPage;

    if (nCommand == ID_NEXT_FIELD)
        nPage++;
    else
        nPage--;

    // are there correct page numbers?
    if (nPage < 0) nPage = m_nPages;
    if (nPage > m_nPages) nPage = 0;

    SetActivePage (nPage);
    return TRUE;
}
////////////////////////////////////////////////////////////////////////////

// Get page number X
CMyPropDialog* CMyPropertySheet::GetPage (int nPage){
    CMyPropertyPage*    pPropPage;

    if (nPage < 0 || nPage > m_nPages) nPage = 0;

    pPropPage = (CMyPropertyPage *) m_Dialogs.GetAt (nPage);
    return pPropPage->m_pDialogPage;
}

////////////////////////////////////////////////////////////////////////////

// Get the active = current page
CMyPropDialog* CMyPropertySheet::GetActivePage (void){
    CMyPropertyPage*    pPropPage;

    pPropPage = (CMyPropertyPage *) m_Dialogs.GetAt (m_nActPage);
    return pPropPage->m_pDialogPage;

}

////////////////////////////////////////////////////////////////////////////

// Get the number of possible pages
int CMyPropertySheet::GetPageCount (void) {
    return    m_nPages;
}


////////////////////////////////////////////////////////////////////////////

// CMyPropDialog 
CMyPropDialog::CMyPropDialog(CWnd* pParent /*=NULL*)
    : CDialog()
//    : CDialog(CMyPropDialog::IDD, pParent)
{
    //{{AFX_DATA_INIT(CMyPropDialog)
        // 
    //}}AFX_DATA_INIT
}


void CMyPropDialog::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    //{{AFX_DATA_MAP(CMyPropDialog)
        // 
    //}}AFX_DATA_MAP
}


BEGIN_MESSAGE_MAP(CMyPropDialog, CDialog)
    //{{AFX_MSG_MAP(CMyPropDialog)
    ON_WM_CTLCOLOR()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

////////////////////////////////////////////////////////////////////////////

// Messages for CMyPropDialog 

////////////////////////////////////////////////////////////////////////////

// handle CTRL-PGUP & PGDN
BOOL CMyPropDialog::PreTranslateMessage(MSG* pMsg)
{
    // TODO:
    CWnd    *pWnd;

    /*First Property Sheet tab key translation
    Ctrl+PageUp, and Ctrl+PageDown *

    if (pMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_CONTROL) < 0 &&
        pMsg->wParam == VK_PRIOR) {
        ((CMyPropertySheet *) GetParent ())->DispPage (ID_PREV_FIELD);
        return TRUE;
    }
    if (pMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_CONTROL) < 0 &&
        pMsg->wParam == VK_NEXT) {
        ((CMyPropertySheet *) GetParent ())->DispPage (ID_NEXT_FIELD);
        return TRUE;
    }


    return CDialog::PreTranslateMessage(pMsg);
}

////////////////////////////////////////////////////////////////////////////

// 
BOOL CMyPropDialog::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{
    // TODO: do nothing here
    return CDialog::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
}


////////////////////////////////////////////////////////////////////////////

// CTabCtrl-Handling
BOOL CMyPropertySheet::PreTranslateMessage(MSG* pMsg)
{
    // TODO:

    /*First Property Sheet tab key translation
    Ctrl+PageUp, and Ctrl+PageDown *

    if (pMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_CONTROL) < 0 &&
        pMsg->wParam == VK_PRIOR) {
        DispPage (ID_PREV_FIELD);
        return TRUE;
    }
    if (pMsg->message == WM_KEYDOWN && GetAsyncKeyState(VK_CONTROL) < 0 &&
        pMsg->wParam == VK_NEXT) {
        DispPage (ID_NEXT_FIELD);
        return TRUE;
    }

    return CTabCtrl::PreTranslateMessage(pMsg);
}

void CMyPropertySheet::PostNcDestroy()
{
    // TODO: Free and unlock

    UINT    nN;
    CMyPropertyPage*    pPropPage;

    // pPropPages deleten
    for (nN = 0; nN <= m_nPages; nN++) {
        //  get page per page
        pPropPage = (CMyPropertyPage *) m_Dialogs.GetAt (0);
        LocalUnlock (pPropPage->m_hLocal);    //pBuffer =
                            // (BYTE*)LocalLock(pPropPage->m_hLocal);
        LocalFree (pPropPage->m_hLocal);    // LocalAlloc(LHND, nBufferSize);
        m_Dialogs.RemoveAt (0);
        delete (pPropPage);
    }
    m_Font.DeleteObject ();
    CTabCtrl::PostNcDestroy();
    delete (this);        // free Resources
}

void CMyPropDialog::PostNcDestroy()
{
    // TODO: 

    CDialog::PostNcDestroy();
    delete (this);        // free it
}


////////////////////////////////////////////////////////////////////////////

// Change colors if you want
HBRUSH CMyPropDialog::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    // TODO: Return a different brush if the default is not desired
    return hbr;
}

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read