Programming‎ > ‎

MFC

MFC Programming Tips

Here are MFC programming tips. Lots of my time and sweat were spent ongetting these tips.

Index


How can AfxGetApp() be broken?

KnowledgeBase Q108587 discusses "How to get current CDocument or CView fromanywhere" where it uses AfxGetApp(). This is only true for a single threaded case. In the case of multi-threaded applications, AfxGetApp() returns the "current thread" app (which may or may not exists), not the main actual application (I spent scratching my head for a while). Thus when you try to get the view or doc from some multi-threaded MFC DLL, you have to do the following, using AfxGetAppModuleState() which remembers the global variables:


  MDI: 
  CMDIFrameWnd *pFrameWnd =   
              (CMDIFrameWnd*) (AfxGetAppModuleState()->m_pCurrentWinApp->m_pMainWnd); 
  CMDIChildWnd *pChild = pFrameWnd->MDIGetActive(); 
  if (pChild)   
    return (CDocument *) pChild->GetActiveDocument(); 
  SDI: 
  CFrameWnd *pFrameWnd =     
              (CFrameWnd*) (AfxGetAppModuleState()->m_pCurrentWinApp->m_pMainWnd); 
  return (CDocument *) pFrame->GetActiveDocument(); 
  

Top


How can I relate my window in dll to the parent?

When I create a MFC DLL (not MFC extension DLL), I like to createa dialog window hooked to the parent window. When the parent ismade from MFC, then I can use the previous item to get the CWnd *parent easily. The problem is when the parent is made of pure Win32 code andonly has HWND. Here is the way:

  
  void MyDLL::Create(HWND &hParent)  
  {     
    CWnd wnd;    
	wnd.Attach(hParent);    
	myDialog->Create(&wnd, rc); 
	// modeless    
	wnd.Detach();  
  }
  

Top


Generic menu idle processing inside MFC-based ActiveX control

Published in "Windows::Developer" (CMP Media LLC, Nov, 2002. vol.13, No.11, p.45)

One of the useful features of MFC is greying/ungreying out menu items easily by adding handlers for menu IDs using OnUpdateCmdUI. I thought that adding these handlers was enough for my context menu built in my MFC ActiveX control. This is the case when you create applications under MFC.

To my amazement, however, OnUpdateCmdUI is never called, except at the time I click a menu item on that particular ID only. Therefore the context menu items have no chance of greying items while the menu is up. In an application or a dialog, WM_KICKIDLE message has been used in the past. Unfortunately this message is never sent to my control from a VB container. I could not find any reference in how to make OnUpdateCmdUI work in MFC-based ActiveX controls. Here is the way I found.

It turned out that there exists a generic solution. This solution works for any MFC based control. You can use it any time you need it by just pasting the code. Note that I use AfxGetThreadState() to get information.

The message you use is WM_ENTERIDLE, which is sent when the popup menu or the dialog is up and this message is sent to an ActiveX control from the VB containter. The first step is to add a handler in your control:

In header file, you add

  
  // Message maps  
  //{
  {AFX_MSG(CMyCtrl)  afx_msg void OnEnterIdle(UINT nWhy, CWnd *pWho);
  ...
  //}} AFX_MSG  
  DECLARE_MESSAGE_MAP()
  

In the implementation file, you add


  BEGIN_MESSAGE_MAP(CMyCtrl, COleControl)  
    ON_WM_ENTERIDLE()  
  ...
  END_MESSAGE_MAP()
  

Now the implementation of OnEnterIdle(). The message map, OnEnterIdel(), has two arguments, nWhy and *pWho. The nWhy tells you whether this message is sent by the dialog or the popup menu. The second one is the window which has the dialog or the menu. This pWho is a temp CWnd* and thus you cannot get the associated menu.

The trick is to use the thread state to get the associated menu pointer. Once you get the menu pointer, you can get the menu IDs in the menu andother information. However, I had to figure out what kind of CCmdUI messageI need to send to my control. Here is the routine you add to your controland OnUpdateCmdUI for your popup menu to work.


  void CMyCtrl::OnEnterIdle(UINT nWhy, CWnd *pWho)
  {  
    // enter idle can be either dialog or menu (including popup menu)  
	// here we issue the popup menu OnUpdateCmdUI messages  
	if (nWhy == MSGF_MENU)  
	{    
	  // look at MFC/SRC/WINCORE.CPP TrackPopupMenu hangling for reference    
	  CMenu *pMenu;    
	  _AFX_THREAD_STATE *pThreadState = AfxGetThreadState();    
	  if (pThreadState->m_hTrackingWindow == this->m_hWnd)    
	  {      
	    // popup menu      
		pMenu = CMenu::FromHandle(pThreadState->m_hTrackingMenu);      
		UINT count = pMenu->GetMenuItemCount();      
		for (int i=0; i < count; ++i)      
		{        
		  UINT menuID = pMenu->GetMenuItemID(i);        
		  if (menuID != 0) // don't handle the separator (id is zero)        
		  {          
		    CCmdUI state; // initializer make all variables zero          
			// we set only the necessary ones          
			state.m_nID = menuID;          
			state.m_nIndex = i;          
			state.m_nIndexMax = count;          
			state.m_pMenu = pMenu;            
			OnCmdMsg(menuID, CN_UPDATE_COMMAND_UI, &state, NULL);        
		  }      
		}    
	  }  
	}  
	COleControl::OnEnterIdle(nWhy, pWho);
  }
  

Top


Single instance application (non MSDN way)

Here is the way described in the MSDN (do this in OnInitInstance() of App)

    
  // class name is set at the top    
  // the following code is just trying to bring up the previous instance of MyAp    
  CWnd *PrevCWnd = CWnd::FindWindow(MyClassName, NULL);    
  // Determine if another window with our class name exists...    
  if (PrevCWnd->GetSafeHwnd())     
  {	
    // Bring the main window to the top      
	if (PrevCWnd->GetForegroundWindow() != PrevCWnd)      
	{        
	  PrevCWnd->SetForegroundWindow();        
	  PrevCWnd->BringWindowToTop();      
	}      
	if (PrevCWnd->IsIconic())        
	  PrevCWnd->ShowWindow(SW_SHOWNORMAL);      
	// parse command line info (I'm looking for file open      
	CCommandLineInfo cmdInfo;      
	ParseCommandLine(cmdInfo);      
	if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileOpen)      
	{        
	  CString fileName = cmdInfo.m_strFileName;        
	  // WM_SETTEXT message can cross process boundary        
	  // WPARAM and LPARAM are copied over the process boundary        
	  BOOL bRes = PrevCWnd->              
	  SendMessage(WM_SETTEXT, (WPARAM) 1, (LPARAM) ((LPCTSTR) fileName));      
	}      
	// Check to see if launched as OLE server      
	else if (cmdInfo.m_bRunEmbedded || cmdInfo.m_bRunAutomated)      
	{        
	  // MyApp is asked to run as automation        
	  AfxMessageBox(_T("MyApp is running without automation.  Please close MyApp and try again."));      
	}
  }
  

