Click here to Skip to main content
Click here to Skip to main content

SAPrefs - Netscape-like Preferences Dialog

By , 20 Apr 2002
 

Sample Image

CSAPrefsDialog is a base for a preferences dialog, similar to the one used in Netscape. Page selection is handled with a CTreeCtrl - pages can be "sub-pages" of other pages!

Use is very similar to CPropertySheet / CPropertyPage : create a CSAPrefsDialog object. This implements the container for your property pages. The pages are objects of type CPrefsSubDlg (a subclass of CDialog). Using these classes is as easy as this :

   // our preferences dialog
   CSAPrefsDialog dlg;

   // the "pages" (all derived from CSAPrefsSubDlg)
   CPage1 page1;
   CPage2 page2;
   CPage3 page3;
   CPage4 page4;
   
   // add 3 pages
   dlg.AddPage(page1, "Page 1");
   dlg.AddPage(page2, "Page 2");
   dlg.AddPage(page3, "Page 3");

   // this one will be a child node on the tree 
   // (&page3 specifies the parent)
   dlg.AddPage(dlg4, "Page 4", &page3);

   // the prefs dialog title
   dlg.SetTitle("This is pretty");

   // text drawn on the right side of the shaded
   // page label. this does not change when the
   // pages change, hence "constant".
   dlg.SetConstantText("SAPrefs");

   // launch it like any other dialog...
   dlg1.m_csText = m_csStupidCString;
   if (dlg.DoModal()==IDOK)
   {
     m_csStupidCString = dlg1.m_csText;
   }

To use this in your own application, you need to follow these steps :

  1. Add the following files to your project :
    • CSAPrefsDialog.cpp,.h
    • CSAPrefsSubDlg.cpp,.h
    • CSAPrefsStatic.cpp,.h
  2. Copy the IDD_SAPREFS dialog resource from the sample project to your project.
  3. Create your preference "pages" in the resource editor with the following settings :
    • Style - Child
    • Border - None
    • No OK or Cancel buttons! (pretend these are CPropertyPages!)
  4. Use Class Wizard to create the dialog classes for the pages.
  5. In the .cpp and .h files for your new dialog classes, replace all occurances of CDialog with CSAPrefsSubDlg. (you will need to #include "SAPrefsSubDlg.h")
  6. Follow the steps shown in the sample code (above) to create the main dialog and add your pages.

Notes

  • The parent/child relationships that you specify with AddPage(page, text, &parent) are strictly cosmetic. Only the tree control knows about them! As far as the CSAPrefsDialog is concerned, all pages are equal and independent of each other.
  • OnOK and OnCancel are virtual functions of CSAPrefsSubDlg. You can override them in your own derived dialogs. But, you will have to do this by-hand (implement them like any other function). Class Wizard will not be able to do this for you. These functions are called, for every page that has been viewed, when CSAPrefsSubDlg is closed by OK or Cancel.
  • You should handle "Help" as with CPropertyPage : with a WM_NOTIFY message handler :
    BOOL CMyPage::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult) 
    {
    	NMHDR* pnmh = (LPNMHDR) lParam;
    	if (pnmh->code == PSN_HELP) {
    		AfxGetApp()->WinHelp(some help ID);
    	}
    
    	return CSAPrefsSubDlg::OnNotify(wParam, lParam, pResult);
    }
    
    
  • Your pages should fit inside the IDC_DLG_FRAME rectangle on the IDD_SAPREFS dialog. CSAPrefsSubDlg does not resize to fit your pages, like CPropertySheet does. This keeps things nice and simple. Auto-resizing would be a nice addition, but I don't need it for my purposes, so I didn't add it.

History

  • Jan 27 2002 - updated source files.
  • 21 April 2002 - updated source files.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Chris Losinger
Software Developer
United States United States
Member
Chris Losinger is the president of Smaller Animals Software, Inc..

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionThanks ! Good code, one question thoughmemberVijayDighe26 Apr '12 - 11:49 
Why the dialog as well as the pages are of instances of CSAPrefsDialog class?
AnswerRe: Thanks ! Good code, one question thoughmemberChris Losinger26 Apr '12 - 16:25 
the dialog itself is CSAPrefsDialog.
the pages are CSAPrefsSubDlg

QuestionBug?memberManfred Drasch1 Oct '06 - 23:29 
In CSAPrefsDialog::OnGetdispinfoPageTree(...)
you wrote
         strcpy(pTVDispInfo->item.pszText, pPS->csCaption);
But pTVDispInfo->item.pszText is a pointer to an empty string.
 
Do you overwrite anything?
 
I changed it to
         LPTSTR pBuffer = pPS->csCaption.GetBuffer();
         pTVDispInfo->item.pszText = pBuffer;
         pPS->csCaption.ReleaseBuffer();
 
Is that right?

AnswerRe: Bug?memberChris Losinger2 Oct '06 - 1:54 
the MSDN for TVITEM says :
    pszText
    Address of a null-terminated string that contains the item text if the structure specifies item attributes. If this member is the LPSTR_TEXTCALLBACK value, the parent window is responsible for storing the name. In this case, the tree view control sends the parent window a TVN_GETDISPINFO notification message when it needs the item text for displaying, sorting, or editing and a TVN_SETDISPINFO notification message when the item text changes.
    If the structure is receiving item attributes, this member is the address of the buffer that receives the item text.
 
