QA: Why does not CPropertySheet work with transcriber?

Alexander Shargin (rudankort@softspb.com), January 04, 2003.

Question

I use CPropertySheet class to display some property sheets in my application. I noticed that every time I try to use transcriber when a property sheet is open it just closes. How can I fix this?

Answer

This is a known problem with MFC implementation of CPropertySheet.

Background

As you may be aware of, MFC has its own implementation of modal windows. This implementation is included in CWnd class and can be used not just for dialog boxes and property sheets, but for any window your program creates. Another advantage of this approach is that you can easily add custom code to the message loop: all you need is to override PreTranslateMessage. Here is how this mechanism works. Modal message loop is executed within CWnd::RunModalLoop method. On each iteration CWnd::ContinueModal method is called to determine if modal loop must be terminated. ContinueModal is virtual so you can override it in a derived class. CWnd also supplies CWnd::EndModalLoop method used to stop modal loop immediately. This method just sets a flag in CWnd class which is checked by the default implementation of ContinueModal.

CDialog uses these facilities of CWnd. CDialog::DoModal just creates a modeless dialog box and then calls CWnd::RunModalLoop to emulate modal behaviour. CDialog::EndDialog just calls CWnd::EndModalLoop. In turn, the default handlers for OK and Cancel buttons both call CDialog::EndDialog to close the dialog when necessary. But with property sheets the situation is different. When the user taps OK in a property sheet, the notification is sent to the active page, not to the sheet. So, another approach was required to emulate modal behaviour for property sheets. The developers of MFC solved the problem by overriding CWnd::ContinueModal method in CPropertySheet class:

BOOL CPropertySheet::ContinueModal() { // allow CWnd::EndModalLoop to be used if (!CWnd::ContinueModal()) return FALSE; // when active page is NULL, the modal loop should end ASSERT(::IsWindow(m_hWnd)); BOOL bResult = SendMessage(PSM_GETCURRENTPAGEHWND); return bResult; }

As we can see, the modal loop ends when there is no active page on the property sheet (i. e. the property sheet has been destroyed). This is determined by sending PSM_GETCURRENTPAGEHWND message to the property sheet. If this returns FALSE, then MFC knows to end the modal loop. But it turns out that Transcriber interferes with this process and causes the PSM_GETCURRENTPAGEHWND message to return FALSE even when the property sheet has not been destroyed. So the property sheet closes.

Solution

Now that we understand why the problem occurs we can fix it. The solution is to create a new property sheet class (e. g. CPropertySheetEx) and override CPropertySheet::ContinueModal method to remove buggy code from the modal loop. Then we can handle IDOK and IDCANCEL in each property page to end modal loop when the property sheet is closed. Modal loop is terminated by CPropertySheet::EndDialog method.

Since handling IDOK and IDCANCEL in every page is rather boring we do required handling in a separate base class (e. g. CPropertyPageEx) and then derive all property pages from it.

Step-By-Step Example

  1. Use wizard to create CPropertySheetEx class derived from CPropertySheet.
  2. Override CPropertySheet::ContinueModal method like this: BOOL CPropertySheetEx::ContinueModal() { if (!CWnd::ContinueModal()) return FALSE; return TRUE; }
  3. Use wizard to create CPropertyPageEx class derived from CPropertyPage.
  4. Override OnOK and OnCancel handlers: void CPropertyPageEx::OnOK() { CPropertySheetEx *pSheet = DYNAMIC_DOWNCAST(CPropertySheetEx, GetParent()); if(pSheet != NULL) { pSheet->EndDialog(IDOK); } CPropertyPage::OnOK(); } void CPropertyPageEx::OnCancel() { CPropertySheetEx *pSheet = DYNAMIC_DOWNCAST(CPropertySheetEx, GetParent()); if(pSheet != NULL) { pSheet->EndDialog(IDCANCEL); } CPropertyPage::OnCancel(); }
  5. Use CPropertyPageEx instead of CPropertyPage and CPropertySheetEx instead of CPropertySheet everywhere in your program.

Sample

You can download sample project - TranscriberFriendlyPS.zip - 15 Kb.