However, FindWindow() could fail on slow PCs when the user clicked very rapid succession and it happened to me. Therefore I introduced the mutex (global resource) to detect the existence of the application.

  
  // CMyApp construction  
  const CString MyClassName(_T("Mine"));  
  ::CreateMutex(NULL, TRUE, (LPCTSTR) MyClassName);  
  // Note that the system closes the handle automatically when the   
  // process terminates.   
  // The mutex object is destroyed when its last handle   
  if (GetLastError() == ERROR_ALREADY_EXISTS)  
  {
  

Top



Send information to another process

It is usually difficult to cross the process boundary. Thefollowing is a quick and easy and works.

      
  CWnd *PrevCWnd = CWnd::FindWindow(MyClassName, NULL);      
  ....(it turned out that the applicaiton is running            
       let the existing one to handle open file command.)      
  // parse command line info (I'm looking for file open)      
  CCommandLineInfo cmdInfo;      
  ParseCommandLine(cmdInfo);      
  if (cmdInfo.m_nShellCommand == CCommandLineInfo::FileOpen)      
  {        
    CString fileName = cmdInfo.m_strFileName;        
	// WM_SETTEXT message can cross process boundary        
	// WPARAM and LPARAM are copied over the process boundary        
	BOOL bRes = PrevCWnd->             
	            SendMessage(WM_SETTEXT, (WPARAM) 1, (LPARAM) ((LPCTSTR) fileName));      
  }
  

Top


Change the default registry key and window title

Changing the default way done in MFC is a joy in source code reading. This is how the registry key is set in app.

  
  // The registry key is usually the name of a company.   
  // It is stored in a key of the following form:   
  // HKCU\Software\company name\application name\section name\value name.  
  // application name is taken from AFX_IDS_APP_TITLE  
  CString keyName;  
  keyName.LoadString(IDS_KEY_TITLE);  
  SetRegistryKey(keyName);
  

We modify it in the following way by loading string from the customization dll.

  
  // try replacing the value set above from gdecust.dll  
  HMODULE hm = ::LoadLibrary(_T("mycust.dll"));  
  if (hm == NULL)    
    ::MessageBox(NULL, _T("Could not find mycust.dll"), "Error",
	             MB_ICONEXCLAMATION | MB_OK);  
  TCHAR buf[MAX_PATH];  
  if (hm)  
  {    
	// load frame title (defined in gdecust(d).dll)    
	int len = ::LoadString(hm, 1, buf, sizeof(buf));    
	// if non-zero, then replace appname    
	if (len)    
	{      
	  // follow MFC way (m_pszAppName is freed at dtor)      
	  free((void*) m_pszAppName);      
	  m_pszAppName = _tcsdup(buf);    
	}    
	len = ::LoadString(hm, 2, buf, sizeof(buf));    
	if (len)    
	{      
	  free((void*)m_pszRegistryKey);      
	  m_pszRegistryKey = NULL;       
	  SetRegistryKey(buf);    
	}    
	// The one and only window has been initialized, so show and update it.    
	// until this time, no window is available    
	// the title is appname - filename    
	CMainFrame *pMainFrame = (CMainFrame *) m_pMainWnd;    
	int len = ::LoadString(hm, 3, buf, sizeof(buf));    
	if (len)    
	{      
	  // if non-zero, then replace the default title for frame       
	  pMainFrame->SetStrTitle(buf);      
	  pMainFrame->OnUpdateFrameTitle(pMainFrame->GetStyle() & FWS_ADDTOTITLE);      
	  // verify      
	  // CString strTitle = ((CMainFrame *) m_pMainWnd)->GetStrTitle();    
	}    
	::FreeLibrary(hm); 
	// reference counting  
  }
  

Top


Loading non-document extension files

When you open a file, the following two members are called

  1. OnOpenFile()
  2. OnOpenRecentFile() when loading from "recent" files
  
The standard execution order is
  1. OnFileOpen->OpenDocumentFile->CDocument::OnOpenDocument()
  2. OnOpenRecentFile()->OpenDocumentFile()->CDocument::OnOpenDocument()
  
Therefore, we need the change in CDocument *CMyAppp::OpenDocumentFile(LPCTSTRlpszFilename), not in CDocument::OnOpenDocument(),since we may load not just workspace but other files. For those non-workspace, we need not to change the workspace andhere we return just NULL

This non-standard way is also needed, since we changed the add recent fileto include not only document files but also non-document files.

Top


Remove All Files(*.*) option from open/save dialog

This one needs a lot of work. Ended up modifying the source,DOCMGR.CPP


  // copied from CDocManager source code (DOCMGR.CPP)
  static void _AppendFilterSuffix(CString& filter, OPENFILENAME& ofn,CDocTemplate* pTemplate, CString* pstrDefaultExt)
  {  
    ASSERT_VALID(pTemplate);  
	ASSERT_KINDOF(CDocTemplate, pTemplate);  
	CString strFilterExt, strFilterName;  
	if (pTemplate->GetDocString(strFilterExt, CDocTemplate::filterExt) 
	    && !strFilterExt.IsEmpty() 
		&& pTemplate->GetDocString(strFilterName, CDocTemplate::filterName) 
		&& !strFilterName.IsEmpty())    
	{       
	  // a file based document template - add to filter list       
	  ASSERT(strFilterExt[0] == '.');       
	  if (pstrDefaultExt != NULL)       
	  {         
	    // set the default extension         
		*pstrDefaultExt = ((LPCTSTR)strFilterExt) + 1;  
		// skip the '.'         
		ofn.lpstrDefExt = (LPTSTR)(LPCTSTR)(*pstrDefaultExt);         
		ofn.nFilterIndex = ofn.nMaxCustFilter + 1;  
		// 1 based number       
	  }       
	  // add to filter       
	  filter += strFilterName;       
	  ASSERT(!filter.IsEmpty());  
	  // must have a file type name       
	  filter += (TCHAR)'\0';  
	  // next string please       
	  filter += (TCHAR)'*';       
	  filter += strFilterExt;       
	  filter += (TCHAR)'\0';  
	  // next string please       
	  ofn.nMaxCustFilter++;    
	}
  }
  // File->Open here and File->Save, SaveAs (used by CMyDoc::DoSave())
  // remove adding All Files (*.*) part
  BOOL CMyApp::DoPromptFileName(CString &fileName, UINT nIDSTitle, DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate *pTemplate)
  {   
	CFileDialog dlgFile(bOpenFileDialog);   
	CString title;   
	VERIFY(title.LoadString(nIDSTitle));   
	dlgFile.m_ofn.Flags |= lFlags;   
	CString strFilter;   
	CString strDefault;   
	if (pTemplate != NULL)   
	{      
	  ASSERT_VALID(pTemplate);      
	  _AppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate, &strDefault);   
	}   
	else   
	{      
	  // do for all doc template      
	  POSITION pos = m_pDocManager->GetFirstDocTemplatePosition();      
	  BOOL bFirst = TRUE;      
	  while (pos != NULL)      
	  {        
	    CDocTemplate* pTemplate =                             
		  (CDocTemplate*) m_pDocManager->GetNextDocTemplate(pos);        
		_AppendFilterSuffix(strFilter, dlgFile.m_ofn, pTemplate, bFirst ? &strDefault : NULL);        
		bFirst = FALSE;      
	  }    
	}    
	dlgFile.m_ofn.lpstrFilter = strFilter;    
	dlgFile.m_ofn.lpstrTitle = title;    
	dlgFile.m_ofn.lpstrFile = fileName.GetBuffer(_MAX_PATH);      
	int nResult = dlgFile.DoModal();      
	fileName.ReleaseBuffer();      
	BOOL bRes = (nResult == IDOK);      
	if (bRes == TRUE)      
	{         
	  TCHAR ext[MAX_PATH];         
	  GetExtension(fileName, ext);         
	  if (_tcscmp(_tcslwr(ext), "mine") != 0)         
	  {            
	    AfxMessageBox(_T("File extension must be \".mine\".  Cancel Operation"),                                          
		 MB_ICONEXCLAMATION);            
		return FALSE;         
	  }         
	  else            
	    return TRUE;      
	}      
	else         
	  return FALSE;}
	  
	void CMyApp::OnFileOpen() 
	{  
	  CString newName;  
	  if (!DoPromptFileName(newName, AFX_IDS_OPENFILE, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))    
	    return; // open cancelled  
	  OpenDocumentFile(newName);
	}
	