i take that last sentence to mean that: in the situation like we have here, where we are responding to a request for the item's text, the pszText pointer is valid and we can copy our text into the buffer it points to. (i think there should probably be a check in there to prevent copying strings larger than item.cchTextMax, however)
 

QuestionHow to use SAPrefs as a tab control item?membersamluo357 Aug '06 - 18:57 
Smile | :) Hi,Chirs,I use SAPrefs in my application, it works great!
 
thx a lot!;P
 
and now ,I want to use it as a tab control item,it doesnt' works right.
 
Did you try to do this before?
 
any suggestion will be appreciated!
 
Best regards!
AnswerRe: How to use SAPrefs as a tab control item?memberChris Losinger8 Aug '06 - 1:04 
hi,
no i've never tried to do that, sorry.
 

QuestionPossible to dynamically add and delete pages?memberhelsten226 Mar '06 - 3:44 
I am using SAPrefs in my app. Works great!
Now I need to dynamically add/delete pages (dialogs, subdialogs) depending on user selection. Is there a way to do this?
QuestionHow 2 use button for change the pages !!!memberc++ noob22 Dec '05 - 6:35 
Hi ! Chis. I am using your SAPrefs in my program. According to my requirements i have to use button to change the pages and take out all tree control code. But i face some problem which when i click the "Junk" button the screen will change "Junk" page then i click the "Weapon" button the "Weapon" page will appear. After that when i click the "Junk" button for 2nd time that is not function at all.
 
CSAPrefsDialog::CSAPrefsDialog(CWnd* pParent /*=NULL*/)
: CDialog(CSAPrefsDialog::IDD, pParent)
{
//{{AFX_DATA_INIT(CSAPrefsDialog)
//}}AFX_DATA_INIT

m_iCurPage = -1;
m_pages.RemoveAll();
m_pStartPage = NULL;
}
 
/////////////////////////////////////////////////////////////////////////////
 
CSAPrefsDialog::~CSAPrefsDialog()
{
// clean up
for (int i=0;imessage == WM_KEYDOWN) && (pMsg->wParam == VK_ESCAPE))
{
return TRUE;
}

if (CWnd::PreTranslateMessage(pMsg))
return TRUE;

// don't translate dialog messages when
// application is in help mode
CFrameWnd* pFrameWnd = GetTopLevelFrame();
if (pFrameWnd != NULL && pFrameWnd->m_bHelpMode)
return FALSE;

// ensure the dialog messages will not
// eat frame accelerators
pFrameWnd = GetParentFrame();
while (pFrameWnd != NULL)
{
if (pFrameWnd->PreTranslateMessage(pMsg))
return TRUE;
pFrameWnd = pFrameWnd->GetParentFrame();
}

return PreTranslateInput(pMsg);

}
 
/////////////////////////////////////////////////////////////////////////////
 
int CSAPrefsDialog::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;

return 0;
}
 
/////////////////////////////////////////////////////////////////////////////
 
BOOL CSAPrefsDialog::OnInitDialog()
{
CDialog::OnInitDialog();

// where will the dlgs live?
m_boundingFrame.GetWindowRect(m_frameRect);
ScreenToClient(m_frameRect);
m_frameRect.DeflateRect(2,2);

// start with page 0
if (m_pStartPage==NULL)
{
if (ShowPage(0))
{
m_iCurPage = 0;
}
}
else
{
// find start page
for (int i=0; ipDlg);
if (pPS->pDlg == m_pStartPage)
{
ShowPage(i);
m_iCurPage = i;
break;
}
}
}
}

return TRUE;
// return TRUE unless you set the focus to a control
// EXCEPTION: OCX Property Pages should return FALSE
}
 
/////////////////////////////////////////////////////////////////////////////
 
bool CSAPrefsDialog::AddPage(CSAPrefsSubDlg &dlg, CSAPrefsSubDlg* pDlgParent /*=NULL*/)
{
if (m_hWnd)
{
// can't add once the window has been created
ASSERT(0);
return false;
}

pageStruct *pPS = new pageStruct;
pPS->pDlg = &dlg;
pPS->id = dlg.GetID();
pPS->pDlgParent = pDlgParent;

m_pages.Add(pPS);

return true;
}
 
/////////////////////////////////////////////////////////////////////////////
 
BOOL CSAPrefsDialog::PreCreateWindow(CREATESTRUCT& cs)
{
if (!CWnd::PreCreateWindow(cs))
return FALSE;

cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS, NULL, NULL, NULL);
cs.style |= WS_CLIPCHILDREN;
return TRUE;
}
 
/////////////////////////////////////////////////////////////////////////////
 
bool CSAPrefsDialog::ShowPage(CSAPrefsSubDlg * pPage)
{
// find that page
for (int i=0;ipDlg);
if (pPS->pDlg == pPage)
{
ShowPage(i);
m_iCurPage = i;
return true;
}
}
}
return false;
}
 
