Visual C++ MFC/ATL开发-提高篇
生活随笔
收集整理的這篇文章主要介紹了
Visual C++ MFC/ATL开发-提高篇
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
MFC簡單打印方法
通過MFC來完成打印作業有很多的方法,應用場合也有所不同。我們通常是利用視圖框架在MFC基礎之上按照OnPreparePrinting()?à?OnBeginPrinting()???à?OnPreprareDC()?à?OnPrinting?()?à?OnEndPrinting()的順序來編程的。對于我們使用單文檔或者多文檔的視圖框架時這無疑是一種很好的方式。但是,在基于對話框的應用程序中,或者在很多打印要求不高的情況下(如只是簡單的打印中間計算結果、報告程序異常等),我們就沒有必要再繞上面的那么一個大彎子,可以直接利用MFC封裝的打印對話框CPrintDialog來執行簡單的打印任務。當然,如果你不怕煩,這種方式也可以執行很復雜的打印作業。
??下面做一個簡單的例子程序。利用Visual?C++?6.0?的AppWizard生成一個對話框應用程序框架。然后,利用資源編輯器添加一個Edit控件,屬性設置為:選擇Multiline、Want?Return?,根據你的興趣選擇滾動條有關的項,綁定成員變量m_strPrintString。使“確定(OK)”按鈕不接受程序的“Enter”鍵消息,Caption改為“打印(Print)”。此鍵按下的處理函數如下:
if?(!UpdateData(TRUE))
{
AfxMessageBox("數據交換有誤!");
}
if?(m_strPrintString==_T(""))
{
AfxMessageBox("請輸入需要打印的文字?quot;);
return;
}
CPrintDialog?dlg(FALSE,
PD_NOPAGENUMS|PD_NOSELECTION,
this);
if(dlg.DoModal()?==?IDOK)
{
CDC?dc;
dc.Attach(dlg.GetPrinterDC());//把打印設備環境附加到DC對象
DOCINFO?di;?
di.cbSize?=?sizeof(DOCINFO);
di.lpszDocName?=?"SimplePrintDoc";
di.lpszOutput?=?NULL;
di.lpszDatatype?=?NULL;
di.fwType?=?0;
dc.StartDoc(&di);?//通知打印機驅動程序執行一新的打印任務
dc.StartPage();//通知打印機驅動程序打印新頁
dc.SetMapMode(MM_HIENGLISH);//設置當前影射模式為:單位0.001英寸
//X方向向右增加,Y方向向上增加
CRect?rectPrint(0,?0,
dc.GetDeviceCaps(HORZRES),//返回設備的.以毫米為單位的物理顯示寬度?
dc.GetDeviceCaps(VERTRES));//返回設備的.以毫米為單位的物理顯示高度
dc.DPtoLP(&rectPrint);//設備物理單位轉化為邏輯單位
dc.SetWindowOrg(0,?-rectPrint.bottom);//設置原點
CFont?font;
VERIFY(font.CreatePointFont(120,?
"Arial",?
&dc));//為DC創建字體
CFont*?def_font?=?dc.SelectObject(&font);//保存現在的字體
dc.SetTextAlign(TA_TOP|TA_LEFT);
CString?s?=?m_strPrintString;?//要打印的字符串(注意有長度的限制)
s?+=?"\n";?//必須增加一個換行符號(因為后面while循環的要求)
CString?ss;
int?index;
CSize?size;
int?x?=?300;
int?y?=?9000;//注意原點位置和坐標增加的方向
size?=?dc.GetTextExtent("00",?2);//計算使用當前字體輸出時文本所占大小、寬?度
while((index?=?s.Find("\n"))?!=?-1)
{
ss?=?s.Left(index);
if(ss.Find("\r")?!=?-1)//輸入的字符串有回車符
ss?=?s.Left(index-1);
s?=?s.Mid(index+1);//取剩余的字符串?//?AfxMessageBox("A"+ss+"A");
dc.TextOut(x,?y,?ss);//打印到緩沖區
y?-=?size.cy;
}
dc.SelectObject(def_font);?//恢復原來的字體
font.DeleteObject();?
dc.EndPage();?//通知打印機驅動程序頁結束
dc.EndDoc();//通知打印機驅動程序打印完畢
DeleteDC(dc.Detach());?
}
??程序代碼很簡單,一看就明白,我想應該適合大多數的簡單打印場合吧。另外,這個例子對于初學Windows編程的朋友來說,無疑也是一個理解Windows設備無關特性的好實例。
MFC文件瀏覽程序中改變文件讀寫對話框樣式
在我告訴大家怎么樣修改MFC瀏覽程序中文件讀寫對話框的缺省設置之前呢,我們應該先知道MFC是怎么樣顯示文件打開和文件保存對話框的。如果你選擇了文件菜單中的打開,那么就會有一個消息發送給CWinApp::OnFileOpen,通過一個成員變量m_pDocManager(一個指向CDocManager?對象的指針)來調用CDocManager::OnFileOpen,那么函數就會調用CDocManager的虛函數DoPromptFileName,成功后再調用CWinApp::OpenDocumentFile函數,這個文件對話框就是在DoPromptFileName的虛函數中顯示的。當我們打開的是保存對話框時,文件保存(或另存為)命令消息就會發給CDocument::OnFileSave?(or?CDocument::OnFileSaveAs),在這2種情況下,CDocument::DoSave函數都會被調用。最后,如果文件名是空的,那么CDocument::DoSave就會調用CWinApp::DoPromptFileName,使得成員變量m_pDocManager有效,并且調用CDocManager::DoPromptFileName來顯示保存對話框。?
??那么現在大家都明白了,CDocManager::DoPromptFileName函數(注意它是一個虛函數)就是負責顯示標準的文件打開和保存對話框的(可是有一個BOOL變量來決定顯示哪一個對話框的)。
??現在看起來,這好象對改變默認的對話框設置沒什么用。你可能也沒有考慮過在類CDocManager中的DoPromptFileName函數,或者不知道怎么改,來使用你自己的CDocManager類。那么你不用急,下面的代碼就是告訴你怎么自定義CDocManager。
//?CDocManager?class?declaration
//?
class?CDocManagerEx?:?public?CDocManager
{
??DECLARE_DYNAMIC(CDocManagerEx)?
//?Construction
public:
??CDocManagerEx();?
//?Attributes
public:?
//?Operations
public:?
//?Overrides
??//?helper?for?standard?commdlg?dialogs
??virtual?BOOL?DoPromptFileName(CString&?fileName,?UINT?nIDSTitle,
??????DWORD?lFlags,?BOOL?bOpenFileDialog,?CDocTemplate*?pTemplate);?
//?Implementation
public:
??virtual?~CDocManagerEx();
};?
//?DocManager.cpp?:?implementation?file
//?
#include?"stdafx.h"
#include?"PreviewFileDlg.h"
#include?"DocManager.h"?//?the?header?with?the?class?declaration?
#ifdef?_DEBUG
#define?new?DEBUG_NEW
#undef?THIS_FILE
static?char?THIS_FILE[]?=?__FILE__;
#endif?
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
#ifndef?_MAC
????ASSERT(strFilterExt[0]?==?'.');
#endif
????if?(pstrDefaultExt?!=?NULL)
????{
??????//?set?the?default?extension
#ifndef?_MAC
??????*pstrDefaultExt?=?((LPCTSTR)strFilterExt)?+?1;?//?skip?the?'.'
#else
??????*pstrDefaultExt?=?strFilterExt;
#endif
??????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
#ifndef?_MAC
????filter?+=?(TCHAR)'*';
#endif
????filter?+=?strFilterExt;
????filter?+=?(TCHAR)'\0';?//?next?string?please
????ofn.nMaxCustFilter++;
??}
}?
/
//?CDocManagerEx?
IMPLEMENT_DYNAMIC(CDocManagerEx,?CDocManager)
CDocManagerEx::CDocManagerEx()
{
}?
CDocManagerEx::~CDocManagerEx()
{
}?
BOOL?CDocManagerEx::DoPromptFileName(CString&?fileName,?UINT?nIDSTitle,?DWORD?lFlags,?BOOL?bOpenFileDialog,?CDocTemplate*?pTemplate)
{
??CPreviewFileDlg?dlgFile(bOpenFileDialog);?//?this?is?the?only?modified?line!?
??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_templateList.GetHeadPosition();
????BOOL?bFirst?=?TRUE;
????while?(pos?!=?NULL)
????{
??????CDocTemplate*?pTemplate?=?(CDocTemplate*)m_templateList.GetNext(pos);
??????AppendFilterSuffix(strFilter,?dlgFile.m_ofn,?pTemplate,
????????bFirst???&strDefault?:?NULL);
??????bFirst?=?FALSE;
????}
??}?
??//?append?the?"*.*"?all?files?filter
??CString?allFilter;
??VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER));
??strFilter?+=?allFilter;
??strFilter?+=?(TCHAR)'\0';?//?next?string?please
#ifndef?_MAC
??strFilter?+=?_T("*.*");
#else
??strFilter?+=?_T("****");
#endif
??strFilter?+=?(TCHAR)'\0';?//?last?string
??dlgFile.m_ofn.nMaxCustFilter++;?
??dlgFile.m_ofn.lpstrFilter?=?strFilter;
#ifndef?_MAC
??dlgFile.m_ofn.lpstrTitle?=?title;
#else
??dlgFile.m_ofn.lpstrPrompt?=?title;
#endif
??dlgFile.m_ofn.lpstrFile?=?fileName.GetBuffer(_MAX_PATH);?
??BOOL?bResult?=?dlgFile.DoModal()?==?IDOK???TRUE?:?FALSE;
??fileName.ReleaseBuffer();
??return?bResult;
}?
??以上代碼是從MFC原代碼中完整的拷貝過來的,當中只有一行需要改:對話框的聲明(當然,由于這是CFileDialog的子類,你可以有更多的修改權),而且對于標準對話框來說,還應該有預覽功能。AppendFilterSuffix函數是從DoPromptFileName中被調用的,原代碼可以從你的工程中獲得。如果你想顯示打開和保存對話框,你可以使用bOpenFileDialog參數,TRUE表示對話框是打開對話框,反之,亦然。
當然我們也可以使用我們自己定義的CDocManagerEx類來代替原來的CDocManager類,CWinApp是通過成員變量m_pDocManager來使用文檔管理器,所以我們必須正確的初始化這個變量。有必要特別關注一下在CWinApp::AddDocTemplate中CWinApp創建對象的原代碼,這其中有一個很普通的函數調用,我們把它給忽略了,那就是CWinApp::InitInstance函數。當m_pDocManager參數是NULL的時候,CWinApp::AddDocTemplate函數只是創建CDocManager對象,那么一旦m_pDocManager指針被正確的初始化,CWinApp::AddDocTemplate函數也就能夠被正確的調用了。因此,這最后一步就是在調用CWinApp::AddDocTemplate之前就初始化成員變量m_pDocManager,以達到我們想要的樣子。(當然你也不一定得調用CWinApp::AddDocTemplate,而是直接調用m_pDocManager->AddDocTemplate也行)
以下就是代碼:?BOOL?COurApp::InitInstance()
{
??//?Standard?initialization
??//?If?you?are?not?using?these?features?and?wish?to?reduce?the?size
??//?of?your?final?executable,?you?should?remove?from?the?following
??//?the?specific?initialization?routines?you?do?not?need.?
#ifdef?_AFXDLL
??Enable3dControls();??????//?Call?this?when?using?MFC?in?a?shared?DLL
#else
??Enable3dControlsStatic();??//?Call?this?when?linking?to?MFC?statically
#endif?
??//?Change?the?registry?key?under?which?our?settings?are?stored.
??//?You?should?modify?this?string?to?be?something?appropriate
??//?such?as?the?name?of?your?company?or?organization.
??SetRegistryKey(_T("Local?AppWizard-Generated?Applications"));?
??LoadStdProfileSettings();?//?Load?standard?INI?file?options?(including?MRU)
??//?Register?the?application's?document?templates.?Document?templates
??//?serve?as?the?connection?between?documents,?frame?windows?and?views.?
??CMultiDocTemplate*?pDocTemplate;
??pDocTemplate?=?new?CMultiDocTemplate(
????IDR_DIBTYPE,
????RUNTIME_CLASS(COurDoc),
????RUNTIME_CLASS(CChildFrame),?//?custom?MDI?child?frame
????RUNTIME_CLASS(COurView));
??ASSERT(m_pDocManager?==?NULL);
??m_pDocManager?=?new?CDocManagerEx;
??m_pDocManager->AddDocTemplate(pDocTemplate);?//?or?just?AddDocTemplate(pDocTemplate);
??//?create?main?MDI?Frame?window
??CMainFrame*?pMainFrame?=?new?CMainFrame;
??if?(!pMainFrame->LoadFrame(IDR_MAINFRAME))
????return?FALSE;
??m_pMainWnd?=?pMainFrame;?
??//?Enable?drag/drop?open
??m_pMainWnd->DragAcceptFiles();?
??//?Enable?DDE?Execute?open
??EnableShellOpen();
??RegisterShellFileTypes(TRUE);?
??//?Parse?command?line?for?standard?shell?commands,?DDE,?file?open
??CCommandLineInfo?cmdInfo;
??ParseCommandLine(cmdInfo);?
??//?Dispatch?commands?specified?on?the?command?line
??if?(!ProcessShellCommand(cmdInfo))
????return?FALSE;?
??//?The?main?window?has?been?initialized,?so?show?and?update?it.
??pMainFrame->ShowWindow(m_nCmdShow);
??pMainFrame->UpdateWindow();?
??return?TRUE;
}?
VC++5的MFC中為程序添加彈出式菜單
彈出式菜單(PopMenu)大家都熟悉,在WIN98的桌面上單擊鼠標右鍵彈出的菜單就是彈出式菜單。通常情況下,彈出式菜單在鼠標右鍵單擊時彈出,當然,也可以根據需要隨時彈出。?在VC++5的MFC中,管理菜單的類叫CMenu。下面我向大家介紹建立一個彈出式菜單的基本方法。?一、在資源編輯器中建立一個菜單資源?
?
??新建一個菜單資源,比如把菜單的ID號為IDC_POPMENU。此菜單有一項兩層,即有一個可彈出的菜單項,而此菜單項的彈出內容即為將要建立的彈出式菜單的內容。如右圖,“可彈出項”下的菜單即為將要建立的彈出式菜單的內容。實際上,“可彈出項”這個名稱在以后的操作中不會被用到,但VC++5不允許直接建立彈出式菜單,所以采用先建立“可彈出項”的方法。?
至于每一個菜單項的消息映射,與一般的菜單相同。
二、使用CMenu類對象?
??CMenu類的成員函數較多,但建立彈出式菜單只需用到其中幾個成員函數。
1、LoadMenu函數?
??原型:BOOL?LoadMenu(?UINT?nIDResource?);
??其中nIDResource是菜單資源的ID號,這里用的是剛建立的IDC_POPMENU。?
2、GetSubMenu函數?
??原型:CMenu*?GetSubMenu(?int?nPos?)?const;
??此函數用于得到子菜單的指針。nPos為層數,0為第一層子菜單……以此類推。
由于我們需要的是“可彈出項”的第一層子菜單,因此用GetSubMenu(0)來得到第一層子菜單的類指針。?
3、TrackPopupMenu函數?
??原型:BOOL?TrackPopupMenu(?UINT?nFlags,int?x,int?y,CWnd*?pWnd,LPCRECT?lpRect?=?NULL?);
??其中:
nFlags為屏幕坐標屬性和鼠標坐標屬性
屏幕坐標屬性:
TPM_CENTERALIGN?橫向將菜單以x居中
TPM_LEFTALIGN?橫向將菜單以x左對齊
TPM_RIGHTALIGN?橫向將菜單以x右對齊
鼠標按鍵屬性(只在響應WM_CONTEXTMENU消息時有效):
TPM_LEFTBUTTON?連續按??右鍵不會連續彈出菜單,鼠標右鍵不可用于選定菜單項
TPM_RIGHTBUTTON?連續按鼠標右鍵會連續彈出菜單,鼠標右鍵可用于選定菜單項
x,y均為屏幕坐標
lpRect?菜單所占的區域。如果為NULL,當用戶在菜單以外的區域按鼠標鍵時,菜單會消失
三、實例?
1、當鼠標右鍵單擊程序窗口的客戶區時,程序會收到一條WM_CONTEXTMENU消息,此時是彈出菜單的最好時機?
??用ClassWizard中的“Add?Windows?Message?Handler”功能添加對WM_CONTEXT消息的響應函數,函數中代碼如下:?
void?CMyDlg::OnContextMenu(CWnd*?pWnd,?CPoint?point)?
{
CMenu?menu;?//定義CMenu類對象
menu.LoadMenu(IDC_POPMENU);?//裝入剛建立的菜單IDC_POPMENU?menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,pWnd);?
/*GetSubMenu(0)得到IDC_POPMENU的第一層子菜單,TrackPopupMenu將菜單彈出到(x,y)處。由于設置為TPM_LEFTALIGN,所以菜單以(x,y)為左上角。*/
}
2、在其他時候彈出菜單也可以,比如,可以響應WM_LBUTTONDOWN消息。這樣,在鼠標左鍵單擊時也能彈出菜單?
??用ClassWizard中的“Add?Windows?Message?Handler”功能添加對WM_LBUTTONDOWN消息的響應函數,函數中代碼如下:?
void?CMfc5Dlg::OnLButtonDown(UINT?nFlags,?CPoint?point)?
{
??CMenu?menu;?//定義CMenu類對象?menu.LoadMenu(IDC_POPMENU);?//裝入剛建立的菜單IDC_POPMENU?ClientToScreen(&point);?menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this);?
/*GetSubMenu(0)得到IDC_POPMENU的第一層子菜單,TrackPopupMenu將菜單彈出到(x,y)處。由于設置為TPM_LEFTALIGN,所以菜單以(x,y)為左上角。*/
ScreenToClient(&point);?
CDialog::OnLButtonDown(nFlags,?point);
}
??注意:在WM_LBUTTONDOWN消息中得到的point對象所存的坐標是相對于窗口客戶區的,而TrackPopupMenu中的x,y需要是相對于屏幕的,所以需用ClientToScreen函數進行轉換,但此消息響應函數要調用CDialog::OnLButtonDown(nFlags,?point),故應該用ScreenToClient函數將point所存的坐標還原為相對于窗口客戶區的。?
談談MFC中的消息映射
引言:
??眾所周知,windows是基于消息驅動的,作好消息處理是WINDOWS編程的關鍵任務之一,用VC制作WINDOWS程式同樣離不開消息的處理。雖然VC++6的類向導可以完成絕大部分工作,但不幸的是,它并不能完成所有的工作。這就要求我們對?VC中消息的處理有一個比較清淅的認識。只有這樣才可能在必要的時候親自動手完成一些復雜的消息映射處理。
??在MFC中消息是通過一種所謂的消息映射機制來處理的。其實質是一張消息及其處理函數的一一對應表以及分析處理這張表的應用框架內部的一些程序代碼.這樣的好處是可以避免像早期的SDK編程一樣需要羅列一大堆的CASE語句來處理各種消息.由于不同種類的消息其處理方法是不同的,所以我們有必要先弄清楚?WINDOWS消息的種類。?
??背景:
??WINDOWS?消息的種類
??WINDOWS中消息主要有以下三種類型:
??1、標準的WINDOWS消息:這類消息是以WM_為前綴,不過WM_COMMAND例外。?例如:?WM_MOVE、WM_QUIT等.
??2、命令消息:命令消息以WM_COMMAND為消息名.在消息中含有命令的標志符ID,以區分具體的命令.由菜單,工具欄等命令接口對象產生.
??3、控件通知消息:控件通知消息也是以WM_COMMAND為消息名.由編輯框,列表框,子窗口發送給父窗口的通知消息.在消息中包含控件通知碼.以區分具體控件的通知消息.
??其中標準的WINDOWS消息及控件通知消息主要由窗口類即直接或間接由CWND類派生類處理.相對標準WINDOWS消息及控件通知消息而言,命令消息的處理對象范圍就廣得多.它不僅可以由窗口類處理,還可以由文檔類,文檔模板類及應用類所處理。
方法:
??不同種類消息的映射方法。
??在以上三種消息中,標準的WINDOWS消息映射是相當簡單的。可直接通過類向導完成不同消息的映射處理,所以不在本文討論之列。
??凡是從CcmdTarget類派生的類都可以有消息映射.消息映射包括如下兩方面的內容:
??在類的定義文件中(.H)中加上一條宏調用:
??DECLARE_MESSAGE_MAP()
??通常這條語句中類定義的最后.
??在類的實現文件(.CPP)中加上消息映射表:
??BEGIN_MESSAGE_MAP(類名,父類名)
????………..
???消息映射入口項.
???……….?
???END_MESSAGE_MAP(?)
??幸運的是除了某些類(如沒有基類的類或直接從CobjectO類派生的類)外.其它許多類均可由類向導生成.盡管生成的類只是一個框架,需要我們補充內容.但消息映射表已經為我們加好了.只是入口項有待我們加入.
??命令消息映射入口項是一個ON_COMMAND的宏.比如文件菜單下的"打開…"菜單(ID值為ID_FILE_OPEN)對應的消息映射入口項為:
??ON_COMMAND(ID_FILE_NEW,OnFileOpen)
??加入消息映射入口項之后需要完成消息處理函數.在類中消息處理函數都是類的成員函數,要響應一個消息,就必須定義一個該消息的處理函數.定義一個消息處理函數包括以下三方面的內容.
??1.在類定義中加入消息處理函數的函數原型(函數聲明)
??2.在類的消息映射表中加入相應的消息映射入口項.
??3.在類的實現中加入消息處理函數的函數體.
??需要說明的是消息處理函數的原型一定要以afx_msg打頭.比如:
????afx_msg?OnFileOpen();//?函數原型
????作為約定.消息處理函數一般以On打頭
??但有時我們可能想用一個消息處理函數來處理一批消息。這時類向導就無能為力了。我們必須手工加入消息映射來完成這種工作。可用如下方法實現:
??首先在處理該消息所在類的實現文件(亦即.CPP)中加入的消息映射入口:
???...
??BEGIN_MESSAGE_MAP(CMyApp,?CWinApp)
???file://{{AFX_MSG_MAP(CMyApp)
???...
???file://}}AFX_MSG_MAP
???ON_COMMAND_RANGE(ID_MYCMD_ONE,?ID_MYCMD_TEN,?OnDoSomething)
??END_MESSAGE_MAP(?)
??...
粗體標志的語句是我們加入的語句(以后約定我們加入的語句均用粗體標志).其中我們使用了宏ON_COMMAND_RANGE來實現從命令消息ID_MYCMD_ONE到?ID_MYCMD_TEN都由OnDoSomthing一個消息函數處理.注意.ID_MYCMD_ONE到?ID_MYCMD_TEN的ID值一定要連續.且ID_MYCMD_ONE值一般較小.
完成上述工作之后我們還需要在該類的頭文件(亦即.H)中加入消息處理函數的申明:
??//?Generated?message-map?functions
??protected:
??file://{{AFX_MSG(CMyApp)?
??...
??file://}}AFX_MSG
??afx_msg?void?OnDoSomething(?UINT?nID?);
??DECLARE_MESSAGE_MAP()
??由于這不是VC類向導加入的函數申明,所以放在了//}}AFX_MSG之外.
??注意這個消息處理函數有一個UINT類型參數.而處理單一命令的消息處理函數一般是沒有參數(除更新用戶接口對象狀態命令消息處理函數).這個參數的主要作用是提供用戶選擇的命令的ID值.
??最后要做的工作就是在該類的實現文件中實現該消息處理函數.?同樣,有時我們也想使用一個消息處理函數處理一批更新用戶接口對象狀態命令消息.方法同上:
??首先在.CPP文件中加入語句如下:
???...
???BEGIN_MESSAGE_MAP(CMyApp,?CWinApp)
????file://{{AFX_MSG_MAP(CMyApp)
?????...
????file://}}AFX_MSG_MAP
????ON_UPDATE_COMMAND_UI_RANGE?(ID_MYCMD_ONE,?ID_MYCMD_TEN,?OnUpdateSomething)
???END_MESSAGE_MAP(?)
????...
??在該類的頭文件(亦即.H)中加入消息處理函數的申明:
???//?Generated?message-map?functions
???protected:
???file://{{AFX_MSG(CMyApp)?
???...
???file://}}AFX_MSG
???afx_msg?void?OnUpdateSomething(?CcmdUI?*?pcmdui?);
???DECLARE_MESSAGE_MAP()
請各位注意了,仔細的讀者已經注意到這里的消息處理函數并未像命令消息處理函數需要一個額外的UINT類型的參數.原因在于pcmdui中已包含了此信息.所以不再需要這個參數了.最后不要忘了完成函數體!
??關于命令消息就討論到這個地方.接下來討論控件通知消息.
控件通知消息相對而言就復雜一點了.限于篇幅不能一一涉及.這里我們僅討論?WM_NOTIFY消息的處理.
??WM_NOTFY產生的原因如下。
??在WINDOWS3.X中控件通知它們父窗口,如鼠標點擊,控件背景繪制事件,通過發送一個消息到父窗口.簡單的通知僅發送一個WM_COMMAND消息.包含一個通知碼(比如BN_CLICKED)和一個在wParam中的控件ID及一個在lPraram中的控件句柄.因為wParam?和lParam均被使用.就沒有方法傳送其它的附加信息了.比如在BN_CLICKED?通知消息中.就沒有辦法發送關于當鼠標點擊時光標的位置信息.在這種情況下就只能使用一些特殊的消息.包括:WM_CTLCOLOR,WM_VSCROLL,?WM_HSCROLL等等.值得一提的是這些消息能被反射回發送它們的控件.就是所謂的消息反射.有興趣的讀者請參閱有關專著.
??在WIN32中同樣可以使用那些在WINDOWS3.1中使用的通知消息.不過不像過去通過增加特殊目的的消息來為新的通知發送附加的數據.而是使用一個叫?WM_NOTIFY的消息,它能以一個標準的風格傳送大量的附加數據.
WM_NOTIFY消息包含一個存在wParam中的發送消息控件的ID和一個存在?lParam中的指向一個結構體的指針.這個結構可能是NMHDR結構體.也可能是第一個成員是NMHDR的更大的結構.因為NMHDR是第一個成員,所以指向這個結構的指針也可以指向NMHDR.
??在許多情況下,這個指針將指向一個更大的結構,當你使用時必需轉換它.只有很少的通知消息.比如通用通知消息(它的名字以NM_打頭),工具提示控件的?TTN_SHOW和TTN_POP實際上在使用NMHDR結構.
??NMHDR結構包含了發送消息控件的句柄,ID及通知碼(如TTN_SHOW),其格式如下:
???Typedef?sturct?tagNMHDR{
????HWND?hwndFrom;
????UINT?idFrom;
????UINT?code;
???}?NMHDR;
??對TTN_SHOW消息而言,code成員的值將設為TTN_SHOW.
??類向導可以創建ON_NOTIFY消息映射入口并為你提供一個處理函數的框架.來處理?WM_NOTIFY類型的消息.ON_NOTIFY消息映射宏有如下語法.
???ON_NOTIFY(wNotifyCode,id,memberFxn)
??數意義如下:
???wNotifyCode:要處理的通知消息通知碼。比如:LVN_KEYDOWN.
???Id:控件標識ID.
???MemberFxn:處理此消息的成員函數.
??此成員函數必需有如下的原形申明:
???afx_msg?void?memberFxn(?NMHDR?*?pNotifyStruct,?LRESULT?*?result);?
??比如:假設你想成員函數OnKeydownList1處理ClistCtrl(標識ID=IDC_LIST1)的?LVN_KEYDOWN消息,你可以使用類向導添加如下的消息映射:
???ON_NOTIFY(?LVN_KEYDOWN,?IDC_LIST1,?OnKeydownList1?)
??在上面的例子中,類向導提供如下函數:
???void?CMessageReflectionDlg::OnKeydownList1(NMHDR*?pNMHDR,?LRESULT*?pResult)
????{
?????LV_KEYDOWN*?pLVKeyDow?=?(LV_KEYDOWN*)pNMHDR;
?????//?TODO:?Add?your?control?notification?handler
?????//?code?here
?????*pResult?=?0;
?????}
???這時類向導提供了一個適當類型的指針.你既可以通過pNMHDR,也可以通過?pLVKeyDow來訪問這個通知結構。
??如前所述,有時我們可能需要為一組控件處理相同的WM_NOTIFY消息.這時需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY.當你使用?ON_NOTIFY_RANGE時,你需要指定控件的ID范圍.其消息映射入口及函數原型如下:
???ON_NOTIFY_RANGE(?wNotifyCode,?id,?idLast,?memberFxn?)
????參數說明:
?????wNotifyCode:消息通知碼.比如:LVN_KEYDOWN,
?????id:?第一控件的標識ID。
?????idLast:最后一個控件的標識ID。(標識值一定要連續)
?????memberFxn:?消息處理函數。
????成員函數必須有如下原型申明:
????afx_msg?void?memberFxn(?UINT?id,?NMHDR?*?pNotifyStruct,?LRESULT?*?result?);
????其中id的表示發送通知消息的控件標識ID
??結束語:
??于目前介紹MFC消息映射的資料甚少.而這部分內容對編程又相當重要.本文簡要地介紹了MFC中的幾種重要的消息映射處理.但基于篇幅有限沒能作更全面更深入的探討.?
通過MFC來完成打印作業有很多的方法,應用場合也有所不同。我們通常是利用視圖框架在MFC基礎之上按照OnPreparePrinting()?à?OnBeginPrinting()???à?OnPreprareDC()?à?OnPrinting?()?à?OnEndPrinting()的順序來編程的。對于我們使用單文檔或者多文檔的視圖框架時這無疑是一種很好的方式。但是,在基于對話框的應用程序中,或者在很多打印要求不高的情況下(如只是簡單的打印中間計算結果、報告程序異常等),我們就沒有必要再繞上面的那么一個大彎子,可以直接利用MFC封裝的打印對話框CPrintDialog來執行簡單的打印任務。當然,如果你不怕煩,這種方式也可以執行很復雜的打印作業。
??下面做一個簡單的例子程序。利用Visual?C++?6.0?的AppWizard生成一個對話框應用程序框架。然后,利用資源編輯器添加一個Edit控件,屬性設置為:選擇Multiline、Want?Return?,根據你的興趣選擇滾動條有關的項,綁定成員變量m_strPrintString。使“確定(OK)”按鈕不接受程序的“Enter”鍵消息,Caption改為“打印(Print)”。此鍵按下的處理函數如下:
if?(!UpdateData(TRUE))
{
AfxMessageBox("數據交換有誤!");
}
if?(m_strPrintString==_T(""))
{
AfxMessageBox("請輸入需要打印的文字?quot;);
return;
}
CPrintDialog?dlg(FALSE,
PD_NOPAGENUMS|PD_NOSELECTION,
this);
if(dlg.DoModal()?==?IDOK)
{
CDC?dc;
dc.Attach(dlg.GetPrinterDC());//把打印設備環境附加到DC對象
DOCINFO?di;?
di.cbSize?=?sizeof(DOCINFO);
di.lpszDocName?=?"SimplePrintDoc";
di.lpszOutput?=?NULL;
di.lpszDatatype?=?NULL;
di.fwType?=?0;
dc.StartDoc(&di);?//通知打印機驅動程序執行一新的打印任務
dc.StartPage();//通知打印機驅動程序打印新頁
dc.SetMapMode(MM_HIENGLISH);//設置當前影射模式為:單位0.001英寸
//X方向向右增加,Y方向向上增加
CRect?rectPrint(0,?0,
dc.GetDeviceCaps(HORZRES),//返回設備的.以毫米為單位的物理顯示寬度?
dc.GetDeviceCaps(VERTRES));//返回設備的.以毫米為單位的物理顯示高度
dc.DPtoLP(&rectPrint);//設備物理單位轉化為邏輯單位
dc.SetWindowOrg(0,?-rectPrint.bottom);//設置原點
CFont?font;
VERIFY(font.CreatePointFont(120,?
"Arial",?
&dc));//為DC創建字體
CFont*?def_font?=?dc.SelectObject(&font);//保存現在的字體
dc.SetTextAlign(TA_TOP|TA_LEFT);
CString?s?=?m_strPrintString;?//要打印的字符串(注意有長度的限制)
s?+=?"\n";?//必須增加一個換行符號(因為后面while循環的要求)
CString?ss;
int?index;
CSize?size;
int?x?=?300;
int?y?=?9000;//注意原點位置和坐標增加的方向
size?=?dc.GetTextExtent("00",?2);//計算使用當前字體輸出時文本所占大小、寬?度
while((index?=?s.Find("\n"))?!=?-1)
{
ss?=?s.Left(index);
if(ss.Find("\r")?!=?-1)//輸入的字符串有回車符
ss?=?s.Left(index-1);
s?=?s.Mid(index+1);//取剩余的字符串?//?AfxMessageBox("A"+ss+"A");
dc.TextOut(x,?y,?ss);//打印到緩沖區
y?-=?size.cy;
}
dc.SelectObject(def_font);?//恢復原來的字體
font.DeleteObject();?
dc.EndPage();?//通知打印機驅動程序頁結束
dc.EndDoc();//通知打印機驅動程序打印完畢
DeleteDC(dc.Detach());?
}
??程序代碼很簡單,一看就明白,我想應該適合大多數的簡單打印場合吧。另外,這個例子對于初學Windows編程的朋友來說,無疑也是一個理解Windows設備無關特性的好實例。
MFC文件瀏覽程序中改變文件讀寫對話框樣式
在我告訴大家怎么樣修改MFC瀏覽程序中文件讀寫對話框的缺省設置之前呢,我們應該先知道MFC是怎么樣顯示文件打開和文件保存對話框的。如果你選擇了文件菜單中的打開,那么就會有一個消息發送給CWinApp::OnFileOpen,通過一個成員變量m_pDocManager(一個指向CDocManager?對象的指針)來調用CDocManager::OnFileOpen,那么函數就會調用CDocManager的虛函數DoPromptFileName,成功后再調用CWinApp::OpenDocumentFile函數,這個文件對話框就是在DoPromptFileName的虛函數中顯示的。當我們打開的是保存對話框時,文件保存(或另存為)命令消息就會發給CDocument::OnFileSave?(or?CDocument::OnFileSaveAs),在這2種情況下,CDocument::DoSave函數都會被調用。最后,如果文件名是空的,那么CDocument::DoSave就會調用CWinApp::DoPromptFileName,使得成員變量m_pDocManager有效,并且調用CDocManager::DoPromptFileName來顯示保存對話框。?
??那么現在大家都明白了,CDocManager::DoPromptFileName函數(注意它是一個虛函數)就是負責顯示標準的文件打開和保存對話框的(可是有一個BOOL變量來決定顯示哪一個對話框的)。
??現在看起來,這好象對改變默認的對話框設置沒什么用。你可能也沒有考慮過在類CDocManager中的DoPromptFileName函數,或者不知道怎么改,來使用你自己的CDocManager類。那么你不用急,下面的代碼就是告訴你怎么自定義CDocManager。
//?CDocManager?class?declaration
//?
class?CDocManagerEx?:?public?CDocManager
{
??DECLARE_DYNAMIC(CDocManagerEx)?
//?Construction
public:
??CDocManagerEx();?
//?Attributes
public:?
//?Operations
public:?
//?Overrides
??//?helper?for?standard?commdlg?dialogs
??virtual?BOOL?DoPromptFileName(CString&?fileName,?UINT?nIDSTitle,
??????DWORD?lFlags,?BOOL?bOpenFileDialog,?CDocTemplate*?pTemplate);?
//?Implementation
public:
??virtual?~CDocManagerEx();
};?
//?DocManager.cpp?:?implementation?file
//?
#include?"stdafx.h"
#include?"PreviewFileDlg.h"
#include?"DocManager.h"?//?the?header?with?the?class?declaration?
#ifdef?_DEBUG
#define?new?DEBUG_NEW
#undef?THIS_FILE
static?char?THIS_FILE[]?=?__FILE__;
#endif?
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
#ifndef?_MAC
????ASSERT(strFilterExt[0]?==?'.');
#endif
????if?(pstrDefaultExt?!=?NULL)
????{
??????//?set?the?default?extension
#ifndef?_MAC
??????*pstrDefaultExt?=?((LPCTSTR)strFilterExt)?+?1;?//?skip?the?'.'
#else
??????*pstrDefaultExt?=?strFilterExt;
#endif
??????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
#ifndef?_MAC
????filter?+=?(TCHAR)'*';
#endif
????filter?+=?strFilterExt;
????filter?+=?(TCHAR)'\0';?//?next?string?please
????ofn.nMaxCustFilter++;
??}
}?
/
//?CDocManagerEx?
IMPLEMENT_DYNAMIC(CDocManagerEx,?CDocManager)
CDocManagerEx::CDocManagerEx()
{
}?
CDocManagerEx::~CDocManagerEx()
{
}?
BOOL?CDocManagerEx::DoPromptFileName(CString&?fileName,?UINT?nIDSTitle,?DWORD?lFlags,?BOOL?bOpenFileDialog,?CDocTemplate*?pTemplate)
{
??CPreviewFileDlg?dlgFile(bOpenFileDialog);?//?this?is?the?only?modified?line!?
??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_templateList.GetHeadPosition();
????BOOL?bFirst?=?TRUE;
????while?(pos?!=?NULL)
????{
??????CDocTemplate*?pTemplate?=?(CDocTemplate*)m_templateList.GetNext(pos);
??????AppendFilterSuffix(strFilter,?dlgFile.m_ofn,?pTemplate,
????????bFirst???&strDefault?:?NULL);
??????bFirst?=?FALSE;
????}
??}?
??//?append?the?"*.*"?all?files?filter
??CString?allFilter;
??VERIFY(allFilter.LoadString(AFX_IDS_ALLFILTER));
??strFilter?+=?allFilter;
??strFilter?+=?(TCHAR)'\0';?//?next?string?please
#ifndef?_MAC
??strFilter?+=?_T("*.*");
#else
??strFilter?+=?_T("****");
#endif
??strFilter?+=?(TCHAR)'\0';?//?last?string
??dlgFile.m_ofn.nMaxCustFilter++;?
??dlgFile.m_ofn.lpstrFilter?=?strFilter;
#ifndef?_MAC
??dlgFile.m_ofn.lpstrTitle?=?title;
#else
??dlgFile.m_ofn.lpstrPrompt?=?title;
#endif
??dlgFile.m_ofn.lpstrFile?=?fileName.GetBuffer(_MAX_PATH);?
??BOOL?bResult?=?dlgFile.DoModal()?==?IDOK???TRUE?:?FALSE;
??fileName.ReleaseBuffer();
??return?bResult;
}?
??以上代碼是從MFC原代碼中完整的拷貝過來的,當中只有一行需要改:對話框的聲明(當然,由于這是CFileDialog的子類,你可以有更多的修改權),而且對于標準對話框來說,還應該有預覽功能。AppendFilterSuffix函數是從DoPromptFileName中被調用的,原代碼可以從你的工程中獲得。如果你想顯示打開和保存對話框,你可以使用bOpenFileDialog參數,TRUE表示對話框是打開對話框,反之,亦然。
當然我們也可以使用我們自己定義的CDocManagerEx類來代替原來的CDocManager類,CWinApp是通過成員變量m_pDocManager來使用文檔管理器,所以我們必須正確的初始化這個變量。有必要特別關注一下在CWinApp::AddDocTemplate中CWinApp創建對象的原代碼,這其中有一個很普通的函數調用,我們把它給忽略了,那就是CWinApp::InitInstance函數。當m_pDocManager參數是NULL的時候,CWinApp::AddDocTemplate函數只是創建CDocManager對象,那么一旦m_pDocManager指針被正確的初始化,CWinApp::AddDocTemplate函數也就能夠被正確的調用了。因此,這最后一步就是在調用CWinApp::AddDocTemplate之前就初始化成員變量m_pDocManager,以達到我們想要的樣子。(當然你也不一定得調用CWinApp::AddDocTemplate,而是直接調用m_pDocManager->AddDocTemplate也行)
以下就是代碼:?BOOL?COurApp::InitInstance()
{
??//?Standard?initialization
??//?If?you?are?not?using?these?features?and?wish?to?reduce?the?size
??//?of?your?final?executable,?you?should?remove?from?the?following
??//?the?specific?initialization?routines?you?do?not?need.?
#ifdef?_AFXDLL
??Enable3dControls();??????//?Call?this?when?using?MFC?in?a?shared?DLL
#else
??Enable3dControlsStatic();??//?Call?this?when?linking?to?MFC?statically
#endif?
??//?Change?the?registry?key?under?which?our?settings?are?stored.
??//?You?should?modify?this?string?to?be?something?appropriate
??//?such?as?the?name?of?your?company?or?organization.
??SetRegistryKey(_T("Local?AppWizard-Generated?Applications"));?
??LoadStdProfileSettings();?//?Load?standard?INI?file?options?(including?MRU)
??//?Register?the?application's?document?templates.?Document?templates
??//?serve?as?the?connection?between?documents,?frame?windows?and?views.?
??CMultiDocTemplate*?pDocTemplate;
??pDocTemplate?=?new?CMultiDocTemplate(
????IDR_DIBTYPE,
????RUNTIME_CLASS(COurDoc),
????RUNTIME_CLASS(CChildFrame),?//?custom?MDI?child?frame
????RUNTIME_CLASS(COurView));
??ASSERT(m_pDocManager?==?NULL);
??m_pDocManager?=?new?CDocManagerEx;
??m_pDocManager->AddDocTemplate(pDocTemplate);?//?or?just?AddDocTemplate(pDocTemplate);
??//?create?main?MDI?Frame?window
??CMainFrame*?pMainFrame?=?new?CMainFrame;
??if?(!pMainFrame->LoadFrame(IDR_MAINFRAME))
????return?FALSE;
??m_pMainWnd?=?pMainFrame;?
??//?Enable?drag/drop?open
??m_pMainWnd->DragAcceptFiles();?
??//?Enable?DDE?Execute?open
??EnableShellOpen();
??RegisterShellFileTypes(TRUE);?
??//?Parse?command?line?for?standard?shell?commands,?DDE,?file?open
??CCommandLineInfo?cmdInfo;
??ParseCommandLine(cmdInfo);?
??//?Dispatch?commands?specified?on?the?command?line
??if?(!ProcessShellCommand(cmdInfo))
????return?FALSE;?
??//?The?main?window?has?been?initialized,?so?show?and?update?it.
??pMainFrame->ShowWindow(m_nCmdShow);
??pMainFrame->UpdateWindow();?
??return?TRUE;
}?
VC++5的MFC中為程序添加彈出式菜單
彈出式菜單(PopMenu)大家都熟悉,在WIN98的桌面上單擊鼠標右鍵彈出的菜單就是彈出式菜單。通常情況下,彈出式菜單在鼠標右鍵單擊時彈出,當然,也可以根據需要隨時彈出。?在VC++5的MFC中,管理菜單的類叫CMenu。下面我向大家介紹建立一個彈出式菜單的基本方法。?一、在資源編輯器中建立一個菜單資源?
?
??新建一個菜單資源,比如把菜單的ID號為IDC_POPMENU。此菜單有一項兩層,即有一個可彈出的菜單項,而此菜單項的彈出內容即為將要建立的彈出式菜單的內容。如右圖,“可彈出項”下的菜單即為將要建立的彈出式菜單的內容。實際上,“可彈出項”這個名稱在以后的操作中不會被用到,但VC++5不允許直接建立彈出式菜單,所以采用先建立“可彈出項”的方法。?
至于每一個菜單項的消息映射,與一般的菜單相同。
二、使用CMenu類對象?
??CMenu類的成員函數較多,但建立彈出式菜單只需用到其中幾個成員函數。
1、LoadMenu函數?
??原型:BOOL?LoadMenu(?UINT?nIDResource?);
??其中nIDResource是菜單資源的ID號,這里用的是剛建立的IDC_POPMENU。?
2、GetSubMenu函數?
??原型:CMenu*?GetSubMenu(?int?nPos?)?const;
??此函數用于得到子菜單的指針。nPos為層數,0為第一層子菜單……以此類推。
由于我們需要的是“可彈出項”的第一層子菜單,因此用GetSubMenu(0)來得到第一層子菜單的類指針。?
3、TrackPopupMenu函數?
??原型:BOOL?TrackPopupMenu(?UINT?nFlags,int?x,int?y,CWnd*?pWnd,LPCRECT?lpRect?=?NULL?);
??其中:
nFlags為屏幕坐標屬性和鼠標坐標屬性
屏幕坐標屬性:
TPM_CENTERALIGN?橫向將菜單以x居中
TPM_LEFTALIGN?橫向將菜單以x左對齊
TPM_RIGHTALIGN?橫向將菜單以x右對齊
鼠標按鍵屬性(只在響應WM_CONTEXTMENU消息時有效):
TPM_LEFTBUTTON?連續按??右鍵不會連續彈出菜單,鼠標右鍵不可用于選定菜單項
TPM_RIGHTBUTTON?連續按鼠標右鍵會連續彈出菜單,鼠標右鍵可用于選定菜單項
x,y均為屏幕坐標
lpRect?菜單所占的區域。如果為NULL,當用戶在菜單以外的區域按鼠標鍵時,菜單會消失
三、實例?
1、當鼠標右鍵單擊程序窗口的客戶區時,程序會收到一條WM_CONTEXTMENU消息,此時是彈出菜單的最好時機?
??用ClassWizard中的“Add?Windows?Message?Handler”功能添加對WM_CONTEXT消息的響應函數,函數中代碼如下:?
void?CMyDlg::OnContextMenu(CWnd*?pWnd,?CPoint?point)?
{
CMenu?menu;?//定義CMenu類對象
menu.LoadMenu(IDC_POPMENU);?//裝入剛建立的菜單IDC_POPMENU?menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,pWnd);?
/*GetSubMenu(0)得到IDC_POPMENU的第一層子菜單,TrackPopupMenu將菜單彈出到(x,y)處。由于設置為TPM_LEFTALIGN,所以菜單以(x,y)為左上角。*/
}
2、在其他時候彈出菜單也可以,比如,可以響應WM_LBUTTONDOWN消息。這樣,在鼠標左鍵單擊時也能彈出菜單?
??用ClassWizard中的“Add?Windows?Message?Handler”功能添加對WM_LBUTTONDOWN消息的響應函數,函數中代碼如下:?
void?CMfc5Dlg::OnLButtonDown(UINT?nFlags,?CPoint?point)?
{
??CMenu?menu;?//定義CMenu類對象?menu.LoadMenu(IDC_POPMENU);?//裝入剛建立的菜單IDC_POPMENU?ClientToScreen(&point);?menu.GetSubMenu(0)->TrackPopupMenu(TPM_LEFTALIGN,point.x,point.y,this);?
/*GetSubMenu(0)得到IDC_POPMENU的第一層子菜單,TrackPopupMenu將菜單彈出到(x,y)處。由于設置為TPM_LEFTALIGN,所以菜單以(x,y)為左上角。*/
ScreenToClient(&point);?
CDialog::OnLButtonDown(nFlags,?point);
}
??注意:在WM_LBUTTONDOWN消息中得到的point對象所存的坐標是相對于窗口客戶區的,而TrackPopupMenu中的x,y需要是相對于屏幕的,所以需用ClientToScreen函數進行轉換,但此消息響應函數要調用CDialog::OnLButtonDown(nFlags,?point),故應該用ScreenToClient函數將point所存的坐標還原為相對于窗口客戶區的。?
談談MFC中的消息映射
引言:
??眾所周知,windows是基于消息驅動的,作好消息處理是WINDOWS編程的關鍵任務之一,用VC制作WINDOWS程式同樣離不開消息的處理。雖然VC++6的類向導可以完成絕大部分工作,但不幸的是,它并不能完成所有的工作。這就要求我們對?VC中消息的處理有一個比較清淅的認識。只有這樣才可能在必要的時候親自動手完成一些復雜的消息映射處理。
??在MFC中消息是通過一種所謂的消息映射機制來處理的。其實質是一張消息及其處理函數的一一對應表以及分析處理這張表的應用框架內部的一些程序代碼.這樣的好處是可以避免像早期的SDK編程一樣需要羅列一大堆的CASE語句來處理各種消息.由于不同種類的消息其處理方法是不同的,所以我們有必要先弄清楚?WINDOWS消息的種類。?
??背景:
??WINDOWS?消息的種類
??WINDOWS中消息主要有以下三種類型:
??1、標準的WINDOWS消息:這類消息是以WM_為前綴,不過WM_COMMAND例外。?例如:?WM_MOVE、WM_QUIT等.
??2、命令消息:命令消息以WM_COMMAND為消息名.在消息中含有命令的標志符ID,以區分具體的命令.由菜單,工具欄等命令接口對象產生.
??3、控件通知消息:控件通知消息也是以WM_COMMAND為消息名.由編輯框,列表框,子窗口發送給父窗口的通知消息.在消息中包含控件通知碼.以區分具體控件的通知消息.
??其中標準的WINDOWS消息及控件通知消息主要由窗口類即直接或間接由CWND類派生類處理.相對標準WINDOWS消息及控件通知消息而言,命令消息的處理對象范圍就廣得多.它不僅可以由窗口類處理,還可以由文檔類,文檔模板類及應用類所處理。
方法:
??不同種類消息的映射方法。
??在以上三種消息中,標準的WINDOWS消息映射是相當簡單的。可直接通過類向導完成不同消息的映射處理,所以不在本文討論之列。
??凡是從CcmdTarget類派生的類都可以有消息映射.消息映射包括如下兩方面的內容:
??在類的定義文件中(.H)中加上一條宏調用:
??DECLARE_MESSAGE_MAP()
??通常這條語句中類定義的最后.
??在類的實現文件(.CPP)中加上消息映射表:
??BEGIN_MESSAGE_MAP(類名,父類名)
????………..
???消息映射入口項.
???……….?
???END_MESSAGE_MAP(?)
??幸運的是除了某些類(如沒有基類的類或直接從CobjectO類派生的類)外.其它許多類均可由類向導生成.盡管生成的類只是一個框架,需要我們補充內容.但消息映射表已經為我們加好了.只是入口項有待我們加入.
??命令消息映射入口項是一個ON_COMMAND的宏.比如文件菜單下的"打開…"菜單(ID值為ID_FILE_OPEN)對應的消息映射入口項為:
??ON_COMMAND(ID_FILE_NEW,OnFileOpen)
??加入消息映射入口項之后需要完成消息處理函數.在類中消息處理函數都是類的成員函數,要響應一個消息,就必須定義一個該消息的處理函數.定義一個消息處理函數包括以下三方面的內容.
??1.在類定義中加入消息處理函數的函數原型(函數聲明)
??2.在類的消息映射表中加入相應的消息映射入口項.
??3.在類的實現中加入消息處理函數的函數體.
??需要說明的是消息處理函數的原型一定要以afx_msg打頭.比如:
????afx_msg?OnFileOpen();//?函數原型
????作為約定.消息處理函數一般以On打頭
??但有時我們可能想用一個消息處理函數來處理一批消息。這時類向導就無能為力了。我們必須手工加入消息映射來完成這種工作。可用如下方法實現:
??首先在處理該消息所在類的實現文件(亦即.CPP)中加入的消息映射入口:
???...
??BEGIN_MESSAGE_MAP(CMyApp,?CWinApp)
???file://{{AFX_MSG_MAP(CMyApp)
???...
???file://}}AFX_MSG_MAP
???ON_COMMAND_RANGE(ID_MYCMD_ONE,?ID_MYCMD_TEN,?OnDoSomething)
??END_MESSAGE_MAP(?)
??...
粗體標志的語句是我們加入的語句(以后約定我們加入的語句均用粗體標志).其中我們使用了宏ON_COMMAND_RANGE來實現從命令消息ID_MYCMD_ONE到?ID_MYCMD_TEN都由OnDoSomthing一個消息函數處理.注意.ID_MYCMD_ONE到?ID_MYCMD_TEN的ID值一定要連續.且ID_MYCMD_ONE值一般較小.
完成上述工作之后我們還需要在該類的頭文件(亦即.H)中加入消息處理函數的申明:
??//?Generated?message-map?functions
??protected:
??file://{{AFX_MSG(CMyApp)?
??...
??file://}}AFX_MSG
??afx_msg?void?OnDoSomething(?UINT?nID?);
??DECLARE_MESSAGE_MAP()
??由于這不是VC類向導加入的函數申明,所以放在了//}}AFX_MSG之外.
??注意這個消息處理函數有一個UINT類型參數.而處理單一命令的消息處理函數一般是沒有參數(除更新用戶接口對象狀態命令消息處理函數).這個參數的主要作用是提供用戶選擇的命令的ID值.
??最后要做的工作就是在該類的實現文件中實現該消息處理函數.?同樣,有時我們也想使用一個消息處理函數處理一批更新用戶接口對象狀態命令消息.方法同上:
??首先在.CPP文件中加入語句如下:
???...
???BEGIN_MESSAGE_MAP(CMyApp,?CWinApp)
????file://{{AFX_MSG_MAP(CMyApp)
?????...
????file://}}AFX_MSG_MAP
????ON_UPDATE_COMMAND_UI_RANGE?(ID_MYCMD_ONE,?ID_MYCMD_TEN,?OnUpdateSomething)
???END_MESSAGE_MAP(?)
????...
??在該類的頭文件(亦即.H)中加入消息處理函數的申明:
???//?Generated?message-map?functions
???protected:
???file://{{AFX_MSG(CMyApp)?
???...
???file://}}AFX_MSG
???afx_msg?void?OnUpdateSomething(?CcmdUI?*?pcmdui?);
???DECLARE_MESSAGE_MAP()
請各位注意了,仔細的讀者已經注意到這里的消息處理函數并未像命令消息處理函數需要一個額外的UINT類型的參數.原因在于pcmdui中已包含了此信息.所以不再需要這個參數了.最后不要忘了完成函數體!
??關于命令消息就討論到這個地方.接下來討論控件通知消息.
控件通知消息相對而言就復雜一點了.限于篇幅不能一一涉及.這里我們僅討論?WM_NOTIFY消息的處理.
??WM_NOTFY產生的原因如下。
??在WINDOWS3.X中控件通知它們父窗口,如鼠標點擊,控件背景繪制事件,通過發送一個消息到父窗口.簡單的通知僅發送一個WM_COMMAND消息.包含一個通知碼(比如BN_CLICKED)和一個在wParam中的控件ID及一個在lPraram中的控件句柄.因為wParam?和lParam均被使用.就沒有方法傳送其它的附加信息了.比如在BN_CLICKED?通知消息中.就沒有辦法發送關于當鼠標點擊時光標的位置信息.在這種情況下就只能使用一些特殊的消息.包括:WM_CTLCOLOR,WM_VSCROLL,?WM_HSCROLL等等.值得一提的是這些消息能被反射回發送它們的控件.就是所謂的消息反射.有興趣的讀者請參閱有關專著.
??在WIN32中同樣可以使用那些在WINDOWS3.1中使用的通知消息.不過不像過去通過增加特殊目的的消息來為新的通知發送附加的數據.而是使用一個叫?WM_NOTIFY的消息,它能以一個標準的風格傳送大量的附加數據.
WM_NOTIFY消息包含一個存在wParam中的發送消息控件的ID和一個存在?lParam中的指向一個結構體的指針.這個結構可能是NMHDR結構體.也可能是第一個成員是NMHDR的更大的結構.因為NMHDR是第一個成員,所以指向這個結構的指針也可以指向NMHDR.
??在許多情況下,這個指針將指向一個更大的結構,當你使用時必需轉換它.只有很少的通知消息.比如通用通知消息(它的名字以NM_打頭),工具提示控件的?TTN_SHOW和TTN_POP實際上在使用NMHDR結構.
??NMHDR結構包含了發送消息控件的句柄,ID及通知碼(如TTN_SHOW),其格式如下:
???Typedef?sturct?tagNMHDR{
????HWND?hwndFrom;
????UINT?idFrom;
????UINT?code;
???}?NMHDR;
??對TTN_SHOW消息而言,code成員的值將設為TTN_SHOW.
??類向導可以創建ON_NOTIFY消息映射入口并為你提供一個處理函數的框架.來處理?WM_NOTIFY類型的消息.ON_NOTIFY消息映射宏有如下語法.
???ON_NOTIFY(wNotifyCode,id,memberFxn)
??數意義如下:
???wNotifyCode:要處理的通知消息通知碼。比如:LVN_KEYDOWN.
???Id:控件標識ID.
???MemberFxn:處理此消息的成員函數.
??此成員函數必需有如下的原形申明:
???afx_msg?void?memberFxn(?NMHDR?*?pNotifyStruct,?LRESULT?*?result);?
??比如:假設你想成員函數OnKeydownList1處理ClistCtrl(標識ID=IDC_LIST1)的?LVN_KEYDOWN消息,你可以使用類向導添加如下的消息映射:
???ON_NOTIFY(?LVN_KEYDOWN,?IDC_LIST1,?OnKeydownList1?)
??在上面的例子中,類向導提供如下函數:
???void?CMessageReflectionDlg::OnKeydownList1(NMHDR*?pNMHDR,?LRESULT*?pResult)
????{
?????LV_KEYDOWN*?pLVKeyDow?=?(LV_KEYDOWN*)pNMHDR;
?????//?TODO:?Add?your?control?notification?handler
?????//?code?here
?????*pResult?=?0;
?????}
???這時類向導提供了一個適當類型的指針.你既可以通過pNMHDR,也可以通過?pLVKeyDow來訪問這個通知結構。
??如前所述,有時我們可能需要為一組控件處理相同的WM_NOTIFY消息.這時需要使用ON_NOTIFY_RANGE而不是ON_NOTIFY.當你使用?ON_NOTIFY_RANGE時,你需要指定控件的ID范圍.其消息映射入口及函數原型如下:
???ON_NOTIFY_RANGE(?wNotifyCode,?id,?idLast,?memberFxn?)
????參數說明:
?????wNotifyCode:消息通知碼.比如:LVN_KEYDOWN,
?????id:?第一控件的標識ID。
?????idLast:最后一個控件的標識ID。(標識值一定要連續)
?????memberFxn:?消息處理函數。
????成員函數必須有如下原型申明:
????afx_msg?void?memberFxn(?UINT?id,?NMHDR?*?pNotifyStruct,?LRESULT?*?result?);
????其中id的表示發送通知消息的控件標識ID
??結束語:
??于目前介紹MFC消息映射的資料甚少.而這部分內容對編程又相當重要.本文簡要地介紹了MFC中的幾種重要的消息映射處理.但基于篇幅有限沒能作更全面更深入的探討.?
轉載于:https://www.cnblogs.com/qq78292959/archive/2010/05/20/2077047.html
總結
以上是生活随笔為你收集整理的Visual C++ MFC/ATL开发-提高篇的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 有两个集合,两个集合都是10万个数据(已
- 下一篇: [导入]Linux下载工具利器ProZi