Top


Shell execute generic version

  
  // The following gives more info on failure.  
  SHELLEXECUTEINFO execInfo;  
  execInfo.cbSize = sizeof(SHELLEXECUTEINFO);  
  execInfo.fMask = NULL;  
  execInfo.hwnd = NULL;  
  execInfo.lpVerb = _T("open");  
  execInfo.lpFile = pathname;  
  execInfo.lpParameters = NULL;  
  execInfo.lpDirectory = NULL;  
  execInfo.nShow = SW_SHOWNORMAL;  
  execInfo.hInstApp = NULL;   
  //
  BOOL bVal = ShellExecuteEx(&execInfo);  
  if (bVal == FALSE)  
  {    
    CString msg;    
	msg += _T("ShellExecuteEx Error for ");    
	msg += pathname;    
	msg += _T(":");    
	DWORD dwErr = GetLastError();    
	switch(dwErr)    
	{    
	  case ERROR_FILE_NOT_FOUND:      
	    msg += _T("The specified file was not found."); 
		break;     
	  case ERROR_PATH_NOT_FOUND:      
	    msg += _T("The specified path was not found."); 
		break;     
	  case ERROR_DDE_FAIL:      
	    msg += _T("The DDE transaction failed."); 
		break;     
	  case ERROR_NO_ASSOCIATION:      
	    msg += _T("There is no application associated with the given file name extension."); 
		break;     
	  case ERROR_ACCESS_DENIED:      
	    msg += _T("Access to the specified file is denied."); 
		break;     
	  case ERROR_DLL_NOT_FOUND:      
	    msg += _T("One of the library files necessary to run the application can't be found."); 
		break;     
	  case ERROR_CANCELLED:      
	    msg += _T("The function prompted the user for additional information, but the user canceled the request."); 
		break;     
	  case ERROR_NOT_ENOUGH_MEMORY:      
	    msg += _T("There is not enough memory to perform the specified action."); 
		break;     
	  case ERROR_SHARING_VIOLATION:      
	    msg += _T("A sharing violation occurred."); 
		break;    
	  default:      
	    msg += _T("unknown error occured"); 
		break;    
	}    
	AfxMessageBox(msg);  
  }
  

Top


Change menu dynamically


  BOOL CMainFrame::ChangeMenu(const UINT resourceID)
  {  
    // The documentation for LoadMenu says  
	// Before exiting, an application must free system resources associated   
	// with a menu if the menu is not assigned to a window. An application frees a   
	// menu by calling the DestroyMenu method.    
	// Note that m_hMenuDefault is done by the MFC.  
	BOOL bVal;  
	CMenu newMenu;  
	bVal = newMenu.LoadMenu(resourceID);  
	// if new menu is loaded  
	if (bVal != FALSE)  
	{    
	  bVal = SetMenu(NULL);    
	  bVal = SetMenu(&newMenu);    
	  if (bVal != FALSE)    
	  {      
	    ::DestroyMenu(m_hMenuDefault);      
		m_hMenuDefault = newMenu.GetSafeHmenu();  
		// or m_NewMenu.m_hMenu;    
	  }    
	  newMenu.Detach();  
	}  
	return bVal;
  }
  

Top


CTEdit class for checking input and change digit display