/////////////////////////////////////////////////////////////////////////////
 
bool CSAPrefsDialog::ShowPage(int iPage)
{
if (iPage == NULL)
iPage = 0;

// turn off the current page
if ((m_iCurPage >= 0) && (m_iCurPage < m_pages.GetSize()))
{
pageStruct *pPS = (pageStruct *)m_pages.GetAt(m_iCurPage);
ASSERT(pPS);
if (pPS)
{
ASSERT(pPS->pDlg);
if (pPS->pDlg)
{
if (::IsWindow(pPS->pDlg->m_hWnd))
{
pPS->pDlg->ShowWindow(SW_HIDE);
}
}
}
else
{
return false;
}
}

// show the new one
if ((iPage >= 0) && (iPage < m_pages.GetSize()))
{
pageStruct *pPS = (pageStruct *)m_pages.GetAt(iPage);
ASSERT(pPS);

if (pPS)
{
ASSERT(pPS->pDlg);
if (pPS->pDlg)
{
// if we haven't already, Create the dialog
if (!::IsWindow(pPS->pDlg->m_hWnd))
{
pPS->pDlg->Create(pPS->pDlg->GetID(), this);
}

// move, show, focus
if (::IsWindow(pPS->pDlg->m_hWnd))
{
pPS->pDlg->MoveWindow(m_frameRect.left, m_frameRect.top, m_frameRect.Width(), m_frameRect.Height());
pPS->pDlg->ShowWindow(SW_SHOW);
//pPS->pDlg->SetFocus();
}

//return true;
}
}
}
return false;
}
 
/////////////////////////////////////////////////////////////////////////////
 
LONG CSAPrefsDialog::OnChangePage(UINT u, LONG l)
{
if (ShowPage(u))
{
m_iCurPage = u;
}

return 0L;
}
 
/////////////////////////////////////////////////////////////////////////////
 
LONG CSAPrefsDialog::OnSetFocusWnd(UINT u, LONG l)
{
if (::IsWindow((HWND)u))
{
::SetFocus((HWND)u);
}
 
return 0L;
}
 

void CSAPrefsDialog::OnBackToPage1()
{
// TODO: Add your control notification handler code here
ShowPage(0);
}
 
void CSAPrefsDialog::OnButton2()
{
// TODO: Add your control notification handler code here
ShowPage(1);
}
 
void CSAPrefsDialog::OnButton3()
{
// TODO: Add your control notification handler code here
ShowPage(2);
}
 
void CSAPrefsDialog::OnButton4()
{
// TODO: Add your control notification handler code here
ShowPage(3);
}

 
Terry (VC++ newbie)
GeneralAdded some functionalitymemberJohn Simmons / outlaw programmer5 Apr '05 - 6:31 
IHi Chris. I'm using your SAPrefs stuff in a priogram, and my requirements included being able to update various pages BEFORE the user had clicked on them the first time. I use PostMessage to post a message to all of tghe pages, and as you might expect, this causes problems because none of the pages except the start page has been created when I post the message.
 
To alleviate the problem, I added the following code to the CSAPrefsDialog. Adnmittedly, it ain't pretty or even elegant, but it fixed my issue...
 
void CSAPrefsDialog::PreLoadPages()
{
	int nCount = m_pages.GetSize();
	for (int iPage = 0; iPage < nCount; iPage++)
	{
		pageStruct *pPS = (pageStruct *)m_pages.GetAt(iPage);
		ASSERT(pPS);
		if (pPS)
		{
			ASSERT(pPS->pDlg);
			if (pPS->pDlg)
			{
				// if we haven't already, Create the dialog
				if (!::IsWindow(pPS->pDlg->m_hWnd))
				{
					pPS->pDlg->Create(pPS->pDlg->GetID(), this);
				}
				// move, show, focus
				if (::IsWindow(pPS->pDlg->m_hWnd))
				{
					pPS->pDlg->MoveWindow(m_frameRect.left, m_frameRect.top, m_frameRect.Width(), m_frameRect.Height());
					pPS->pDlg->ShowWindow(SW_HIDE);
				}
			}
		}
	}
}
 
 
It's nothing more than some of the ShowPage function copied to a new function, and call it from the OnInitDialog function of the CSAPrefsDialog class just before it shows the start page. Voila - no more ASSERTS caused by hWnd = "0xbaadf00d"...
 
Thanks for the classes though - they saved me a buttload of time. Smile | :)

 
------- sig starts
 
"I've heard some drivers saying, 'We're going too fast here...'. If you're not here to race, go the hell home - don't come here and grumble about going too fast. Why don't you tie a kerosene rag around your ankles so the ants won't climb up and eat your candy ass..." - Dale Earnhardt
 
"...the staggering layers of obscenity in your statement make it a work of art on so many levels." - Jason Jystad, 10/26/2001
 

QuestionHow do you copy a dialog resource?memberjhorstkamp27 Oct '03 - 9:36 
I've never had to do that before and can't quite figure it out.
Do you open the .rc file and copy from there, or is it something more simple?
Thanks in advance.
 
John

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 21 Apr 2002
Article Copyright 1999 by Chris Losinger
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid