//----------------------------------------------------------------------------
// ObjectWindows
// Copyright (c) 1993, 1996 by Borland International, All Rights Reserved
//
/// \file
/// Implementation of TPropertyPage and TPropertySheet classes
//----------------------------------------------------------------------------
#include <owl/pch.h>
#include <owl/commctrl.h>
#include <owl/propsht.h>
#include <owl/tabctrl.h>
#include <owl/resource.h>
#include <owl/propsht.rh>
#include <owl/uimetric.h>
#if !defined MAXPROPPAGES
#define MAXPROPPAGES 100
#endif
namespace owl {
OWL_DIAGINFO;
//----------------------------------------------------------------------------
// TPropertySheet
//----------------------------------------------------------------------------
const uint PropSheetTimerID = 0xBACB;
const uint PropSheetTimeOut = 100;
DEFINE_RESPONSE_TABLE1(TPropertySheet, TWindow)
EV_WM_TIMER,
END_RESPONSE_TABLE;
namespace
{
//
// Initializes the structure representing the property sheet.
//
PROPSHEETHEADER InitHeaderInfo_(
const TWindow& w,
uint startPage,
bool isWizard,
uint32 flags)
{
PROPSHEETHEADER h = {sizeof(PROPSHEETHEADER)};
h.dwFlags= flags | (isWizard ? PSH_WIZARD : 0);
h.hInstance = w.GetModule()->GetHandle();
h.pszCaption = w.GetCaption();
h.nStartPage = startPage;
return h;
}
} // namespace
//
/// Constructs a PropertySheet object.
//
TPropertySheet::TPropertySheet(
TWindow* parent,
LPCTSTR title,
uint startPage,
bool isWizard,
uint32 flags,
TModule* module)
:
TWindow(parent, title, module),
SubClassSheet(false),
WantTimer(false),
TimerID(0),
HeaderInfo(InitHeaderInfo_(*this, startPage, isWizard, flags)),
IsWizard(isWizard)
{}
//
/// String-aware overload
//
TPropertySheet::TPropertySheet(
TWindow* parent,
const tstring& title,
uint startPage,
bool isWizard,
uint32 flags,
TModule* module)
:
TWindow(parent, title, module),
SubClassSheet(false),
WantTimer(false),
TimerID(0),
HeaderInfo(InitHeaderInfo_(*this, startPage, isWizard, flags)),
IsWizard(isWizard)
{}
//
// Clean up resources used by PropertySheet object
//
TPropertySheet::~TPropertySheet()
{
}
//
//
bool
TPropertySheet::EnableTimer()
{
// Set flag and let 'SetupWindow' create timer
//
WantTimer = true;
if (!GetHandle())
return true;
// Set a timer if one's not enabled already
//
if(!TimerID)
TimerID = SetTimer(PropSheetTimerID, PropSheetTimeOut);
return TimerID != 0;
}
//
/// Computes the size of the sheet, activates the designated 'startPage'
/// and sets focus to the tab control...
//
void
TPropertySheet::SetupWindow()
{
TWindow::SetupWindow();
// if 'WantTimer' is enabled, start a timer
//
if (WantTimer)
EnableTimer();
}
//
//
//
void
TPropertySheet::CleanupWindow()
{
// Cleanup pending timer
//
if (TimerID && KillTimer(TimerID)) {
TimerID = 0;
}
// Chain to base class' version
//
TWindow::CleanupWindow();
}
//
// Handler for the timer event.
// Closes the window.
//
void
TPropertySheet::EvTimer(uint timerId)
{
if(timerId == TimerID)
IdleAction(0);
}
//
/// Updates the caption of the property sheet.
//
void
TPropertySheet::SetCaption(LPCTSTR title)
{
// Here we'll be a little flexible and allow a call to SetCaption
// even before the underlying window element has been created...
//
if (GetHandle())
SetTitle(title, 0);
else
{
// TWindow's implementation will cache a copy of the caption.
//
TWindow::SetCaption(title);
HeaderInfo.pszCaption = GetCaption() ? GetCaption() : _T("");
}
}
//
/// Brings up the property sheet modally.
/// Returns >= 1 if changes were saved by the user (possibly ID_PSREBOOTSYSTEM or
/// ID_PSRESTARTWINDOWS), 0 if no changes were saved by the user, and -1 if an error occurred.
/// To retrieve extended error information, call GetLastError.
//
int
TPropertySheet::Execute()
{
return static_cast<int>(Run(true));
}
//
/// Brings up the property sheet modelessly.
/// Returns `true` if successful, and `false` if an error occurred.
/// To retrieve extended error information, call GetLastError.
//
bool
TPropertySheet::Create()
{
return Run(false) != 0;
}
//
// Internal routine use allow each page to register the classes
// of it's children
//
void
TPropertyPage::RegisterPageChildObjects(TPropertyPage* page, void*)
{
CHECK(page);
page->RegisterChildObjects();
}
//
/// Brings up the property sheet by calling the Windows API function PropertySheet.
/// For more information about the return value, see the Windows API documentation.
/// \sa http://msdn.microsoft.com/en-us/library/windows/desktop/bb760811.aspx
//
INT_PTR
TPropertySheet::Run(bool modal)
{
// At this point our C++ parent object has (should have!) a valid HWND -
// hence update our Header structure
//
PRECONDITION(GetParentO()->GetHandle());
HeaderInfo.hwndParent = GetParentO()->GetHandle();
HPROPSHEETPAGE pHandle[MAXPROPPAGES];
// Have each page give us its handle
//
HeaderInfo.nPages = 0;
HeaderInfo.phpage = pHandle;
GetPageHandles();
// Have each page register the window classes of it's children
//
ForEachPage(TActionPageFunc(TPropertyPage::RegisterPageChildObjects), 0);
// Flag modal vs. modeless sheet
//
if (modal) {
HeaderInfo.dwFlags &= ~PSH_MODELESS;
}
else {
// Subclass the control so that this window will receive
// button click notifications
//
EnableSubclass(true);
HeaderInfo.dwFlags |= PSH_MODELESS;
}
// Display property sheet
//
const auto r = PropertySheet(&HeaderInfo);
if (modal)
GetApplication()->ResumeThrow();
return r;
}
//
// Internal routine used to add the page handle of a 'TPropertyPage'
// pointer to the array of page handles stored in a PROPSHEETHEADER
// structure.
//
static void
fillPageInfo(TPropertyPage* page, PROPSHEETHEADER* pHeader)
{
CHECK(page);
CHECK(pHeader);
CHECK(pHeader->phpage != 0);
pHeader->phpage[pHeader->nPages] = page->CreatePropertyPage();
pHeader->nPages++;
}
//
// Have each page of dialog hand over the page handle.
//
bool
TPropertySheet::GetPageHandles()
{
#if defined(__TRACE) || defined(__WARN)
// Retrieve number of pages in this sheet's child list
//
int pageCount = GetPageCount();
// A sheet must have a least one page
//
CHECK(pageCount);
CHECK(pageCount <= MAXPROPPAGES);
#endif
// Have each page hand over it's handle
//
ForEachPage(TActionPageFunc(fillPageInfo), &HeaderInfo);
#if defined(__TRACE) || defined(__WARN)
// Double check count
//
CHECK(pageCount == (int)HeaderInfo.nPages);
#endif
return true;
}
//
/// Applies the specified 'action' function to each TPropertyPage child of the
/// sheet.
/// \note The logic here traverses the TPropertySheet's ChildList. Therefore
/// we will miss any page that does not have an associated TPropertyPage
/// inserted in the sheet's ChildList.
void
TPropertySheet::ForEachPage(TActionPageFunc action, void* paramList)
{
if (GetLastChild()) {
TWindow* curChild;
TWindow* nextChild = GetLastChild()->Next();
TPropertyPage* curPage;
do {
curChild = nextChild;
nextChild = nextChild->Next();
curPage = TYPESAFE_DOWNCAST(curChild, TPropertyPage);
if (curPage)
action(curPage, paramList);
} while (curChild != GetLastChild() && GetLastChild() != 0);
}
}
//
/// Applies the specified 'test' function to each 'TPropertyPage' of the sheet and
/// returns the first page which causes the 'test' function to return true. Returns
/// '0' if no page meets the condition.
//
TPropertyPage*
TPropertySheet::FirstPageThat(TCondPageFunc test, void* paramList)
{
if (GetLastChild()) {
TWindow* curChild;
TWindow* nextChild = GetLastChild()->Next();
TPropertyPage* curPage;
do {
curChild = nextChild;
nextChild = nextChild->Next();
curPage = TYPESAFE_DOWNCAST(curChild, TPropertyPage);
if (curPage) {
if (test(curPage, paramList))
return curPage;
}
} while (curChild != GetLastChild() && GetLastChild() != 0);
}
return 0;
}
//
// Internal callback used to count the number of pages within
// a Property Sheet.
//
static void
countPages(TPropertyPage* /*page*/, int* pCount)
{
(*pCount)++;
}
//
/// Retrieves the number of pages within a particular sheet.
//
int
TPropertySheet::GetPageCount() const
{
int pageCount = 0;
CONST_CAST(TPropertySheet*,
this)->ForEachPage(TActionPageFunc(countPages), &pageCount);
return pageCount;
}
//
// Updates the 'HWINDOW' data member of the PropertySheet Object.
// NOTE: This method is called from the InitHandle method of a
// page of the sheet.
//
void
TPropertySheet::InitHandle(THandle sheetHandle)
{
PRECONDITION(GetHandle()==0);
PRECONDITION(::IsWindow(sheetHandle));
SetHandle(sheetHandle);
// When using the system's underlying implementation of PropertySheet
// should we subclass the Sheet or should be it be treated as a black
// box? Ideally the Sheet is this abstract container and we're only
// concerned with our pages [dialogs]. However, there are scenarios where
// we might want to subclass it. For example, if the sheet is used as a
// client of a framewindow and 'ShrinkToClient' is enabled, we'll need to
// detect when the sheet is resized (i.e. receiving WM_SIZE messages) to
// allow the frame to adjust.
//
if (SubClassSheet) {
SubclassWindowFunction();
GetHWndState(true);
}
// Here we must explicitly set up the window - The typical OWL run-through
// (i.e. setup invoked off WM_CREATE) fails in this case since
// the sheet is created indirectly.
//
PerformSetupAndTransfer();
}
//
/// Adds a new page to the end of the PropertySheet.
/// \note The 'pg' must have been created via a call to
/// 'TPropertyPage::CreatePropertyPage' before invoking the 'AddPage' method.
/// \note The property sheet is not resized to fit the new page. The new page should
/// be no larger than the largest page already in the property sheet.
//
void
TPropertySheet::AddPage(TPropertyPage& pg)
{
// Update pointer to parent object
//
if (pg.GetParentO() != this)
pg.SetParent(this);
// Have page create itself it necessary
//
pg.CreatePropertyPage();
CHECK(HPROPSHEETPAGE(pg));
// Inform sheet about new page
//
CHECK(HWND(*this));
SendMessage(PSM_ADDPAGE, 0, TParam2(HPROPSHEETPAGE(pg)));
}
//
/// Simulates the choice of the Apply button, indicating that one or more pages have
/// changed and the changes need to be validated or recorded. The property sheet
/// sends the PSN_KILLACTIVE notification message to the current page. If the
/// current page returns FALSE, the propertysheet sends the PSN_APPLY notification
/// message to all pages. Returns true if all pages successfully applied the
/// changes, or false otherwise.
//
bool
TPropertySheet::Apply()
{
CHECK(HWND(*this));
return SendMessage(PSM_APPLY) != 0;
}
//
/// Disables the 'Cancel' button and changes the text of the 'OK' button to 'Close'.
/// You must invoke this method after applying a change that cannot be canceled.
//
void
TPropertySheet::CancelToClose()
{
CHECK(HWND(*this));
SendMessage(PSM_CANCELTOCLOSE);
}
//
/// Informs the sheet that information in a sheet has changed. The sheet enables the
/// 'Apply' button.
//
void
TPropertySheet::PageChanged(const TPropertyPage& pg)
{
PRECONDITION(HPROPSHEETPAGE(pg));
SendMessage(PSM_CHANGED, TParam1(pg.GetHandle()));
}
//
// Retrieves the handle to the window of the current page of the sheet.
//
HWND
TPropertySheet::GetCurrentPage() const
{
return HWND(CONST_CAST(TPropertySheet*,
this)->SendMessage(PSM_GETCURRENTPAGEHWND));
}
//
/// Retrieves the handle to a tab control of a property sheet.
//
HWND
TPropertySheet::GetTabControl() const
{
CHECK(GetHandle());
return HWND(CONST_CAST(TPropertySheet*,
this)->SendMessage(PSM_GETTABCONTROL));
}
//
/// Passes a message to a property sheet dialog box and indicates whether the dialog
/// processed the message. Returns true if the message was processed or false
/// otherwise.
//
bool
TPropertySheet::IsDialogMessage(MSG& msg)
{
CHECK(GetHandle());
return SendMessage(PSM_ISDIALOGMESSAGE, 0, TParam2(&msg)) != 0;
}
//
/// Simulates the choice of a property sheet button. The button parameter can be one
/// of the following:
/// - \c \b PSBTN_APPLYNOW Apply Now button.
/// - \c \b PSBTN_BACK Back button.
/// - \c \b PSBTN_CANCEL Cancel button.
/// - \c \b PSBTN_FINISH Finish button.
/// - \c \b PSBTN_HELP Help button.
/// - \c \b PSBTN_NEXT Next button.
/// - \c \b PSBTN_OK OK button
//
void
TPropertySheet::PressButton(int button)
{
CHECK(GetHandle());
SendMessage(PSM_PRESSBUTTON, TParam1(button));
}
//
/// Forwards the 'PSM_QUERYSIBLINGS' message to each page in the property sheet. If
/// a page returns a nonzeroe value, the property sheet does not send the message to
/// subsequent pages. Returns the nonzero value from a page in the property sheet,
/// or zero if no page returns a nonzero value.
//
int
TPropertySheet::QuerySiblings(TParam1 p1, TParam2 p2)
{
CHECK(GetHandle());
return (int)SendMessage(PSM_QUERYSIBLINGS, p1, p2);
}
//
/// Indicates that the system needs to be restarted for the changes to take effect.
/// You should invoke this method only in response to the PSN_APPLY or
/// PSN_KILLACTIVE notifications.
/// \note It's your responsibility to reboot the system (via ExitWindowEx, for
/// example).
/// \note Invoking this method causes the TPropertySheet::Execute method to return
/// ID_PSREBOOTSYSTEM.
//
void
TPropertySheet::RebootSystem()
{
CHECK(GetHandle());
SendMessage(PSM_REBOOTSYSTEM);
}
//
/// Removes the specified page from the property sheet
//
void
TPropertySheet::RemovePage(TPropertyPage& pg)
{
PRECONDITION(HPROPSHEETPAGE(pg));
CHECK(GetHandle());
SendMessage(PSM_REMOVEPAGE, 0, TParam2(HPROPSHEETPAGE(pg)));
//
// Should we actually invoke 'DestroyPropertySheetPage' for
// Pages which are added then removed from the PropertySheet??
}
//
/// Removes the page at the specified index from the property sheet
//
void
TPropertySheet::RemovePage(int pgIndex)
{
CHECK(GetHandle());
SendMessage(PSM_REMOVEPAGE, pgIndex);
//
// Should we actually invoke 'DestroyPropertySheetPage' for
// Pages which are added then removed from the PropertySheet??
}
//
/// Indicates that the system needs to be restarted for the changes to take effect.
/// You should invoke this method only in response to the PSN_APPLY or
/// PSN_KILLACTIVE notifications.
/// \note It's your responsibility to reboot the system [via ExitWindowEx for
/// example].
/// \note Invoking this method causes the TPropertySheet::Execute method to return
/// ID_PSRESTARTWINDOWS.
//
void
TPropertySheet::RestartWindows()
{
CHECK(GetHandle());
SendMessage(PSM_RESTARTWINDOWS);
}
//
/// Activates the specified page in the property sheet. Returns true if successful
/// or false otherwise.
/// \note The page that's losing activation receives a PSN_KILLACTIVE notification
/// while the window that's gaining activation receives a PSN_SETACTIVE
/// notification.
//
bool
TPropertySheet::SelectPage(TPropertyPage& pg)
{
PRECONDITION(GetHandle());
PRECONDITION(HPROPSHEETPAGE(pg));
return SendMessage(PSM_SETCURSEL, 0, TParam2(HPROPSHEETPAGE(pg))) != 0;
}
//
/// Activates the page at the specified index in the property sheet. Returns true if
/// successful or false otherwise.
/// \note The page that's losing activation receives a PSN_KILLACTIVE notification
/// while the window that's gaining activation receives a PSN_SETACTIVE
/// notification.
//
bool
TPropertySheet::SelectPage(int pgIndex)
{
PRECONDITION(GetHandle());
return SendMessage(PSM_SETCURSEL, TParam1(pgIndex)) != 0;
}
//
/// Activates the page with the specified resource identifier. Returns true if
/// successful or false otherwise.
/// \note The page that's losing activation receives a PSN_KILLACTIVE notification
/// while the window that's gaining activation receives a PSN_SETACTIVE
/// notification.
//
bool
TPropertySheet::SelectPage(TResId pgRes)
{
CHECK(GetHandle());
return SendMessage(PSM_SETCURSELID, 0, TParam2(static_cast<LPCTSTR>(pgRes)));
}
//
/// Sets the text for the 'Finish' button in a Wizard property sheet.
/// \note The button is enabled while the 'Next' and 'Back' buttons are hidden.
//
void
TPropertySheet::SetFinishText(LPCTSTR txt)
{
CHECK(GetHandle());
SendMessage(PSM_SETFINISHTEXT, 0, TParam2(txt));
}
//
/// Sets the title of a property sheet. If 'style' parameter is the PSH_PROPTITLE
/// value, the prefix "Properties of" is included with the specified title ('txt')
/// parameter.
//
void
TPropertySheet::SetTitle(LPCTSTR txt, uint32 style)
{
CHECK(GetHandle());
SendMessage(PSM_SETTITLE, TParam1(style), TParam2(txt));
}
//
// Enables the 'Back', 'Next' or 'Finish' button in a wizard
// property sheet. The 'flags' parameter can be a combination of
// the following values:
//
// PSWIZB_BACK Back button
// PSWIZB_NEXT Next button
// PSWIZB_FINISH Finish button
//
void
TPropertySheet::SetWizButtons(uint32 flags)
{
CHECK(GetHandle());
SendMessage(PSM_SETWIZBUTTONS, 0, TParam2(flags));
}
//
/// Informs the sheet that the information in the specified page has reverted to the
/// previously saved state. The sheet disables the 'Apply' button if no other pages
/// have registered changes with the property sheet.
//
void
TPropertySheet::PageUnchanged(TPropertyPage& pg)
{
PRECONDITION(HPROPSHEETPAGE(pg));
CHECK(GetHandle());
SendMessage(PSM_UNCHANGED, TParam1(pg.GetHandle()));
}
//
//
//
bool
TPropertySheet::PreProcessMsg(MSG& msg) {
// If current page = 0, then it's time to close the property sheet.
//
HWND page = GetCurrentPage();
if (!page) {
CloseWindow();
return false;
}
else {
return TWindow::PreProcessMsg(msg);
}
}
// ---------------------------------------------------------------------------
// TPropertyPage
// ---------------------------------------------------------------------------
DEFINE_RESPONSE_TABLE1(TPropertyPage, TDialog)
EV_WM_CLOSE,
EV_PSN_SETACTIVE(SetActive),
EV_PSN_KILLACTIVE(KillActive),
EV_PSN_APPLY(Apply),
EV_PSN_RESET(Reset),
EV_PSN_HELP(Help),
EV_PSN_WIZBACK(WizBack),
EV_PSN_WIZFINISH(WizFinish),
EV_PSN_WIZNEXT(WizNext),
EV_PSN_QUERYCANCEL(QueryCancel),
END_RESPONSE_TABLE;
//
/// Constructor for TPropertyPage
//
//
TPropertyPage::TPropertyPage(TPropertySheet* parent, TResId resId,
LPCTSTR title, TResId iconRes,
TModule* module)
:
TDialog(parent, resId, module),
HPropPage(0)
{
// Initialize the PROPSHEETPAGE structure
// NOTE: We're storing the 'this' pointer in the application-defined
// section of the PROPSHEETPAGE structure...
//
memset(&PageInfo, 0, sizeof(PageInfo));
PageInfo.dwSize = sizeof(PROPSHEETPAGE);
PageInfo.dwFlags= PSP_DEFAULT;
PageInfo.pszTemplate = resId;
PageInfo.hInstance = *GetModule();
if (title)
SetTitle(title);
if (iconRes)
SetIcon(iconRes);
PageInfo.dwFlags |= PSP_USECALLBACK;
PageInfo.pfnCallback = PropCallback;
PageInfo.pfnDlgProc = PropDlgProc;
PageInfo.lParam = LPARAM(this);
}
//
/// String-aware overload
//
//
TPropertyPage::TPropertyPage(TPropertySheet* parent, TResId resId,
const tstring& title, TResId iconRes,
TModule* module)
:
TDialog(parent, resId, module),
HPropPage(0)
{
// Initialize the PROPSHEETPAGE structure
// NOTE: We're storing the 'this' pointer in the application-defined
// section of the PROPSHEETPAGE structure...
//
memset(&PageInfo, 0, sizeof(PageInfo));
PageInfo.dwSize = sizeof(PROPSHEETPAGE);
PageInfo.dwFlags= PSP_DEFAULT;
PageInfo.pszTemplate = resId;
PageInfo.hInstance = *GetModule();
SetTitle(title);
if (iconRes)
SetIcon(iconRes);
PageInfo.dwFlags |= PSP_USECALLBACK;
PageInfo.pfnCallback = PropCallback;
PageInfo.pfnDlgProc = PropDlgProc;
PageInfo.lParam = LPARAM(this);
}
//
// !BB TDialog should probably support LPCDLGTEMPLATE in 32-bit to
// allow proper support of the following constructor...
//
/// Constructor to create a property page object using the information stored in the
/// "pgInfo" parameter.
//
TPropertyPage::TPropertyPage(TPropertySheet* parent,
const PROPSHEETPAGE& pgInfo,
TModule* module)
:
TDialog(parent, *pgInfo.pResource, NoAutoDelete, module),
HPropPage(0)
{
// Initialize the PROPSHEETPAGE structure
// NOTE: We're storing the 'this' pointer in the application-defined
// section of the PROPSHEETPAGE structure...
PageInfo = pgInfo;
PageInfo.dwSize = sizeof(PROPSHEETPAGE);
PageInfo.hInstance = *GetModule();
PageInfo.dwFlags |= PSP_USECALLBACK;
PageInfo.pfnCallback = PropCallback;
PageInfo.pfnDlgProc = PropDlgProc;
PageInfo.lParam = LPARAM(this);
}
//
/// Destructor of TPropertyPage. Cleans up allocated buffers used when ObjectWindows
/// provides implementation of property pages.
//
TPropertyPage::~TPropertyPage()
{}
//
/// Specifies flags to be used in creating the property page. These
/// are the flags that belong in PROPSHEETPAGE.dwFlags. If used, this
/// method should be called immediately after the TPropertyPage is
/// constructed.
//
void
TPropertyPage::SetFlags(uint32 flags)
{
PageInfo.dwFlags = flags;
}
//
/// Specifies the icon to be used for this page.
/// \note This routine must be invoked before the page is created.
//
void
TPropertyPage::SetIcon(const TIcon& icon)
{
PageInfo.hIcon = icon;
PageInfo.dwFlags &= ~PSH_USEICONID;
PageInfo.dwFlags |= PSH_USEHICON;
}
//
/// Specifies the icon to be used for this page.
/// \note This routine must be invoked before the page is created.
//
void
TPropertyPage::SetIcon(TResId iconResId)
{
PageInfo.pszIcon = iconResId;
PageInfo.dwFlags &= ~PSH_USEHICON;
PageInfo.dwFlags |= PSH_USEICONID;
}
//
/// Sets the caption of this page.
/// \note This routine must be invoked before the page is created.
//
void
TPropertyPage::SetTitle(LPCTSTR title)
{
// Let TWindow make a copy of the title.
// Then point to the 'duped' copy...
//
SetCaption(title);
PageInfo.pszTitle = GetCaption();
PageInfo.dwFlags |= PSP_USETITLE;
}
//
/// Sets the caption of this page.
/// \note This routine must be invoked before the page is created.
//
void
TPropertyPage::SetTitle(int txtResId)
{
PageInfo.pszTitle = MAKEINTRESOURCE(txtResId);
PageInfo.dwFlags &= ~PSP_USETITLE;
}
//
/// WM_NOTIFY handler: Scans for property sheet notifications to 'patch' the
/// 'idFrom' member to the predefined 'PropPageID'.
/// \note This is necessary because WM_NOTIFY subdispatching relies on the ID of
/// the sender.
//
TResult
TPropertyPage::EvNotify(uint id, TNotify& notifyInfo)
{
if (notifyInfo.code <= (uint)PSN_FIRST && notifyInfo.code >= (uint)PSN_LAST){
// Property sheet notifications require special handling since the
// concept of ctlId is non-existent. We patch it to the default
// PageID expected by the ObjectWindows Property Page dispatchers
//
notifyInfo.idFrom = PropPageID;
id = PropPageID;
// Also make sure we don't reflect the message back to what looks like
// the 'child' sender but is really the sheet. We achieve this by
// NULLing out the HWND of the sender.
//
notifyInfo.hwndFrom = 0;
}
return TDialog::EvNotify(id, notifyInfo);
}
//
// Handle case of Escape from Edit control with ES_MULTILINE style
//
void TPropertyPage::EvClose()
{
tchar szClass[10];
HWND hWnd = ::GetFocus();
if(hWnd && IsChild(hWnd) && (::GetWindowLong(hWnd, GWL_STYLE)&ES_MULTILINE) &&
::GetClassName(hWnd, szClass, 10) && lstrcmpi(szClass, _T("EDIT")) == 0){
GetParentO()->PostMessage(WM_CLOSE, 0, 0);
}
TDialog::EvClose();
}
//
/// This callback is the default 'Dialog box procedure' of each page of our
/// property sheet....
//
INT_PTR CALLBACK
TPropertyPage::PropDlgProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) {
case WM_INITDIALOG: {
// Attach C++ object with it's underlying handle if necessary
//
LPPROPSHEETPAGE pageInfo = REINTERPRET_CAST(LPPROPSHEETPAGE,lParam);
InitHandle(hDlg, pageInfo);
}
break;
case WM_NOTIFY: {
TNotify& notifyInfo= *(REINTERPRET_CAST(TNotify*, lParam));
if (notifyInfo.code<=(uint)PSN_FIRST && notifyInfo.code>=(uint)PSN_LAST){
// Property sheet notifications require special handling
// since the concept of ctlId is non-existent. We patch it
// to the default PageID expected by the ObjectWindows
// Property Page dispatchers
//
notifyInfo.idFrom = PropPageID;
wParam = PropPageID;
}
}
break;
default:
break;
}
return TDialog::StdDlgProc(hDlg, msg, wParam, lParam);
}
//
/// As with TDialog, most of the page's events are dispatched directly from the
/// window procedure. Although the Sheet has each page's DialogProc, the notifications
/// are not (or don't seem to be) funneled directly to the dialogProc.
//
INT_PTR
TPropertyPage::DialogFunction(TMsgId msg, TParam1 p1, TParam2 p2)
{
switch(msg) {
case WM_NOTIFY: {
NMHDR& nmhdr = *(REINTERPRET_CAST(NMHDR *, p2));
if (nmhdr.code >= (uint)PSN_LAST && nmhdr.code <= (uint)PSN_FIRST) {
CHECK(p1 != 0);
CHECK(nmhdr.idFrom != 0);
}
}
break;
default:
break;
}
return TDialog::DialogFunction(msg, p1, p2);
}
// 'CopyPageInfo' is called by the 'Sheet' object requesting
// the page to fill out a 'PROPSHEETPAGE' structure which
// describes the attribute of the page.
//
void
TPropertyPage::CopyPageInfo(PROPSHEETPAGE& pgInfo) const
{
pgInfo = PageInfo;
}
//
/// CreatePropertyPage is called by the Sheet object requesting the page to return a
/// handle used to represent this dialog when it's inserted into the Sheet.
//
HPROPSHEETPAGE
TPropertyPage::CreatePropertyPage()
{
if (!HPropPage)
HPropPage = CreatePropertySheetPage(&PageInfo);
return HPropPage;
}
//
/// Destroys the page represented by this object.
//
bool
TPropertyPage::DestroyPropertyPage()
{
PRECONDITION(HPropPage);
if (DestroyPropertySheetPage(HPropPage)) {
HPropPage= 0;
return true;
}
return false;
}
//
/// Creates the page.
//
bool
TPropertyPage::Create()
{
// When using the system's implementation if PropertyDialogs, the page
// is actually created behind the scene when the PropertySheet is
// created. The callbacks specified by ObjectWindows [PropDlgProc &
// PropCallback] will update the TPropertyPage's HWindow data member.
//
// Therefore, our 'Create' method simply checks that the handle was
// indeed initialized and happily returns true.
//
CHECK(GetHandle() != 0);
return true;
}
/// Method that ties the C++ object presenting the page with the underlying 'HWND'
//
/// The pages of a propertysheet are created internally by windows...
/// Consequently, we must attempt to grab and thunk the 'HWND' as
/// early as possible. There are two basic opportunities to do so:
///
/// (a) A Sheet can provide a callback which is called whenever a
/// page is created or released. Hence 'TPropertyPage::PropCallback'
/// invokes 'InitHandle'.
///
/// (b) Each page provides a dialog-procedure callback. Hence,
/// 'TPropertyPage::PropDlgProc' invokes 'InitHandle' upon receiving
/// a WM_INITDIALOG message.
//
void
TPropertyPage::InitHandle(THandle pageHandle, LPPROPSHEETPAGE ppsp)
{
// First check that the lParam data member of the PROPSHEETPAGE
// contains a 'this' pointer to an OWL TPropertyPage object
// wrapping the dialog
//
TPropertyPage* page = REINTERPRET_CAST(TPropertyPage*, ppsp->lParam);
if (page && pageHandle) {
// Only proceed if the C++ object is not fully initialized
//
if (page->GetHandle()) {
CHECK(page->GetHandle() == pageHandle);
return;
}
// Proceed to initialize the handle of the page object.
//
page->SetHandle(pageHandle);
// We can now retrieve the pointer to the sheet object
// and initialize the latter if necessary.
//
TPropertySheet* sheet = page->GetSheet();
if (sheet) {
if (!sheet->GetHandle()) {
HWND sheetHandle = ::GetParent(pageHandle);
if (sheetHandle)
sheet->InitHandle(sheetHandle);
}
}
// Allow OWL to thunk the page window.
//
page->SubclassWindowFunction();
// NOTE: Typically, we'd call 'GetHWndState', 'PerformDlgInit',
// and 'PerformSetupAndTransfer' after
// thunking a window. However, TDialog's 'EvInitDialog'
// method [invoked via the PropDlgProc callback] will
// handle that.
}
}
//
// Static callback invoked whenever a Property Page is created is
// destroyed.
//
UINT CALLBACK
TPropertyPage::PropCallback(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp)
{
switch (uMsg) {
case PSPCB_CREATE: {
// A Property Page was just created.. We'll attempt to thunk
// the underlying 'HWND' if it was specified..
//
if (hwnd)
InitHandle(hwnd, ppsp);
}
break;
case PSPCB_RELEASE: {
// A Property Page was just released... Currently, we
// don't have any processing to do here....
// Should we invoke a virtual method of TPropertyPage
// from here - just in case a page needs to do something
// when it's released or are the default ObjectWindows
// mechanisms (CleanupWindow & Destructor) sufficient....
//
}
break;
default:
break;
}
// The return is ignored for PSPCB_RELEASE (according to the doc).
// A non-zero value allows the page to be created...
//
return 1;
}
// Default implementation of PropertySheet notifications.. Derived classes
// will override - most likely.
//
//
/// Virtual methods to handle the Sheet notifications: PSN_APPLY
//
int
TPropertyPage::Apply(TPshNotify&)
{
// Check if it's OK to close and attempt to retrieve data
//
try
{
if (CanClose())
{
TransferData(tdGetData);
return PSNRET_NOERROR;
}
}
catch (TXOwl& x)
{
int MessageLoopResult = x.Unhandled(GetModule(), IDS_OKTORESUME);
if (MessageLoopResult != 0)
{
::PostQuitMessage(MessageLoopResult);
}
return PSNRET_NOERROR;
}
// It's not OK to proceed - return focus to this page
//
return PSNRET_INVALID_NOCHANGEPAGE;
}
//
/// Virtual methods to handle the Sheet notifications: PSN_KILLACTIVE
//
bool
TPropertyPage::KillActive(TPshNotify&)
{
return false;
}
//
/// Virtual methods to handle the Sheet notifications: PSN_APPLY
//
void
TPropertyPage::Help(TPshNotify&)
{
}
//
/// This virtual function is called when a user exits a property sheet by clicking
/// the Cancel button. By default, Reset() does nothing, but it can be overridden.
/// Virtual methods to handle the Sheet notifications: PSN_RESET
//
void
TPropertyPage::Reset(TPshNotify&)
{
}
//
/// Virtual methods to handle the Sheet notifications: PSN_SETACTIVE
//
int
TPropertyPage::SetActive(TPshNotify&)
{
return 0;
}
//
/// Virtual methods to handle the Sheet notifications: PSN_WIZBACK
//
int
TPropertyPage::WizBack(TPshNotify&)
{
return 0;
}
//
/// Virtual methods to handle the Sheet notifications: PSN_FINISH
//
bool
TPropertyPage::WizFinish(TPshNotify&)
{
return false;
}
//
/// Virtual methods to handle the Sheet notifications: PSN_WIZNEXT
//
int
TPropertyPage::WizNext(TPshNotify&)
{
return 0;
}
//
/// Virtual methods to handle the Sheet notifications: PSN_QUERYCANCEL
//
bool
TPropertyPage::QueryCancel(TPshNotify&)
{
return false;
}
} // OWL namespace
/* ========================================================================== */
↑ V1004 The 'page' pointer was used unsafely after it was verified against nullptr. Check lines: 216, 217.
↑ V1004 The 'pHeader' pointer was used unsafely after it was verified against nullptr. Check lines: 276, 277.
↑ V1004 The 'page' pointer was used unsafely after it was verified against nullptr. Check lines: 275, 278.
↑ V507 Pointer to local array 'pHandle' is stored outside the scope of this array. Such a pointer will become invalid.