CTEdit class was created to augument CEdit so that you can checkthe input and change digit display.


  Use CTEdit class in your program in the following way:  
  
  m_rwcamoriginx.SubclassDlgItem(IDC_SOMEVALUE, this);  
  m_rwcamoriginx.Min(-1000000000.);   
  m_rwcamoriginx.Max( 1000000000.);  
  m_rwcamoriginx.IncludeMin(TRUE);  
  m_rwcamoriginx.IncludeMax(TRUE);  
  m_rwcamoriginx.AllowMinusOne(FALSE);
  
  // TEdit.h : header file 
  //#define WM_EDITLOSTFOCUS (WM_USER+100)
  /////////////////////////////////////////////////////////////////////////////// 
  CTEdit window
  //// Philosophy of this class //
  // 1. The precision is only a viewing convenience function
  // 2. We will not change the value unless user types in some number
  // 3. User can see the full value by setting Precision(-1)
  //   
  class CTEdit : public CEdit
  {
    // Constructionpublic:   
	CTEdit();   
	BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
	// Attributespublic:  
	void   Value(double val);  
	// update the text box also.  
	double Value() const 
	{ return m_dValue; }  
	void   Min(double min) { m_dMin = min; }  
	double Min() const { return m_dMin;}  
	void   Max(double max) { m_dMax = max; }  
	double Max() const { return m_dMax; }  
	// if Precision >= 0, then digit specifies the number of digits after .  
	// if Precision < 0, then  the edit box is printed as it is using %.15g format  
	void Precision(int digit);  
	int Precision() const { return m_uDigit; }  
	// whether to include min value as a valid value or not  
	BOOL IncludeMin() const { return m_bIncludeMin; }  
	void IncludeMin(BOOL bVal) { m_bIncludeMin = bVal; }  
	BOOL IncludeMax() const { return m_bIncludeMax; }  
	void IncludeMax(BOOL bVal) { m_bIncludeMax = bVal; }  
	// whether to allow -1 when min >= 0  
	BOOL AllowMinusOne() const { return m_bAllowMinusOne; }  
	void AllowMinusOne(BOOL bVal) { m_bAllowMinusOne = bVal; }  
	// convenience function  
	void SetAttrib(double min, double max, BOOL includemin, BOOL includemax, BOOL allowminusone);
	// Operationspublic:  
	void UpdateEditBox();  
	// assignment from double (Note that Value() does more than just assignment)  
	CTEdit &operator=(double val) { Value(val); return *this; }  
	// explicit conversion to double  
	operator double() const { return m_dValue; }  
	CString GetFormatted();  // return the formatted text in current setting  
	void Reset();   // after this IsChanged() return FALSE  
	BOOL IsChanged();// Overrides	
	// ClassWizard generated virtual function overrides	
	//{{AFX_VIRTUAL(CTEdit)
	//}}AFX_VIRTUAL
	// Implementation
	public:    
	  virtual ~CTEdit();	
	// Generated message map functionsprotected:	
	//{{AFX_MSG(CTEdit)	
	afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);	
	afx_msg void OnKillFocus(CWnd* pNewWnd);	
	afx_msg void OnRButtonDown(UINT nFlags, CPoint point);	
	//}}AFX_MSG	DECLARE_MESSAGE_MAP()  
	BOOL SimpleFloatParse(LPCTSTR lpszText, double& d);  
	double m_dValue;  // the current value  
	double m_dMin;  // allowed min  
	double m_dMax;  // allowed max  
	double m_dSaved; // original value saved  
	BOOL   m_bIncludeMin;    
	BOOL   m_bIncludeMax;  
	BOOL   m_bAllowMinusOne;  
	int   m_uDigit; // specify the number of digits  
	CString m_format;  // format specification  
	BOOL m_SelChanged;  // flag whether user typed a number or not};
	/////////////////////////////////////////////////////////////////////////////// CTEdit
	// this routine makes 5.123456789 into 5.12345678989999
	CString dtoa(double dVal)
	{  
	  int dec;  
	  int sign;  
	  char *p = _fcvt(dVal, 32, &dec, &sign );  
	  CString tmp = p;  // tmp contains just numbers  
	  // now add '.'  
	  CString whole = tmp.Left(dec);  
	  CString fract = tmp.Mid(dec);  
	  // fract may contain trainling zeros and thus remove them  
	  int pos = fract.GetLength()-1; 
	  // zero based  
	  while (fract.Mid(pos) == "0")  
	  {    
	    CString tmp = fract.Left(pos);    
		fract = tmp;    
		pos = fract.GetLength()-1;  
	  }  
	  CString res = whole;  
	  res += '.';  
	  res += fract;  
	  return res;
	}
	
	CTEdit::CTEdit() 
	  :m_uDigit(-1), m_dMin(0), m_dMax(1000000000000.), m_dValue(0.0),  
	   m_bIncludeMin(TRUE), m_bIncludeMax(TRUE), m_bAllowMinusOne(TRUE),  
	   m_SelChanged(FALSE)
	 {  
	   Precision(-1);  
	   // create text format
	 }
	
	BOOL CTEdit::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
	{  
	  BOOL bVal = CEdit::Create(dwStyle, rect, pParentWnd, nID);  
	  CString text = GetFormatted();  
	  SetWindowText(text);   
	  return bVal;
	}
	
	CTEdit::~CTEdit(){}
	
	BEGIN_MESSAGE_MAP(CTEdit, CEdit)	
	//{{AFX_MSG_MAP(CTEdit)	
	ON_WM_CHAR()	
	ON_WM_KILLFOCUS()	
	ON_WM_RBUTTONDOWN()	
	//}}AFX_MSG_MAPEND_MESSAGE_MAP()
	
	/////////////////////////////////////////////////////////////////////////////
	// CTEdit message handlers
	void CTEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
	{  
	  // Q92394		
	  // The default CEdit::OnChar(nChar, nRepCnt, nFlags); should not be used  
	  if (_istalpha(nChar))    
	    MessageBeep(MB_ICONQUESTION);  
	  else  
	  {    
	    CString curText;    
		// get the current text    
		GetWindowText(curText);    
		// find where the . is    
		int index = curText.ReverseFind(_T('.'));    
		// do we have two '.'?    
		if (nChar == _T('.') && index !=-1)    
		{      
		  MessageBeep(MB_ICONQUESTION);      
		  return;    
		}    
		// when user type some number, then it is changed    
		m_SelChanged = TRUE;    
		SetModify(TRUE);    
		// ok handle this char    
		DefWindowProc(WM_CHAR, nChar, MAKELONG(nRepCnt, nFlags));  
	  }
	}
	
	void CTEdit::UpdateEditBox()
	{  
	  if (::IsWindow(m_hWnd))  
	  {    
	    CString textFormatted = GetFormatted();    
		SetWindowText(textFormatted);        
		// the value did not change, but representation changed    
		CMyPropertyPage *pPage = dynamic_cast (GetParent());    
		if (pPage)      
		  pPage->SetModified(IsChanged());  
	  }
	}
	
	void CTEdit::OnKillFocus(CWnd* pNewWnd) 
	{  
	  if (m_SelChanged)  
	  {    
	    CString text;    
		CString format;    
		CString errMsg;    
		GetWindowText(text);	  
		double d;	  
		if (!SimpleFloatParse((LPCTSTR) text, d))    
		{      
		  errMsg = _T("Please enter a double value");      
		  ::MessageBox(m_hWnd, errMsg, _T("Validation"), MB_ICONEXCLAMATION);      
		  text = GetFormatted();      
		  this->SetWindowText(text);      
		  this->SetSel(0,-1);      
		  this->SetFocus();      
		  return;	  
		}    
		double dVal = d;    
		// Check the range in min and max    
		BOOL bMin;    
		if (m_dMin == DBL_MIN)      
		  bMin = FALSE;    
		else      
		// if does not satify the constraint, use the old value      
		  bMin = (m_bIncludeMin == TRUE) ? (dVal < m_dMin) : (dVal <= m_dMin);    
		BOOL bMax;    
		if (m_dMax == DBL_MAX)      
		  bMax = FALSE;    
		else      
		  bMax = (m_bIncludeMax == TRUE) ? (dVal > m_dMax) : (dVal >= m_dMax);    
		if (bMin == TRUE) // less than min    
		{      
		  // check whether the value is -1 or not      
		  if (dVal == -1 && m_bAllowMinusOne == TRUE)      
		  {        
		    SetWindowText(_T("-1"));        
			return;      
		  }    
		}    
		if (bMin || bMax)    
		{      
		  CString errMsgFormat;      
		  errMsgFormat.Format(_T("Invalid value.  Valid Range is "));      
		  errMsgFormat += m_format;      
		  errMsgFormat += (m_bIncludeMin==TRUE) ? _T(" <= ") : _T(" < ");      
		  errMsgFormat += _T(" value ");      
		  errMsgFormat += (m_bIncludeMax==TRUE) ? _T(" <= ") : _T(" < ");      
		  errMsgFormat += m_format;      
		  errMsg.Format((LPCTSTR) errMsgFormat, m_dMin, m_dMax);      
		  ::MessageBox(m_hWnd, errMsg, _T("Validation"), MB_ICONEXCLAMATION);      
		  text = GetFormatted();      
		  this->SetWindowText(text);      
		  this->SetSel(0,-1);      
		  this->SetFocus();      
		  return;    
		}    
		m_dValue = dVal;    
		m_SelChanged = FALSE;    
		UpdateEditBox();  
	  }  
	  CWnd::OnKillFocus(pNewWnd);
	}
	
	void CTEdit::Precision(int digit)
	{  
	  m_uDigit = digit;  
	  if (m_uDigit >= 0)  
	  {    
	    CString  tmp;    
		tmp.Format(_T("%d"), m_uDigit);    
		m_format = _T("%.");    
		m_format += tmp;    
		m_format += _T("f");  
	  }  
	  else  
	  {    
	    // if negative, reset to -1     
		m_uDigit = -1;    
		m_format = _T("%.15g"); 
		// this is the way done by Microsoft  
	  }  
	  // we may have to change the format, since format has been changed  
	  UpdateEditBox();
	}
	
	void CTEdit::Value(double val)
	{  
	  // well, this depends on m_uDigit is set or not  
	  m_dValue = val;  
	  m_dSaved = val; 
	  // saved.  used in IsChanged()  
	  UpdateEditBox();
	}
	
	// copied from AfxSimpleFloatParseBOOL 
	CTEdit::SimpleFloatParse(LPCTSTR lpszText, double& d)
	{  
	  ASSERT(lpszText != NULL);  
	  while (*lpszText == ' ' || *lpszText == '\t')    
	    lpszText++;  
	  TCHAR chFirst = lpszText[0];  
	  d = _tcstod(lpszText, (LPTSTR*)&lpszText);  
	  if (d == 0.0 && chFirst != '0')    
	    return FALSE;   
		// could not convert  
	  while (*lpszText == ' ' || *lpszText == '\t')    
	    lpszText++;  
		if (*lpszText != '\0')    
		  return FALSE;   
		// not terminated properly  
		return TRUE;
	}
	
	CString CTEdit::GetFormatted()
	{  
	  if (m_uDigit >= 0)  
	  {    
	    CString  tmp;    
		tmp.Format(_T("%d"), m_uDigit);    
		m_format = _T("%.");    
		m_format += tmp;    
		m_format += _T("f");  
	  }  
	  else  
	  {    
	    // if negative, reset to -1     
		m_uDigit = -1;    
		m_format = _T("%.15g"); 
		// this is the way done by Microsoft  
	  }  
	  CString value;  
	  if (m_dValue != -1)    
	    value.Format(m_format, m_dValue);  
	  else  
	  {    
	    // special handling for -1    
		if (AllowMinusOne())      
		  value = _T("-1");    
		else      
		  value.Format(m_format, m_dValue);  
	  }  
	  return value;
	}
	
	void CTEdit::OnRButtonDown(UINT nFlags, CPoint point) 
	{
	  //	CEdit::OnRButtonDown(nFlags, point);  
	  if ((nFlags & MK_CONTROL) && (nFlags & MK_SHIFT))  
	  {    
	    // this is a full property dialog    
		CTEditProp PropDlg(this);    
		PropDlg.DoModal();    
		UpdateEditBox();  
	  }  
	  else if ((nFlags & MK_SHIFT) || (nFlags & MK_CONTROL))  
	  {    
	    // only a precision dialog    
		CPrecisionDlg precDlg(this);    
		precDlg.DoModal();    
		UpdateEditBox();  
	  }  
	  else    
	    CEdit::OnRButtonDown(nFlags, point);
	}
	
	BOOL CTEdit::IsChanged()
	{
	  //  TRACE("m_dSaved = %f, m_dValue = %f\n", m_dSaved, m_dValue);  
	  if (m_dSaved != m_dValue)    
	    return TRUE;  
	  else    
	    return FALSE;
	}
	
	void CTEdit::Reset()
	{  
	  m_dSaved = m_dValue;
	}
	
	void CTEdit::SetAttrib(double min, double max, BOOL includemin, BOOL includemax, BOOL allowminusone)
	{ 
	  Min(min);  
	  Max(max);  
	  IncludeMin(includemin);  
	  IncludeMax(includemax);  
	  AllowMinusOne(allowminusone);
	}
	

Top


Customization of menus and commands

This was done for accomodating the change of menus and commandsafter the SDI application was built. In order to make this work in multithreaded application, you have to replaceAfxGetApp() with AfxGetAppModule()->m_pCurrentWinApp.(See the first item in this page.) The idea is an old one, OnCmdMsg(). Add to the message macros

   
  ...   
  ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTW, 0, 0xFFFF, OnToolTipText)   
  ON_NOTIFY_EX_RANGE(TTN_NEEDTEXTA, 0, 0xFFFF, OnToolTipText)
  END_MESSAGE_MAP()
  
  Here are the modification in the main application CMainFrm.h has two lines added
  ==============================
  public:  
  BOOL AddCommand(CCustomCommands *pCommand);  
  BOOL RemoveCommand(CCustomCommands *pCommand);  
  void InitCustomCommands();  
  void EndCustomCommands();  
  CCmdTargetList &customCommandList() 
  { 
    return m_CustomCommands; 
  }
  
  private:  
  CCmdTargetList m_CustomCommands;  
  CCustomize *m_pCustomize;
  
  CMainFrm.cpp has the following mods:
  ====================================
  /////////////////////////////////////////////////////////////////////
  // customization/////////////////////////////////////////////////////////////////////
  BOOL CMainFrame::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) 
  {  
    // process custom commands first  
	if (nID >= ID_CUSTOM_START && nID <= ID_CUSTOM_END)    
	  if (m_CustomCommands.OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
	    return TRUE;  
	  return CFrameWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);}
	#ifndef _countof
	#define _countof(array) (sizeof(array)/sizeof(array[0]))
	#endif
	
	// process tooltip for added commands
	BOOL CMainFrame::OnToolTipText(UINT id, NMHDR* pNMHDR, LRESULT* pResult)
	{  
	  ASSERT(pNMHDR->code == TTN_NEEDTEXTA || pNMHDR->code == TTN_NEEDTEXTW);  
	  UINT nID = pNMHDR->idFrom;  
	  CString strTipText;  
	  if (m_CustomCommands.ToolTip(nID, strTipText))  
	  {    
	    // if tip is zero length, then just return    
		if (strTipText.GetLength() == 0)      
		  return FALSE;    
		// need to handle both ANSI and UNICODE versions of the message    
		TOOLTIPTEXTA* pTTTA = (TOOLTIPTEXTA*)pNMHDR;    
		TOOLTIPTEXTW* pTTTW = (TOOLTIPTEXTW*)pNMHDR;
		#ifndef _UNICODE    
		if (pNMHDR->code == TTN_NEEDTEXTA)      
		  lstrcpyn(pTTTA->szText, strTipText, _countof(pTTTA->szText));    
		else      
		  _mbstowcsz(pTTTW->szText, strTipText, _countof(pTTTW->szText));
		#else    
		if (pNMHDR->code == TTN_NEEDTEXTA)      
		  _wcstombsz(pTTTA->szText, strTipText, _countof(pTTTA->szText));    
		else      
		  lstrcpyn(pTTTW->szText, strTipText, _countof(pTTTW->szText));
		#endif    
		*pResult = 0;    
		// bring the tooltip window above other popup windows    
		::SetWindowPos(pNMHDR->hwndFrom, HWND_TOP, 0, 0, 0, 0,	  
		       SWP_NOACTIVATE|SWP_NOSIZE|SWP_NOMOVE|SWP_NOOWNERZORDER);    
	    return TRUE;    
		// message was handled  
	  }  
	  else    
	    return CFrameWnd::OnToolTipText(id, pNMHDR, pResult);
    }
	
	// this will handle prompt for added menu and toolbar
	void CMainFrame::GetMessageString(UINT nID, CString& rMessage) const
	{  
	  if (nID >= ID_CUSTOM_START && nID <= ID_CUSTOM_END)  
	  {    
	    // because of "const", I have to do trick    
		BOOL bVal = ((CMainFrame *)AfxGetMainWnd())->customCommandList().Prompt(nID, rMessage);    
		if(bVal)    
		{      
		  TRACE("Custome Command: nID = %d, msg = %s\n", nID, (LPCTSTR) rMessage);      
		  return;    
		}  
	  }  
	  else    
	    CFrameWnd::GetMessageString(nID, rMessage);  
  	  // for debugging custom commands
	}
	
	// add to the command list
	BOOL CMainFrame::AddCommand(CCustomCommands *pCommand)
	{  
	  return m_CustomCommands.Add(pCommand);
	}
	
	// remove from the command list
	BOOL CMainFrame::RemoveCommand(CCustomCommands *pCommand)
	{  
	  return m_CustomCommands.Remove(pCommand);
	}
	
	// callback functions
	BOOL cfAddCommand(CCustomCommands *pCom)
	{  
	  return ((CMainFrame *) AfxGetMainWnd())->AddCommand(pCom);
	}
	
	BOOL cfRemoveCommand(CCustomCommands *pCom)
	{  
	  return ((CMainFrame *) AfxGetMainWnd())->RemoveCommand(pCom);
	}
	
	void CMainFrame::InitCustomCommands()
	{  
	  cfInitCustomize(&m_pCustomize);  
	  if (m_pCustomize)  
	  {    
	    m_pCustomize->pfAddCommand = cfAddCommand;    
		m_pCustomize->pfRemoveCommand = cfRemoveCommand;    
		m_pCustomize->Modify();    
		TRACE("Customization initialized.");  
	  }
	}
	
	void CMainFrame::EndCustomCommands()
	{  
	  // customization ends  
	  cfEndCustomize(&m_pCustomize);  
	  if (m_pCustomize)  
	  {    
	    delete m_pCustomize;    
		TRACE("Customization was not deleted.");  
	  }
	}
	// In customization DLL has the following:
	///////////////////////////////////////////////////////////
	//  call this function at the initialization
	//  DLLImport = export speficication for dllImport/Export
	void DLLImport cfInitCustomize(CCustomize **p)
	{  
	  if (*p)    
	    delete (*p);  
	  // perform initialization
	}
	
	//  call this function at the closing
	void DLLImport cfEndCustomize(CCustomize **p)
	{  
	  // perform cleanup  
	  if (*p)    
	    delete (*p);  
		*p = 0;
	}
	
	Here are the header files for the customizatio dll: 
	// COMMAND class////////////////////////////////////////////////////////
	class DLLImport COMMAND{public:  CString prompt;  CString tooltip;};
	// CCustomCommand command target///////////////////////////////////////////////////////////
	class DLLImport CCustomCommands : public CCmdTarget
	{  
	  DECLARE_DYNCREATE(CCustomCommands)      
	  CCustomCommands();             
	  // protected constructor used by dynamic creation
	  // Attributes  
	  public:  
	  virtual ~CCustomCommands();    
	  virtual BOOL Prompt(UINT nCommandID, CString &promptText);  
	  // purpose: provide the prompt for the commands  
	  //          return TRUE if nCommandID is registered  
	  virtual BOOL ToolTip(UINT nCommandID, CString &tipText);  
	  // purpose: provide the tooltip for the commands and return tooltip text  
	  //          return TRUE if nCommandID is registered  
	  void Add(UINT nCommandID, const COMMAND &command);  
	  // purpose: register a command handled by this custome command  
	  //          with prompt and tooltip    
	  void Remove(UINT nCommandID);  
	  // purpose: remove a particular command ID from the list  
	  const std::map &CommandMap() { return m_CommandMap;}  
	  // purpose: return a command ID list which contains all the command ids  
	  std::vector CommandIDs();  
	  // purpose: return the command ID list this command implements
	  // Operations 
	  public:
	  // Overrides  
	  // ClassWizard generated virtual function overrides  
	  //{{AFX_VIRTUAL(CCustomCommand)  
	  //}}AFX_VIRTUAL
	  // Implementation
	  protected:  
	  // use ID as a key and prompt and tooltip as value  
	  std::map m_CommandMap;  
	  // Generated message map functions  
	  //{{AFX_MSG(CCustomCommand)  
	  // NOTE - the ClassWizard will add and remove member functions here.  
	  //}}AFX_MSG  
	  DECLARE_MESSAGE_MAP()};
	  // customization base class
	  // you must derive your own class to do add other member funcitons.
	  // this is done so that GDE can call two functions of whatever
	  // you creat without knowing actual implementation.
	  // if used from outside, declare for import
	  class CCustomCommands;
	  class DLLImport CCustomize
	  {
	    public:  
		  CCustomize(): pfAddCommand(0), pfRemoveCommand(0) {};  
		  virtual ~CCustomize() {};  
		  // adding command handlers to the main application  
		  // this requires pfAddCommand Initialized  
		  // this is called by the application and should never call yourself.  
		  virtual void Modify() = 0;  
		  // GDE now change the device and accordingly   
		  // need necessary menu changes  
		  virtual void SelectDevice(int device) = 0;  
		  // add/remove a submenu from the top menu  
		  void AddMenus(const CString &TopMenu, const CString &Submenu, DWORD ID, UINT Position);  
		  void RemoveMenu(const CString &TopMenu, const CString &SubMenu);   
		  // add/remove toolbutton  
		  void AddToolbarButton(DWORD ToolBarID, DWORD ID, DWORD BitmapID);  
		  void RemoveToolbarButton(DWORD ToolBarID, DWORD ID);  
		  void AddSeparatorButton(DWORD ToolBarID);  
		  // callback functions to be used to communicate with CMainFrame  
		  // these are set by CMianFrame.  Do not modify  
		  BOOL (*pfAddCommand)(CCustomCommands *pCommand);  
		  BOOL (*pfRemoveCommand)(CCustomCommands *pCommand);
	  };
	  // The application will call cfInitCustomize after its initialization done.
	  // you should implement your own so that all initializations will
	  // be done here.  In particular, you must new your derived object
	  // of CCustomize and return the pointer to the application.
	  extern void DLLImport cfInitCustomize(CCustomize **p);
	  // The application will call cfEndCustomize before shutting down the 
	  // application. you must do whatever cleanup needed.  If p is not NULL, 
	  // then the application will do "delete p" after this function is called.
	  extern void DLLImport cfEndCustomize(CCustomize **p);
  

Top


TBN_DROPDOWN menu


  Add the member to MainFrame:  
  CMenu m_menuImgSrc;
  Load it (popup menu):  
  VERIFY(m_menuImgSrc.LoadMenu(IDR_MENU_IMGSRC));
  Add to the message macro  
  ON_NOTIFY(TBN_DROPDOWN, IDR_ACTION_NONE, OnDropImgSrc)
  void CMainFrame::OnDropImgSrc(NMHDR *pNotifyStruct, LRESULT *result)
  {  
    TBNOTIFY *pTBNotify = (TBNOTIFY *) pNotifyStruct;  
	// is this actiontoolbar click?  
	// TRACE("idFrom: %d, item: %d\n", pNotifyStruct->idFrom, pTBNotify->iItem);  
	if (pNotifyStruct->idFrom == IDR_ACTION_NONE)   
	{    
	  // get the id for this button    
	  int   &iItem = pTBNotify->iItem; 
	  // this is the id for a particular button    
	  // if it is greyed, return    
	  CToolBarCtrl &ActionBarCtrl = m_wndActionBar.GetToolBarCtrl();    
	  if (!ActionBarCtrl.IsButtonEnabled(iItem))  
	    //if it is greyed, return      
	    return;  
	  CMenu* pPopup = m_menuImgSrc.GetSubMenu(0);  
	  VERIFY(pPopup != NULL);  
	  CWnd* pWndPopupOwner = this;  
	  while (pWndPopupOwner->GetStyle() & WS_CHILD)    
	    pWndPopupOwner = pWndPopupOwner->GetParent();  
	  CRect rect;  
	  // GetRect needs the ver 4.70 or later comctrl32.dll  
	  m_wndActionBar.GetToolBarCtrl().GetRect(pTBNotify->iItem, &rect);  
	  // translate the coords into screen coordinates  
	  m_wndActionBar.ClientToScreen(&rect);  
	  // setting up excluding region  
	  TPMPARAMS tpm;  
	  tpm.cbSize = sizeof(TPMPARAMS);  
	  tpm.rcExclude = rect;  
	  // CMenu Popup does not have Ex function  
	  ::TrackPopupMenuEx(pPopup->m_hMenu,
	          TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL,                      
			  rect.left, rect.bottom, pWndPopupOwner->m_hWnd, &tpm);
    }
  }
  

Top


How can I play a sound file (.wav) ?

In the old days, you used video for window routines to playsound. The functionalities still exist but it works slow.I tried Direct Sound, but it is too complicated just to playsound, even though it offers more control of playing. (DirectX SDKhas Samples/C++/Common and Samples/C++/DirectSound/playsound directories are the ones to look at. Unfortunately these samples are really lacking the quality of the codes. For example, no consideration for copy ctor and assignment operators for classes. They even contain bugs so thatyou cannot change volume nor pan.) The easiest one is to use system function PlaySound function.

First, you use the resource dialog to import .wav file into the resource. The resource editor will add the following line when I picked a file from windows/Media/Windows XP Startup.wav:


  ///////////////////////////////////////////////////////////////////////////////
  // WAVE//IDR_WAVE1          
  WAVE                    "res\\Windows XP Startup.wav"
  
Then add the following line in the source code:

  #include <mmsystem.h>  
  ...  
  PlaySound(MAKEINTRESOURCE(IDR_WAVE1), GetModuleHandle(NULL), SND_RESOURCE | SND_ASYNC | SND_NODEFAULT);
  
Link with winmm.lib. If you want to write more code, then here is the routine explained in MSDN:

  BOOL PlayResource(LPCSTR lpName)
  {  
    BOOL bRtn;  
	LPCSTR lpRes;  
	HANDLE hResInfo, hRes;  
	hResInfo = FindResource(GetModuleHandle(NULL), lpName, "WAVE");  
	if (hResInfo == NULL)    
	  return FALSE;  
	hRes = LoadResource(GetModuleHandle(NULL), (HRSRC) hResInfo);  
	if (hRes == NULL)    
	  return FALSE;  
	lpRes = (LPCSTR) LockResource(hRes);  
	if (lpRes != NULL)  
	{    
	  bRtn = sndPlaySound(lpRes, SND_MEMORY | SND_SYNC | SND_NODEFAULT);    
	  UnlockResource(hRes);  
	}  
	else    
	  bRtn = FALSE;  
	FreeResource(hRes);  
	return bRtn;
  }
  and call it in the source code as  PlayResource(MAKEINTRESOURCE(IDR_WAVE1));
  

Top


How can I make OpenFile dialog resizable?

This is a quick hack. Under the usual MFC MDI/SDI, the defaultOpenFile dialog is not resizable (You see how it is done in DocMgr.cppin Vc7/atlmfc/src/mfc directory). In order to make it resizable, you have to make the dialog EXPLORER style. The fix is rather quick and easy. You add your app event handler for ID_FILE_OPEN. The IDE Wizard will create OnOpenFile() handler. You modify the flag for DoPromptFileName() and done!


  void CMyApp::OnFileOpen()
  {  
    // the standard processing is this:  
	// void CWinApp::OnFileOpen()  
	// {  
	//   ASSERT(m_pDocManager != NULL);  
	//   m_pDocManager->OnFileOpen();  
	// }  
	// this is in appdlg.cpp then call m_pDocManager->OnFileOpen()  
	// so I copied from DocMgr.cpp for OnFileOpen()  
	// modified to add style to EXPLORER and SIZING so that we can   
	// see, e.g. more thumnail pictures.  
	//  
	CString newName;  
	if (!DoPromptFileName(newName, AFX_IDS_OPENFILE, OFN_EXPLORER | OFN_ENABLESIZING |    
	////// added this line      
	    OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, TRUE, NULL))    
	  return; // open cancelled  
	OpenDocumentFile(newName);  
	// if returns NULL, the user has already been alerted
  }
  

Top


Why won't double-clicking on a registered file open my MFC application?

When you create an MFC application, double-clicking Explorer file for the registered extension will causethe error "Error: failed to execute DDE commnad". This is due to the bug in Visual Studio .NET 2003, not presentin other versions of Visual Studio.

By looking at the src in $(VS .NET 2003)/Vc7/atlmfc/src/winfrm.cpp OnDDEExecute(), you find the problem in that copying DDE command to the buffer "szCommand" is forgotten and thus AfxGetApp()->OnDDECommand(szCommand) is not getting the DDE command "[open("filename")]". The fix is described below. You handle the WM_DDE_EXECUTE message in your MainFrame. See http://www.codeguru.com/Cpp/W-D/doc_view/misc/article.php/c8549/

Top


How can I use accelerator keys in dialog

Dialogs do not process accelerator keys. You have to add them by hand.Here is how http://www.codeguru.com/Cpp/W-D/dislog/tutorials/article.php/c4965.

  
  1. HANDLE m_hAccel;     
     ... in dialog.h  
  2. Add OnInitDialog()     
     m_hAccel = ::LoadAccelerators(AfxGetResourceHandle(), MAKEINTRESOURCE(IDR_ACCELERATOR1);  
  3. Add DestroyWindow     
     BOOL bRes = ::DestroyAcceleratorTable(m_hAccel);  
  4. Add PreTranslateMessage()     
     BOOL CRDlg::PreTranslateMessage(MSG* pMsg)     
	 {       
	   if (WM_KEYFIRST <= pMsg->message && pMsg->message <= WM_KEYLAST)         
	     if (m_hAccel && ::TranslateAccelerator(m_hWnd, m_hAccel, pMsg))	   
		   return TRUE;       
	   return CDialog::PreTranslateMessage(pMsg);     
	 }  
  5. Add accelerator key resource by using Resource -> Add Accelerator.     
     Now modify "ID", "Modifier", "Key", and "Type".  
  6. Add handler for ID in dialog.
  

Top


How can I make Enter key not to close dialog?

When you have a dialog and press Enter key, it usually closes the dialog.This is good most of the time, but if you have a dialog based application,this is a disaster. When you create a dialog based application, you usually want to make Enter key to select the currently focused button. Paul DiLascia describes MSDN Magazine July 2000. You make use of the modification of the previous item. That is, add the accelerator keys for VK_RETURN and VK_TAB and add the handlers OnMyEnter() and OnMyTab(). An additional benefit is that we can handlemodeless child dialogs.


  1. Follow the previous item (PreTranslateMessage() etc. ) and    
     add the accelerator in the resource:
	 ///////////////////////////////////////////////////////////////////////////////// 
	 Accelerator//IDR_ACCELERATOR1 ACCELERATORS BEGIN VK_RETURN, ID_MY_ENTER, VIRTKEY, NOINVERT, 
	   VK_TAB,ID_MY_TAB,VIRTKEY, NOINVERTEND2. 
  2. Add message handler in the dialog header file:   
	 afx_msg OnMyEnter();   
	 afx_msg OnMyTab();
  3. Add Message Map and implement handler functions as follows:   
     BEGIN_MESSAGE_MAP(....Dlg, CDialog)   
	 ON_COMMAND(ID_MY_ENTER, OnMyEnter)   
	 ON_COMMAND(ID_MY_TAB, OnMyTab)   
	 ...   
	 END_MESSAGE_MAP   
	 void C...Dlg::OnMyEnter()   
	 {     
	   CWnd *pWnd = GetFocus();     
	   CWnd *pParent = pWnd->GetParent();     
	   int ID = pWnd->GetDlgCtrlID();     
	   TRACE("Enter pressed on ID = %d\n", ID);     
	   pParent->SendMessage(WM_COMMAND, ID, 0L);   
	 }   
	 
	 void C...Dlg::OnMyTab()   
	 {     
	   CWnd *pWnd = GetFocus();     
	   CWnd *pParent = pWnd->GetParent();     
	   CWnd *pWndNext = pParent->GetNextDlgTabItem(pWnd);     
	   TRACE("TAB to ID %d\n", pWndNext->GetDlgCtrlID());     
	   pWndNext->SetFocus();   
	 }
  
The reason why I have pParent is that the code can handle the case of modeless child dialog, since in that case pParent points to the child dialog instead of the parent dialog.

Top


How can I calculate the dialog unit size using pixel size?

MFC creates a dialog template with dialog unit. This width unit is the 1/4 of the average dialog font width and the height unit is the 1/8 of the average dialog font (MS San Serif). If you change the font size, then you get a bigger dialog with the same number of units in size. This seems to be a good idea but I wanted to size the window to some pixel unit.Note that the unit height and unit width are not the same! Thus if you want to create a window to fit a 640x480 image, you need to know the dialogunit size in the template or you adjust the size dynamically at runtime using GetClientRect.

  
  TEXTMETRIC metric;  
  BOOL b = dc.GetTextMetrics(&metric);  
  int xunit = MulDiv(xPixels, 4, metric.tmAveCharWidth-1);  
  int yunit = MulDiv(yPixels, 8, metric.tmAscent);
  

Note that pixel size must include the window border size. The formula was verified with MapDialogRect call. I found an article about how to get this number spit out at MSDN August 2005, C++ at Work by Paul DiLascia. The following works only for system font. You usually gets completely bad numbers, since a dialog usually uses a MS San Serif.

    
  CSize base = ::GetDialogBaseUnits();  
  xunit = MulDiv(xPixels, 4, base.cx);  
  yunit = MulDiv(yPixels, 8, base.cy);
  

Top


Where is the pen drawn? (Drawing convention)

MFC has drawing device CPen to draw lines. My question was where it is actually drawn when the pen size is only pixel wide. It turned out thatit does the right thing if the pixel coordinate in floating point unit is counted from the edge, i.e. pixel edges has integer values.


  Here is the pixel coordinate convention used:  
  floating point     0     1     2     3     4                     
                     |     |     |     |     |  
  pixel position        0     1     2     3
  

If you use a pen width to be two, then drawing at point 2 means

  
  floating point     0     1     2     3     4         
                     |     |XXXXX|XXXXX|     |  
  pixel position        0     1     2     3
  
The question is what happens if you pick a pen width to be one. It turned outthat drawing at point 2 means
  
  floating point     0     1     2     3     4            
                     |     |     |XXXXX|     |  
  pixel position        0     1     2     3
  

Top


How can I add menu bar to a dialog app?

When you create a dialog application, you won't get a menu bar. It turned out that it is easy to add a menu bar in a dialog app by add CMenu object in the dialog class and OnInitDialog()you call SetMenu().

  
  CMenu m_menu;  
  ...  
  OnInitDialog()  
  {    
    m_menu.LoadMenu(IDR_MENU_APP);    
	SetMenu(&m_menu);    
	...  
  }
  

Unfortunately OnCmdUI() is not handled in your menu items even thoughAppWizard is happy to add OnUpdate...() method to your menu item.In order to fix this problem, you must handle WM_INITMENUPOPUP message in the following way after adding handler for WM_INITMENUPOPUP:

  
  void ..Dlg::OnInitMenuPopup(CMenu *pPopupMenu, UINT nIndex, BOOL bSysMenu)  
  {    
    CCmdUI cmdUI;    
	if (bSysMenu)      
	  return;    
	cmdUI.m_nIndexMax = pPopupMenu->GetMenuItemCount();    
	for (UINT i=0; i < cmdUI.m_nIndexMax; ++i)    
	{      
	  cmdUI.m_nIndex = i;      
	  cmdUI.m_nID = pPopupMenu->GetMenuItemID(i);      
	  cmdUI.m_pMenu = pPopupMenu;      
	  cmdUI.DoUpdate(this, true);    
	}  
  }
  

For more items for dialog app, see How To ... by Gerald Dueck.

Top


Home

Updated 10/8/2008