vc 入门
? Visual C++/MFC入門教程?收藏
Visual C++/MFC入門教程
VC開發指南
1.1 如何學好VC
這個問題很多朋友都問過我,當然流汗是必須的,但同時如果按照某種思路進行有計劃的學習就會起到更好的效果。萬事開頭難,為了幫助朋友們更快的掌握VC開發,下面我將自己的一點體會講一下:
1、需要有好的C/C++基礎。正所謂“磨刀不誤砍柴工”,最開始接觸VC時不要急于開始Windows程序開發,而是應該進行一些字符界面程序的編寫。這樣做的目的主要是增加對語言的熟悉程度,同時也訓練自己的思維和熟悉一些在編程中常犯的錯誤。更重要的是理解并能運用C++的各種特性,這些在以后的開發中都會有很大的幫助,特別是利用MFC進行開發的朋友對C++一定要能熟練運用。
2、理解Windows的消息機制,窗口句柄和其他GUI句柄的含義和用途。了解和MFC各個類功能相近的API函數。
3、一定要理解MFC中消息映射的作用。
4、訓練自己在編寫代碼時不使用參考書而是使用Help Online。
5、記住一些常用的消息名稱和參數的意義。
6、學會看別人的代碼。
7、多看書,少買書,買書前一定要慎重。
8、閑下來的時候就看參考書。
9、多來我的主頁。^O^
后面幾條是我個人的一點意見,你可以根據需要和自身的情況選用適用于自己的方法。
此外我將一些我在選擇參考書時的原則:
對于初學者:應該選擇一些內容比較全面的書籍,并且書籍中的內容應該以合理的方式安排,在使用該書時可以達到循序漸進的效果,書中的代碼要有詳細的講解。盡量買翻譯的書,因為這些書一般都比較易懂,而且語言比較輕松。買書前一定要慎重如果買到不好用的書可能會對自己的學習積極性產生打擊。
對于已經掌握了VC的朋友:這種程度的開發者應該加深自己對系統原理,技術要點的認識。需要選擇一些對原理講解的比較透徹的書籍,這樣一來才會對新技術有更多的了解,最好書中對技術的應用有一定的闡述。盡量選擇示范代碼必較精簡的書,可以節約銀子。
此外最好涉獵一些輔助性的書籍。
1.2 理解Windows消息機制
Windows系統是一個消息驅動的OS,什么是消息呢?我很難說得清楚,也很難下一個定義(誰在噓我),我下面從不同的幾個方面講解一下,希望大家看了后有一點了解。
1、消息的組成:一個消息由一個消息名稱(UINT),和兩個參數(WPARAM,LPARAM)。當用戶進行了輸入或是窗口的狀態發生改變時系統都會發送消息到某一個窗口。例如當菜單轉中之后會有WM_COMMAND消息發送,WPARAM的高字中(HIWORD(wParam))是命令的ID號,對菜單來講就是菜單ID。當然用戶也可以定義自己的消息名稱,也可以利用自定義消息來發送通知和傳送數據。
2、誰將收到消息:一個消息必須由一個窗口接收。在窗口的過程(WNDPROC)中可以對消息進行分析,對自己感興趣的消息進行處理。例如你希望對菜單選擇進行處理那么你可以定義對WM_COMMAND進行處理的代碼,如果希望在窗口中進行圖形輸出就必須對WM_PAINT進行處理。
3、未處理的消息到那里去了:M$為窗口編寫了默認的窗口過程,這個窗口過程將負責處理那些你不處理消息。正因為有了這個默認窗口過程我們才可以利用Windows的窗口進行開發而不必過多關注窗口各種消息的處理。例如窗口在被拖動時會有很多消息發送,而我們都可以不予理睬讓系統自己去處理。
4、窗口句柄:說到消息就不能不說窗口句柄,系統通過窗口句柄來在整個系統中唯一標識一個窗口,發送一個消息時必須指定一個窗口句柄表明該消息由那個窗口接收。而每個窗口都會有自己的窗口過程,所以用戶的輸入就會被正確的處理。例如有兩個窗口共用一個窗口過程代碼,你在窗口一上按下鼠標時消息就會通過窗口一的句柄被發送到窗口一而不是窗口二。
5、示例:下面有一段偽代碼演示如何在窗口過程中處理消息
LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
{
switch(uMessageType)
{//使用SWITCH語句將各種消息分開
case(WM_PAINT):
doYourWindow(...);//在窗口需要重新繪制時進行輸出
break;
case(WM_LBUTTONDOWN):
doYourWork(...);//在鼠標左鍵被按下時進行處理
break;
default:
callDefaultWndProc(...);//對于其它情況就讓系統自己處理
break;
}
}
接下來談談什么是消息機制:系統將會維護一個或多個消息隊列,所有產生的消息都回被放入或是插入隊列中。系統會在隊列中取出每一條消息,根據消息的接收句柄而將該消息發送給擁有該窗口的程序的消息循環。每一個運行的程序都有自己的消息循環,在循環中得到屬于自己的消息并根據接收窗口的句柄調用相應的窗口過程。而在沒有消息時消息循環就將控制權交給系統所以Windows可以同時進行多個任務。下面的偽代碼演示了消息循環的用法:
while(1)
{
id=getMessage(...);
if(id == quit)
break;
translateMessage(...);
}
當該程序沒有消息通知時getMessage就不會返回,也就不會占用系統的CPU時間。 圖示消息投遞模式
在16位的系統中系統中只有一個消息隊列,所以系統必須等待當前任務處理消息后才可以發送下一消息到相應程序,如果一個程序陷如死循環或是耗時操作時系統就會得不到控制權。這種多任務系統也就稱為協同式的多任務系統。Windows3.X就是這種系統。
而32位的系統中每一運行的程序都會有一個消息隊列,所以系統可以在多個消息隊列中轉換而不必等待當前程序完成消息處理就可以得到控制權。這種多任務系統就稱為搶先式的多任務系統。Windows95/NT就是這種系統。
?
1.3 利用Visual C++/MFC開發Windows程序的優勢
MFC借助C++的優勢為Windows開發開辟了一片新天地,同時也借助ApplicationWizzard使開發者擺脫離了那些每次都必寫基本代碼,借助ClassWizard和消息映射使開發者擺脫了定義消息處理時那種混亂和冗長的代碼段。更令人興奮的是利用C++的封裝功能使開發者擺脫Windows中各種句柄的困擾,只需要面對C++中的對象,這樣一來使開發更接近開發語言而遠離系統。(但我個人認為了解系統原理對開發很有幫助)
正因為MFC是建立在C++的基礎上,所以我強調C/C++語言基礎對開發的重要性。利用C++的封裝性開發者可以更容易理解和操作各種窗口對象;利用C++的派生性開發者可以減少開發自定義窗口的時間和創造出可重用的代碼;利用虛擬性可以在必要時更好的控制窗口的活動。而且C++本身所具備的超越C語言的特性都可以使開發者編寫出更易用,更靈活的代碼。
在MFC中對消息的處理利用了消息映射的方法,該方法的基礎是宏定義實現,通過宏定義將消息分派到不同的成員函數進行處理。下面簡單講述一下這種方法的實現方法:
代碼如下
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)?
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()?
//}}AFX_MSG_MAP
ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)
END_MESSAGE_MAP()
經過編譯后,代碼被替換為如下形式(這只是作講解,實際情況比這復雜得多):
//BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)?
CMainFrame::newWndProc(...)
{
switch(...)
{
//{{AFX_MSG_MAP(CMainFrame)
// ON_WM_CREATE()?
case(WM_CREATE):
OnCreate(...);
break;
//}}AFX_MSG_MAP
// ON_COMMAND(ID_FONT_DROPDOWN, DoNothing)
case(WM_COMMAND):
if(HIWORD(wP)==ID_FONT_DROPDOWN)
{
DoNothing(...);
}
break;
//END_MESSAGE_MAP()
}
}
newWndProc就是窗口過程只要是該類的實例生成的窗口都使用該窗口過程。
所以了解了Windows的消息機制在加上對消息映射的理解就很容易了解MFC開發的基本思路了。
1.4 利用MFC進行開發的通用方法介紹
以下是我在最初學習VC時所常用的開發思路和方法,希望能對初學VC的朋友有所幫助和啟發。
1、開發需要讀寫文件的應用程序并且有簡單的輸入和輸出可以利用單文檔視結構。
2、開發注重交互的簡單應用程序可以使用對話框為基礎的窗口,如果文件讀寫簡單這可利用CFile進行。
3、開發注重交互并且文件讀寫復雜的的簡單應用程序可以利用以CFormView為基礎視的單文檔視結構。
4、利用對話框得到用戶輸入的數據,在等級提高后可使用就地輸入。
5、在對多文檔要求不強烈時盡量避免多文檔視結構,可以利用分隔條產生單文檔多視結構。
6、在要求在多個文檔間傳遞數據時使用多文檔視結構。
7、學會利用子窗口,并在自定義的子窗口包含多個控件達到封裝功能的目的。
8、盡量避免使用多文檔多視結構。
9、不要使用多重繼承并盡量減少一個類中封裝過多的功能。
1.5 MFC中常用類,宏,函數介紹
常用類
CRect:用來表示矩形的類,擁有四個成員變量:top left bottom right。分別表是左上角和右下角的坐標。可以通過以下的方法構造:
CRect( int l, int t, int r, int b ); 指明四個坐標
CRect( const RECT& srcRect ); 由RECT結構構造
CRect( LPCRECT lpSrcRect ); 由RECT結構構造
CRect( POINT point, SIZE size ); 有左上角坐標和尺寸構造
CRect( POINT topLeft, POINT bottomRight ); 有兩點坐標構造
下面介紹幾個成員函數:
int Width( ) const; 得到寬度?
int Height( ) const; 得到高度?
CSize Size( ) const; 得到尺寸?
CPoint& TopLeft( ); 得到左上角坐標?
CPoint& BottomRight( ); 得到右下角坐標?
CPoint CenterPoint( ) const; 得當中心坐標?
此外矩形可以和點(CPoint)相加進行位移,和另一個矩形相加得到“并”操作后的矩形。
CPoint:用來表示一個點的坐標,有兩個成員變量:x y。 可以和另一個點相加。
CString:用來表示可變長度的字符串。使用CString可不指明內存大小,CString會根據需要自行分配。下面介紹幾個成員函數:
GetLength 得到字符串長度?
GetAt 得到指定位置處的字符?
operator + 相當于strcat?
void Format( LPCTSTR lpszFormat, ... ); 相當于sprintf?
Find 查找指定字符,字符串?
Compare 比較?
CompareNoCase 不區分大小寫比較?
MakeUpper 改為小寫?
MakeLower 改為大寫
CStringArray:用來表示可變長度的字符串數組。數組中每一個元素為CString對象的實例。下面介紹幾個成員函數:
Add 增加CString?
RemoveAt 刪除指定位置CString對象?
RemoveAll 刪除數組中所有CString對象?
GetAt 得到指定位置的CString對象?
SetAt 修改指定位置的CString對象?
InsertAt 在某一位置插入CString對象
常用宏
RGB
TRACE
ASSERT
VERIFY
常用函數
CWindApp* AfxGetApp();
HINSTANCE AfxGetInstanceHandle( );
HINSTANCE AfxGetResourceHandle( );
int AfxMessageBox( LPCTSTR lpszText, UINT nType = MB_OK, UINT nIDHelp = 0 );用于彈出一個消息框
2.1 和GUI有關的各種對象
在Windows中有各種GUI對象(不要和C++對象混淆),當你在進行繪圖就需要利用這些對象。而各種對象都擁有各種屬性,下面分別講述各種GUI對象和擁有的屬性。
字體對象CFont用于輸出文字時選用不同風格和大小的字體。可選擇的風格包括:是否為斜體,是否為粗體,字體名稱,是否有下劃線等。顏色和背景色不屬于字體的屬性。關于如何創建和使用字體在2.2 在窗口中輸出文字中會詳細講解。
刷子CBrush對象決定填充區域時所采用的顏色或模板。對于一個固定色的刷子來講它的屬性為顏色,是否采用網格和網格的類型如水平的,垂直的,交叉的等。你也可以利用8*8的位圖來創建一個自定義模板的刷子,在使用這種刷子填充時系統會利用位圖逐步填充區域。關于如何創建和使用刷子在2.3 使用刷子,筆進行繪圖中會詳細講解。
畫筆CPen對象在畫點和畫線時有用。它的屬性包括顏色,寬度,線的風格,如虛線,實線,點劃線等。關于如何創建和使用畫筆在2.3 使用刷子,筆進行繪圖中會詳細講解。
位圖CBitmap對象可以包含一幅圖像,可以保存在資源中。關于如何使用位圖在2.4 在窗口中繪制設備相關位圖,圖標,設備無關位圖中會詳細講解。
還有一種特殊的GUI對象是多邊形,利用多邊形可以很好的限制作圖區域或是改變窗口外型。關于如何創建和使用多邊形在2.6 多邊形和剪貼區域中會詳細講解。
在Windows中使用GUI對象必須遵守一定的規則。首先需要創建一個合法的對象,不同的對象創建方法不同。然后需要將該GUI對象選入DC中,同時保存DC中原來的GUI對象。如果選入一個非法的對象將會引起異常。在使用完后應該恢復原來的對象,這一點特別重要,如果保存一個臨時對象在DC中,而在臨時對象被銷毀后可能引起異常。有一點必須注意,每一個對象在重新創建前必須銷毀,下面的代碼演示了這一種安全的使用方法:
OnDraw(CDC* pDC)
{
CPen pen1,pen2;
pen1.CreatePen(PS_SOLID,2,RGB(128,128,128));//創建對象
pen2.CreatePen(PS_SOLID,2,RGB(128,128,0));//創建對象
CPen* pPenOld=(CPen*)pDC->SelectObject(&pen1);//選擇對象進DC
drawWithPen1...
(CPen*)pDC->SelectObject(&pen2);//選擇對象進DC
drawWithPen2...
pen1.DeleteObject();//再次創建前先銷毀
pen1.CreatePen(PS_SOLID,2,RGB(0,0,0));//再次創建對象
(CPen*)pDC->SelectObject(&pen1);//選擇對象進DC
drawWithPen1...
pDC->SelectObject(pOldPen);//恢復
}
此外系統中還擁有一些庫存GUI對象,你可以利用CDC::SelectStockObject(SelectStockObject( int nIndex )選入這些對象,它們包括一些固定顏色的刷子,畫筆和一些基本字體。
BLACK_BRUSH Black brush.
DKGRAY_BRUSH Dark gray brush.
GRAY_BRUSH Gray brush.
HOLLOW_BRUSH Hollow brush.
LTGRAY_BRUSH Light gray brush.
NULL_BRUSH Null brush.
WHITE_BRUSH White brush.
BLACK_PEN Black pen.
NULL_PEN Null pen.
WHITE_PEN White pen.
ANSI_FIXED_FONT ANSI fixed system font.
ANSI_VAR_FONT ANSI variable system font.
DEVICE_DEFAULT_FONT Device-dependent font.
OEM_FIXED_FONT OEM-dependent fixed font.
SYSTEM_FONT The system font. By default, Windows uses the system font to draw menus, dialog-box controls, and other text. In Windows versions 3.0 and later, the system font is proportional width; earlier versions of Windows use a fixed-width system font.
SYSTEM_FIXED_FONT The fixed-width system font used in Windows prior to version 3.0. This object is available for compatibility with earlier versions of Windows.
DEFAULT_PALETTE Default color palette. This palette consists of the 20 static colors in the system palette.?
這些對象留在DC中是安全的,所以你可以利用選入庫存對象來作為恢復DC中GUI對象。
大家可能都注意到了繪圖時都需要一個DC對象,DC(Device Context設備環境)對象是一個抽象的作圖環境,可能是對應屏幕,也可能是對應打印機或其它。這個環境是設備無關的,所以你在對不同的設備輸出時只需要使用不同的設備環境就行了,而作圖方式可以完全不變。這也就是Windows耀眼的一點設備無關性。如同你將對一幅畫使用照相機或復印機將會產生不同的輸出,而不需要對畫進行任何調整。DC的使用會穿插在本章中進行介紹。
2.2 在窗口中輸出文字
在這里我假定讀者已經利用ApplicationWizard生成了一個SDI界面的程序代碼。接下來的你只需要在CView派生類的OnDraw成員函數中加入繪圖代碼就可以了。在這里我需要解釋一下OnDraw函數的作用,OnDraw函數會在窗口需要重繪時自動被調用,傳入的參數CDC* pDC對應的就是DC環境。使用OnDraw的優點就在于在你使用打印功能的時候傳入OnDraw的DC環境將會是打印機繪圖環境,使用打印預覽時傳入的是一個稱為CPreviewDC的繪圖環境,所以你只需要一份代碼就可以完成窗口/打印預覽/打印機繪圖三重功能。利用Windows的設備無關性和M$為打印預覽所編寫的上千行代碼你可以很容易的完成一個具有所見即所得的軟件。
輸出文字一般使用CDC::BOOL TextOut( int x, int y, const CString& str )和CDC::int DrawText( const CString& str, LPRECT lpRect, UINT nFormat )兩個函數,對TextOut來講只能輸出單行的文字,而DrawText可以指定在一個矩形中輸出單行或多行文字,并且可以規定對齊方式和使用何種風格。nFormat可以是多種以下標記的組合(利用位或操作)以達到選擇輸出風格的目的。
DT_BOTTOM底部對齊 Specifies bottom-justified text. This value must be combined with DT_SINGLELINE.
DT_CALCRECT計算指定文字時所需要矩形尺寸 Determines the width and height of the rectangle. If there are multiple lines of text, DrawText will use the width of the rectangle pointed to by lpRect and extend the base of the rectangle to bound the last line of text. If there is only one line of text, DrawText will modify the right side of the rectangle so that it bounds the last character in the line. In either case, DrawText returns the height of the formatted text, but does not draw the text.
DT_CENTER中部對齊 Centers text horizontally.
DT_END_ELLIPSIS or DT_PATH_ELLIPSIS Replaces part of the given string with ellipses, if necessary, so that the result fits in the specified rectangle. The given string is not modified unless the DT_MODIFYSTRING flag is specified.?
You can specify DT_END_ELLIPSIS to replace characters at the end of the string, or DT_PATH_ELLIPSIS to replace characters in the middle of the string. If the string contains backslash (/) characters, DT_PATH_ELLIPSIS preserves as much as possible of the text after the last backslash.
DT_EXPANDTABS Expands tab characters. The default number of characters per tab is eight.
DT_EXTERNALLEADING Includes the font抯 external leading in the line height. Normally, external leading is not included in the height of a line of text.
DT_LEFT左對齊 Aligns text flush-left.
DT_MODIFYSTRING Modifies the given string to match the displayed text. This flag has no effect unless the DT_END_ELLIPSIS or DT_PATH_ELLIPSIS flag is specified.?
Note Some uFormat flag combinations can cause the passed string to be modified. Using DT_MODIFYSTRING with either DT_END_ELLIPSIS or DT_PATH_ELLIPSIS may cause the string to be modified, causing an assertion in the CString override.
DT_NOCLIP Draws without clipping. DrawText is somewhat faster when DT_NOCLIP is used.
DT_NOPREFIX禁止使用&前綴 Turns off processing of prefix characters. Normally, DrawText interprets the ampersand (&) mnemonic-prefix character as a directive to underscore the character that follows, and the two-ampersand (&&) mnemonic-prefix characters as a directive to print a single ampersand. By specifying DT_NOPREFIX, this processing is turned off.
DT_PATH_ELLIPSIS
DT_RIGHT右對齊 Aligns text flush-right.
DT_SINGLELINE單行輸出 Specifies single line only. Carriage returns and linefeeds do not break the line.
DT_TABSTOP設置TAB字符所占寬度 Sets tab stops. The high-order byte of nFormat is the number of characters for each tab. The default number of characters per tab is eight.
DT_TOP定部對齊 Specifies top-justified text (single line only).
DT_VCENTER中部對齊 Specifies vertically centered text (single line only).
DT_WORDBREAK每行只在單詞間被折行 Specifies word-breaking. Lines are automatically broken between words if a word would extend past the edge of the rectangle specified by lpRect. A carriage return杔inefeed sequence will also break the line.
在輸出文字時如果希望改變文字的顏色,你可以利用CDC::SetTextColor( COLORREF crColor )進行設置,如果你希望改變背景色就利用CDC::SetBkColor( COLORREF crColor ),很多時候你可能需要透明的背景色你可以利用CDC::SetBkMode( int nBkMode )設置,可接受的參數有
OPAQUE Background is filled with the current background color before the text, hatched brush, or pen is drawn. This is the default background mode.
TRANSPARENT Background is not changed before drawing.
接下來講講如何創建字體,你可以創建的字體有兩種:庫存字體CDC::CreateStockObject( int nIndex )和自定義字體。
在創建非庫存字體時需要填充一個LOGFONT結構并使用CFont::CreateFontIndirect(const LOGFONT* lpLogFont )(可以參考文章在同一系統中顯示GB字符和BIG5字符),或使用CFont::CreateFont( int nHeight, int nWidth, int nEscapement, int nOrientation, int nWeight, BYTE bItalic, BYTE bUnderline, BYTE cStrikeOut, BYTE nCharSet, BYTE nOutPrecision, BYTE nClipPrecision, BYTE nQuality, BYTE nPitchAndFamily, LPCTSTR lpszFacename )其中的參數和LOGFONT中的分量有一定的對應關系。下面分別講解參數的意義:
nHeight 字體高度(邏輯單位)等于零為缺省高度,否則取絕對值并和可用的字體高度進行匹配。
nWidth 寬度(邏輯單位)如果為零則使用可用的橫縱比進行匹配。
nEscapement 出口矢量與X軸間的角度
nOrientation 字體基線與X軸間的角度
nWeight 字體粗細,可取以下值
Constant Value?
FW_DONTCARE 0?
FW_THIN 100?
FW_EXTRALIGHT 200?
FW_ULTRALIGHT 200?
FW_LIGHT 300?
FW_NORMAL 400?
FW_REGULAR 400?
FW_MEDIUM 500?
FW_SEMIBOLD 600?
FW_DEMIBOLD 600?
FW_BOLD 700?
FW_EXTRABOLD 800?
FW_ULTRABOLD 800?
FW_BLACK 900?
FW_HEAVY 900
bItalic 是否為斜體
bUnderline 是否有下劃線
cStrikeOut 是否帶刪除線
nCharSet 指定字符集合,可取以下值
Constant Value?
ANSI_CHARSET 0?
DEFAULT_CHARSET 1?
SYMBOL_CHARSET 2?
SHIFTJIS_CHARSET 128?
OEM_CHARSET 255
nOutPrecision 輸出精度
OUT_CHARACTER_PRECIS OUT_STRING_PRECIS?
OUT_DEFAULT_PRECIS OUT_STROKE_PRECIS?
OUT_DEVICE_PRECIS OUT_TT_PRECIS?
OUT_RASTER_PRECIS
nClipPrecision 剪輯精度,可取以下值
CLIP_CHARACTER_PRECIS CLIP_MASK?
CLIP_DEFAULT_PRECIS CLIP_STROKE_PRECIS?
CLIP_ENCAPSULATE CLIP_TT_ALWAYS?
CLIP_LH_ANGLES
nQuality 輸出質量,可取以下值
DEFAULT_QUALITY Appearance of the font does not matter.
DRAFT_QUALITY Appearance of the font is less important than when PROOF_QUALITY is used. For GDI raster fonts, scaling is enabled. Bold, italic, underline, and strikeout fonts are synthesized if necessary.
PROOF_QUALITY Character quality of the font is more important than exact matching of the logical-font attributes. For GDI raster fonts, scaling is disabled and the font closest in size is chosen. Bold, italic, underline, and strikeout fonts are synthesized if necessary.?
nPitchAndFamily 字體間的間距
lpszFacename 指定字體名稱,為了得到系統所擁有的字體可以利用EmunFontFamiliesEx。(可以參考文章在同一系統中顯示GB字符和BIG5字符)
此外可以利用CFontDialog來得到用戶選擇的字體的LOGFONT數據。
最后我講一下文本坐標的計算,利用CDC::GetTextExtent( const CString& str )可以得到字符串的在輸出時所占用的寬度和高度,這樣就可以在手工輸出多行文字時使用正確的行距。另外如果需要更精確的對字體高度和寬度進行計算就需要使用CDC::GetTextMetrics( LPTEXTMETRIC lpMetrics ) 該函數將會填充TEXTMETRIC結構,該結構中的分量可以非常精確的描述字體的各種屬性。
2.3 使用點,刷子,筆進行繪圖
在Windows中畫點的方法很簡單,只需要調用COLORREF CDC::SetPixel( int x, int y, COLORREF crColor )就可以在指定點畫上指定顏色,同時返回原來的顏色。COLORREF CDC::GetPixel( int x, int y)可以得到指定點的顏色。在Windows中應該少使用畫點的函數,因為這樣做的執行效率比較低。
刷子和畫筆在Windows作圖中是使用最多的GUI對象,本節在講解刷子和畫筆使用方法的同時也講述一寫基本作圖函數。
在畫點或畫線時系統使用當前DC中的畫筆,所以在創建畫筆后必須將其選入DC才會在繪圖時產生效果。畫筆可以通過CPen對象來產生,通過調用CPen::CreatePen( int nPenStyle, int nWidth, COLORREF crColor )來創建。其中nPenStyle指名畫筆的風格,可取如下值:
PS_SOLID 實線 Creates a solid pen.
PS_DASH 虛線,寬度必須為一 Creates a dashed pen. Valid only when the pen width is 1 or less, in device units.
PS_DOT 點線,寬度必須為一 Creates a dotted pen. Valid only when the pen width is 1 or less, in device units.
PS_DASHDOT 點劃線,寬度必須為一 Creates a pen with alternating dashes and dots. Valid only when the pen width is 1 or less, in device units.
PS_DASHDOTDOT 雙點劃線,寬度必須為一 Creates a pen with alternating dashes and double dots. Valid only when the pen width is 1 or less, in device units.
PS_NULL 空線,使用時什么也不會產生 Creates a null pen.
PS_ENDCAP_ROUND 結束處為圓形 End caps are round.
PS_ENDCAP_SQUARE 結束處為方形 End caps are square.
nWidth和crColor為線的寬度和顏色。
刷子是在畫封閉曲線時用來填充的顏色,例如當你畫圓形或方形時系統會用當前的刷子對內部進行填充。刷子可利用CBrush對象產生。通過以下幾種函數創建刷子:
BOOL CreateSolidBrush( COLORREF crColor ); 創建一種固定顏色的刷子?
BOOL CreateHatchBrush( int nIndex, COLORREF crColor ); 創建指定顏色和網格的刷子,nIndex可取以下值:?
HS_BDIAGONAL Downward hatch (left to right) at 45 degrees
HS_CROSS Horizontal and vertical crosshatch
HS_DIAGCROSS Crosshatch at 45 degrees
HS_FDIAGONAL Upward hatch (left to right) at 45 degrees
HS_HORIZONTAL Horizontal hatch
HS_VERTICAL Vertical hatch?
BOOL CreatePatternBrush( CBitmap* pBitmap ); 創建以8*8位圖為模板的刷子
在選擇了畫筆和刷子后就可以利用Windows的作圖函數進行作圖了,基本的畫線函數有以下幾種
CDC::MoveTo( int x, int y ); 改變當前點的位置?
CDC::LineTo( int x, int y ); 畫一條由當前點到參數指定點的線?
CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 畫弧線?
CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 將多條線依次序連接?
基本的作圖函數有以下幾種:?
CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形?
CDC::RoundRect( LPCRECT lpRect, POINT point ); 圓角矩形?
CDC::Draw3dRect( int x, int y, int cx, int cy, COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D邊框?
CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形?
CDC::Ellipse( LPCRECT lpRect ); 橢圓形?
CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );?
CDC::Polygon( LPPOINT lpPoints, int nCount ); 多邊形?
對于矩形,圓形或類似的封閉曲線,系統會使用畫筆繪制邊緣,使用刷子填充內部。如果你不希望填充或是畫出邊緣,你可以選入空刷子(NULL_PEN)或是(NULL_BRUSH)空筆。
下面的代碼創建一條兩象素寬的實線并選入DC。并進行簡單的作圖:?
{
...
CPen pen;
pen.CreatePen(PS_SOLID,2,RGB(128,128,128));
CPen* pOldPen=(CPen*)dc.SelectObject(&pen);
dc.SelectStockObject(NULL_BRUSH);//選入空刷子
dc.Rectangle(CRect(0,0,20,20));//畫矩形
...
2.4 在窗口中繪制設備相關位圖,圖標,設備無關位圖
在Windows中可以將預先準備好的圖像復制到顯示區域中,這種內存拷貝執行起來是非常快的。在Windows中提供了兩種使用圖形拷貝的方法:通過設備相關位圖(DDB)和設備無關位圖(DIB)。
DDB可以用MFC中的CBitmap來表示,而DDB一般是存儲在資源文件中,在加載時只需要通過資源ID號就可以將圖形裝入。BOOL CBitmap::LoadBitmap( UINT nIDResource )可以裝入指定DDB,但是在繪制時必須借助另一個和當前繪圖DC兼容的內存DC來進行。通過CDC::BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop )繪制圖形,同時指定光柵操作的類型。BitBlt可以將源DC中位圖復制到目的DC中,其中前四個參數為目的區域的坐標,接下來是源DC指針,然后是源DC中的起始坐標,由于BitBlt為等比例復制,所以不需要再次指定長寬,(StretchBlt可以進行縮放)最后一個參數為光柵操作的類型,可取以下值:
BLACKNESS 輸出區域為黑色 Turns all output black.
DSTINVERT 反色輸出區域 Inverts the destination bitmap.
MERGECOPY 在源和目的間使用AND操作 Combines the pattern and the source bitmap using the Boolean AND operator.
MERGEPAINT 在反色后的目的和源間使用OR操作 Combines the inverted source bitmap with the destination bitmap using the Boolean OR operator.
NOTSRCCOPY 將反色后的源拷貝到目的區 Copies the inverted source bitmap to the destination.
PATINVERT 源和目的間進行XOR操作 Combines the destination bitmap with the pattern using the Boolean XOR operator.
SRCAND 源和目的間進行AND操作 Combines pixels of the destination and source bitmaps using the Boolean AND operator.
SRCCOPY 復制源到目的區 Copies the source bitmap to the destination bitmap.
SRCINVERT 源和目的間進行XOR操作 Combines pixels of the destination and source bitmaps using the Boolean XOR operator.
SRCPAINT 源和目的間進行OR操作 Combines pixels of the destination and source bitmaps using the Boolean OR operator.
WHITENESS 輸出區域為白色 Turns all output white.?
下面用代碼演示這種方法:?
CYourView::OnDraw(CDC* pDC)
{
CDC memDC;//定義一個兼容DC
memDC.CreateCompatibleDC(pDC);//創建DC
CBitmap bmpDraw;
bmpDraw.LoadBitmap(ID_BMP);//裝入DDB
CBitmap* pbmpOld=memDC.SelectObject(&bmpDraw);//保存原有DDB,
并選入新DDB入DC
pDC->BitBlt(0,0,20,20,&memDC,0,0,SRCCOPY);//將源DC中(0,0,20,20)
復制到目的DC(0,0,20,20)
pDC->BitBlt(20,20,40,40,&memDC,0,0,SRCAND);//將源DC中(0,0,20,20)
和目的DC(20,20,40,40)中區域進行AND操作
memDC.SelectObject(pbmpOld);//選入原DDB
}
(圖標并不是一個GDI對象,所以不需要選入DC)在MFC中沒有一個專門的圖標類,因為圖標的操作比較簡單,使用HICON CWinApp::LoadIcon( UINT nIDResource )或是HICON CWinApp::LoadStandardIcon( LPCTSTR lpszIconName ) 裝入后就可以利用BOOL CDC::DrawIcon( int x, int y, HICON hIcon )繪制。由于在圖標中可以指定透明區域,所以在某些需要使用非規則圖形而且面積不大的時候使用圖標會比較簡單。下面給出簡單的代碼:
OnDraw(CDC* pDC)
{
HICON hIcon1=AfxGetApp()->LoadIcon(IDI_I1);
HICON hIcon2=AfxGetApp()->LoadIcon(IDI_I2);
pDC->DrawIcon(0,0,hIcon1);
pDC->DrawIcon(0,40,hIcon2);
DestroyIcon(hIcon1);
DestroyIcon(hIcon2);
}
同樣在MFC也沒有提供一個DIB的類,所以在使用DIB位圖時我們需要自己讀取位圖文件中的頭信息, 并讀入數據,并利用API函數StretchDIBits繪制。位圖文件以BITMAPFILEHEADER結構開始,然后是BITMAPINFOHEADER 結構和調色版信息和數據,其實位圖格式是圖形格式中最簡單的一種,而且也是Windows可以理解的一種。我不詳細 講解DIB位圖的結構,提供一個CDib類供大家使用,這個類包含了基本的功能如:Load,Save,Draw。
2.5 使用各種映射方式
所謂的映射方式簡單點講就是坐標的安排方式,系統默認的映射方式為MM_TEXT即X坐標向右增加,Y坐標向下增加,(0,0)在屏幕左上方,DC中的每一點就是屏幕上的一個象素。也許你會認為這種方式下是最好理解的,但是一個點和象素對應的關系在屏幕上看來是正常的,但到了打印機上就會很不正常。因為我們作圖是以點為單位并且打印機的分辨率遠遠比顯示器高(800DPI 800點每英寸)所以在打印機上圖形看起來就會很小。這樣就需要為打印另做一套代碼而加大了工作量。如果每個點對應0.1毫米那么在屏幕上的圖形就會和打印出來的圖形一樣大小。
通過int CDC::SetMapMode( int nMapMode )可以指定映射方式,可用的有以下幾種:
MM_HIENGLISH 每點對應0.001英寸 Each logical unit is converted to 0.001 inch. Positive x is to the right; positive y is up.
MM_HIMETRIC 每點對應0.001毫米 Each logical unit is converted to 0.01 millimeter. Positive x is to the right; positive y is up.
MM_LOENGLISH 每點對應0.01英寸 Each logical unit is converted to 0.01 inch. Positive x is to the right; positive y is up.
MM_LOMETRIC 每點對應0.001毫米 Each logical unit is converted to 0.1 millimeter. Positive x is to the right; positive y is up.
MM_TEXT 象素對應 Each logical unit is converted to 1 device pixel. Positive x is to the right; positive y is down.
以上幾種映射默認的原點在屏幕左上方。除MM_TEXT外都為X坐標向右增加,Y坐標向上增加,和自然坐標是一致的。所以在作圖是要注意什么時候應該使用負坐標。而且以上的映射都是X-Y等比例的,即相同的長度在X,Y軸上顯示的長度都是相同的。
另外的一種映射方式為MM_ANISOTROPIC,這種方式可以規定不同的長寬比例。在設置這中映射方式后必須調用CSize CDC::SetWindowExt( SIZE size )和CSize CDC::SetViewportExt( SIZE size )來設定長寬比例。系統會根據兩次設定的長寬的比值來確定長寬比例。下面給出一段代碼比較映射前后的長寬比例:
OnDraw(CDC* pDC)
{
CRect rcC1(200,0,400,200);
pDC->FillSolidRect(rcC1,RGB(0,0,255));
pDC->SetMapMode(MM_ANISOTROPIC );
CSize sizeO;
sizeO=pDC->SetWindowExt(5,5);
TRACE("winExt %d %d/n",sizeO.cx,sizeO.cy);
sizeO=pDC->SetViewportExt(5,10);
TRACE("ViewExt %d %d/n",sizeO.cx,sizeO.cy);
CRect rcC(0,0,200,200);
pDC->FillSolidRect(rcC,RGB(0,128,0));
}
上面代碼在映射后畫出的圖形將是一個長方形。
最后講講視原點(viewport origin),你可以通過調用CPoint CDC::SetViewportOrg( POINT point )重新設置原點的位置,這就相對于對坐標進行了位移。例如你將原點設置在(20,20)那么原來的(0,0)就變成了(-20,-20)。
2.6 多邊形和剪貼區域
多邊形也是一個GDI對象,同樣遵守其他GDI對象的規則,只是通常都不將其選入DC中。在MFC中多邊形有CRgn表示。多邊形用來表示一個不同與矩形的區域,和矩形具有相似的操作。如:檢測某點是否在內部,并操作等。此外還得到一個包含此多邊形的最小矩形。下面介紹一下多邊形類的成員函數:
CreateRectRgn 由矩形創建一個多邊形?
CreateEllipticRgn 由橢圓創建一個多邊形?
CreatePolygonRgn 創建一個有多個點圍成的多邊形?
PtInRegion 某點是否在內部?
CombineRgn 兩個多邊形相并?
EqualRgn 兩個多邊形是否相等
在本節中講演多邊形的意義在于重新在窗口中作圖時提高效率。因為引發窗口重繪的原因是某個區域失效,而失效的區域用多邊形來表示。假設窗口大小為500*400當上方的另一個窗口從(0,0,10,10)移動到(20,20,30,30)這時(0,0,10,10)區域就失效了,而你只需要重繪這部分區域而不是所有區域,這樣你程序的執行效率就會提高。
通過調用API函數int GetClipRgn( HDC hdc, HRGN hrgn)就可以得到失效區域,但是一般用不著那么精確而只需得到包含該區域的最小矩形就可以了,所以可以利用int CDC::GetClipBox( LPRECT lpRect )完成這一功能。
3.1 文檔 視圖 框架窗口間的關系和消息傳送規律
在MFC中M$引入了文檔-視結構的概念,文檔相當于數據容器,視相當于查看數據的窗口或是和數據發生交互的窗口。(這一結構在MFC中的OLE,ODBC開發時又得到更多的拓展)因此一個完整的應用一般由四個類組成:CWinApp應用類,CFrameWnd窗口框架類,CDocument文檔類,CView視類。(VC6中支持創建不帶文檔-視的應用)
在程序運行時CWinApp將創建一個CFrameWnd框架窗口實例,而框架窗口將創建文檔模板,然后有文檔模板創建文檔實例和視實例,并將兩者關聯。一般來講我們只需對文檔和視進行操作,框架的各種行為已經被MFC安排好了而不需人為干預,這也是M$設計文檔-視結構的本意,讓我們將注意力放在完成任務上而從界面編寫中解放出來。
在應用中一個視對應一個文檔,但一個文檔可以包含多個視。一個應用中只用一個框架窗口,對多文檔界面來講可能有多個MDI子窗口。每一個視都是一個子窗口,在單文檔界面中父窗口即是框架窗口,在多文檔界面中父窗口為MDI子窗口。一個多文檔應用中可以包含多個文檔模板,一個模板定義了一個文檔和一個或多個視之間的對應關系。同一個文檔可以屬于多個模板,但一個模板中只允許定義一個文檔。同樣一個視也可以屬于多個文檔模板。(不知道我說清楚沒有)
接下來看看如何在程序中得到各種對象的指針:
全局函數AfxGetApp可以得到CWinApp應用類指針?
AfxGetApp()->m_pMainWnd為框架窗口指針?
在框架窗口中:CFrameWnd::GetActiveDocument得到當前活動文檔指針?
在框架窗口中:CFrameWnd::GetActiveView得到當前活動視指針?
在視中:CView::GetDocument得到對應的文檔指針?
在文檔中:CDocument::GetFirstViewPosition,CDocument::GetNextView用來遍歷所有和文檔關聯的視。?
在文檔中:CDocument::GetDocTemplate得到文檔模板指針?
在多文檔界面中:CMDIFrameWnd::MDIGetActive得到當前活動的MDI子窗口
一般來講用戶輸入消息(如菜單選擇,鼠標,鍵盤等)會先發往視,如果視未處理則會發往框架窗口。所以定義消息映射時定義在視中就可以了,如果一個應用同時擁有多個視而當前活動視沒有對消息進行處理則消息會發往框架窗口。
3.2 接收用戶輸入
在視中接收鼠標輸入:
鼠標消息是我們常需要處理的消息,消息分為:鼠標移動,按鈕按下/松開,雙擊。利用ClassWizard可以輕松的添加這幾種消息映射,下面分別講解每種消息的處理。
WM_MOUSEMOVE對應的函數為OnMouseMove( UINT nFlags, CPoint point ),nFlags表明了當前一些按鍵的消息,你可以通過“位與”操作進行檢測。
MK_CONTROL Ctrl鍵是否被按下 Set if the CTRL key is down.
MK_LBUTTON 鼠標左鍵是否被按下 Set if the left mouse button is down.
MK_MBUTTON 鼠標中間鍵是否被按下 Set if the middle mouse button is down.
MK_RBUTTON 鼠標右鍵是否被按下 Set if the right mouse button is down.
MK_SHIFT Shift鍵是否被按下 Set if the SHIFT key is down.?
point表示當前鼠標的設備坐標,坐標原點對應視左上角。
WM_LBUTTONDOWN/WM_RBUTTONDOWN(鼠標左/右鍵按下)對應的函數為OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point )參數意義和OnMouseMove相同。
WM_LBUTTONUP/WM_RBUTTONUP(鼠標左/右鍵松開)對應的函數為OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point )參數意義和OnMouseMove相同。
WM_LBUTTONDBLCLK/WM_RBUTTONDBLCLK(鼠標左/右鍵雙擊)對應的函數為OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point )參數意義和OnMouseMove相同。
下面我用一段偽代碼來講解一下這些消息的用法:
代碼的作用是用鼠標拉出一個矩形
global BOOL fDowned;//是否在拉動
global CPoint ptDown;//按下位置
global CPoint ptUp;//松開位置
OnLButtonDown(UINT nFlags, CPoint point)
{
fDowned=TRUE;
ptUp=ptDown=point;
DrawRect();
...
}
OnMouseMove(UINT nFlags, CPoint point)
{
if(fDowned)
{
DrawRect();//恢復上次所畫的矩形
ptUp=point;
DrawRect();//畫新矩形
}
}
OnLButtonUp(UINT nFlags, CPoint point)
{
if(fDowned)
{
DrawRect();//恢復上次所畫的矩形
ptUp=point;
DrawRect();//畫新矩形
fDowned=FALSE;
}
}
DrawRect()
{//以反色屏幕的方法畫出ptDown,ptUp標記的矩形
CClientDC dc(this);
MakeRect(ptDown,ptUp);
SetROP(NOT);
Rect();
}
坐標間轉換:在以上的函數中point參數對應的都是窗口的設備坐標,我們應該將設備坐標和邏輯坐標相區別,在圖32_g1由于窗口使用了滾動條,所以傳入的設備坐標是對應于當前窗口左上角的坐標,沒有考慮是否滾動,而邏輯坐標必須考慮滾動后對應的坐標,所以我以黃線虛擬的表達一個邏輯坐標的區域。可以看得出同一點在滾動后的坐標值是不同的,這一規則同樣適用于改變了映射方式的窗口,假設你將映射方式設置為每點為0.01毫米,那么設備坐標所對應的邏輯坐標也需要重新計算。進行這種轉換需要寫一段代碼,所幸的是系統提供了進行轉換的功能DC的DPtoLP,LPtoDP,下面給出代碼完成由設備坐標到邏輯坐標的轉換。
CPoint CYourView::FromDP(CPoint point)
{
CClientDC dc(this);
CPoint ptRet=point;
dc.PrepareDC();//必須先準備DC,這在使用滾動時讓DC重新計算坐標
//如果你作圖設置了不同的映射方式,則在下面需要設置
dc.SetMapMode(...)
//
dc.DPtoLP(&ptRet);//DP->LP進行轉換
return ptRet;
}
在圖32_g1中以藍線標記的是屏幕區域,紅線標記的客戶區域。利用ScreenToClient,ClientToScreen可以將坐標在這兩個區域間轉換。
在視中接收鍵盤輸入:
鍵盤消息有三個:鍵盤被按下/松開,輸入字符。其中輸入字符相當于直接得到用戶輸入的字符這在不需要處理按鍵細節時使用,而鍵盤被按下/松開在按鍵狀態改變時發送。
WM_CHAR對應的函數為OnChar( UINT nChar, UINT nRepCnt, UINT nFlags ),其中nChar為被按下的字符,nRepCnt表明在長時間為松開時相當于的按鍵次數,nFlags中的不同位代表不同的含義,在這里一般不使用。
WM_KEYDOWN/WM_KEYUP所對應的函數為OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags )nChar代表按鍵的虛擬碼值,如VK_ALT為ALT鍵,VK_CONTROL為Ctrl鍵。nFlags各位的含義如下: Value Description?
0? Scan code (OEM-dependent value).?
8 Extended key, such as a function key or a key on the numeric keypad (1 if it is an extended key).?
9?0 Not used.?
11?2 Used internally by Windows.?
13 Context code (1 if the ALT key is held down while the key is pressed; otherwise 0).?
14 Previous key state (1 if the key is down before the call, 0 if the key is up).?
15 Transition state (1 if the key is being released, 0 if the key is being pressed).
3.3 使用菜單
利用菜單接受用戶命令是一中很簡單的交互方法,同時也是一種很有效的方法。通常菜單作為一中資源存儲在文件中,因此我們可以在設計時就利用資源編輯器設計好一個菜單。關于使用VC設計菜單我就不再多講了,但你在編寫菜單時應該盡量在屬性對話框的底部提示(Prompt)處輸入文字,這雖然不是必要的,但MFC在有狀態欄和工具條的情況下會使用該文字,文字的格式為“狀態欄出說明/n工具條提示”。圖33_g1
我們要面臨的任務是如何知道用戶何時選擇了菜單,他選的是什么菜單項。當用戶選擇了一個有效的菜單項時系統會向應用發送一個WM_COMMAND消息,在消息的參數中表明來源。在MFC中我們只需要進行一次映射,將某一菜單ID映射到一處理函數,圖33_g2。在這里我們在CView的派生類中處理菜單消息,同時我對同一ID設置兩個消息映射,接下來將這兩種映射的作用。
ON_COMMAND 映射的作用為在用戶選擇該菜單時調用指定的處理函數。如:ON_COMMAND(IDM_COMMAND1, OnCommand1)會使菜單被選擇時調用OnCommand1成員函數。
ON_UPDATE_COMMAND_UI(IDM_COMMAND1, OnUpdateCommand1) 映射的作用是在菜單被顯示時通過調用指定的函數來進行確定其狀態。在這個處理函數中你可以設置菜單的允許/禁止狀態,其顯示字符串是什么,是否在前面打鉤。函數的參數為CCmdUI* pCmdUI,CCmdUI是MFC專門為更新命令提供的一個類,你可以調用
Enable 設置允許/禁止狀態?
SetCheck 設置是否在前面打鉤?
SetText 設置文字
下面我講解一個例子:我在CView派生類中有一個變量m_fSelected,并且在視中處理兩個菜單的消息,當IDM_COMMAND1被選時,對m_fSelected進行邏輯非操作,當IDM_COMMAND2被選中時出一提示;同時IDM_COMMAND1根據m_fSelected決定菜單顯示的文字和是否在前面打上檢查符號,IDM_COMMAND2根據m_fSelected的值決定菜單的允許/禁止狀態。下面是代碼和說明:
void CMenuDView::OnCommand1()?
{
m_fSelected=!m_fSelected;
TRACE("command1 selected/n");
}
void CMenuDView::OnUpdateCommand1(CCmdUI* pCmdUI)?
{
pCmdUI->SetCheck(m_fSelected);//決定檢查狀態
pCmdUI->SetText(m_fSelected?"當前被選中":"當前未被選中");//決定所顯示的文字
}
void CMenuDView::OnUpdateCommand2(CCmdUI* pCmdUI)?
{//決定是否為允許
pCmdUI->Enable(m_fSelected);
}
void CMenuDView::OnCommand2()?
{//選中時給出提示
AfxMessageBox("你選了command2");
}
接下來再講一些通過代碼操縱菜單的方法,在MFC中有一個類CMenu用來處理和菜單有關的功能。在生成一個CMenu對象時你需要從資源中裝如菜單,通過調用BOOL CMenu::LoadMenu( UINT nIDResource )進行裝入,然后你就可以對菜單進行動態的修改,所涉及到的函數有:
CMenu* GetSubMenu( int nPos ) 一位置得到子菜單的指針,因為一個CMenu對象只能表示一個彈出菜單,如果菜單中的某一項也為彈出菜單,就需要通過該函數獲取指針。?
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL ) 在末尾添加一項,nFlag為MF_SEPARATOR表示增加一個分隔條,這樣其他兩個參數將會被忽略;為MF_STRING表示添加一個菜單項uIDNewItem為該菜單的ID命令值;為MF_POPUP表示添加一個彈出菜單項,這時uIDNewItem為另一菜單的句柄HMENU。lpszNewItem為菜單文字說明。?
BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于在指定位置插入一菜單,位置由變量nPosition指明。如果nFlags包含MF_BYPOSITION則表明插入在nPosition位置,如果包含MF_BYCOMMAND表示插入在命令ID為nPosition的菜單處。?
BOOL ModifyMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem = 0, LPCTSTR lpszNewItem = NULL )用于修改某一位置的菜單,如果nFlags包含MF_BYPOSITION則表明修改nPosition位置的菜單,如果包含MF_BYCOMMAND表示修改命令ID為nPosition處的菜單。?
BOOL RemoveMenu( UINT nPosition, UINT nFlags )用于刪除某一位置的菜單。如果nFlags包含MF_BYPOSITION則表明刪除nPosition位置的菜單,如果包含MF_BYCOMMAND表示刪除命令ID為nPosition處的菜單。?
BOOL AppendMenu( UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp ) 和 BOOL InsertMenu( UINT nPosition, UINT nFlags, UINT nIDNewItem, const CBitmap* pBmp )可以添加一位圖菜單,但這樣的菜單在選中時只是反色顯示,并不美觀。(關于使用自繪OwnerDraw菜單請參考我翻譯的一篇文章自繪菜單類)
視圖中是沒有菜單的,在框架窗口中才有,所以只有用AfxGetApp()->m_pMainWnd->GetMenu()才能得到應用的菜單指針。
最后我講一下如何在程序中彈出一個菜單,你必須先裝入一個菜單資源,你必需得到一個彈出菜單的指針然后調用BOOL TrackPopupMenu( UINT nFlags, int x, int y, CWnd* pWnd, LPCRECT lpRect = NULL )彈出菜單,你需要指定(x,y)為菜單彈出的位置,pWnd為接收命令消息的窗口指針。下面有一段代碼說明方法,當然為了處理消息你應該在pWnd指明的窗口中對菜單命令消息進行映射。
CMenu menu;
menu.LoadMenu(IDR_POPUP);
CMenu* pM=menu.GetSubMenu(0);
CPoint pt;
GetCursorPos(&pt);
pM->TrackPopupMenu(TPM_LEFTALIGN,pt.x,pt.y,this);
另一種做法是通過CMenu::CreatePopupMenu()建立一個彈出菜單,然后使用TrackPopupMenu彈出菜單。使用CreatePopupMenu創建的菜單也可以將其作為一個彈出項添加另一個菜單中。下面的偽代碼演示了如何創建一個彈出菜單并進行修改后彈出:
CMenu menu1,menu2;
menu1.CreatePopupMenu
menu1.InsertMenu(1)
menu1.InsertMenu(2)
menu1.InsertMenu(3)
menu2.CreatePopupMenu
menu2.AppendMenu(MF_POPUP,1,menu1.Detach()) 將彈出菜單加入 or InsertMenu...
menu2.InsertMenu("string desc");
menu.TrackPopupMenu(...)
3.4 文檔,視,框架之間相互作用
一般來說用戶的輸入/輸出基本都是通過視進行,但一些例外的情況下可能需要和框架直接發生作用,而在多視的情況下如何在視之間傳遞數據。
在使用菜單時大家會發現當一個菜單沒有進行映射處理時為禁止狀態,在多視的情況下菜單的狀態和處理映射是和當前活動視相聯系的,這樣MFC可以保證視能正確的接收到各種消息,但有時候也會產生不便。有一個解決辦法就是在框架中對消息進行處理,這樣也可以保證當前文檔可以通過框架得到當前消息。
在用戶進行輸入后如何使視的狀態得到更新?這個問題在一個文檔對應一個視圖時是不存在的,但是現在有一個文檔對應了兩個視圖,當在一個視上進行了輸入時如何保證另一個視也得到通知呢?MFC的做法是利用文檔來處理的,因為文檔管理著當前和它聯系的視,由它來通知各個視是最合適的。讓我們同時看兩個函數:
void CView::OnUpdate( CView* pSender, LPARAM lHint, CObject* pHint )?
void CDocument::UpdateAllViews( CView* pSender, LPARAM lHint = 0L, CObject* pHint = NULL )?
當文檔的UpdateAllViews被調用時和此文檔相關的所有視的OnUpdate都會被調用,而參數lHint和pHint都會被傳遞。這樣一來發生改變視就可以通知其他的兄弟了。那么還有一個問題:如何在OnUpdate中知道是那個視圖發生了改變呢,這就可以利用pHint參數,只要調用者將this指針賦值給參數就可以了,當然完全可以利用該參數傳遞更復雜的結構。
視的初始化,當一個文檔被打開或是新建一個文檔時視圖的CView::OnInitialUpdate()會被調用,你可以通過重載該函數對視進行初始化,并在結束前調用父類的OnInitialUpdate,因為這樣可以保證OnUpdate會被調用。
文檔中內容的清除,當文檔被關閉時(比如退出或是新建前上一個文檔清除)void CDocument::DeleteContents ()會被調用,你可以通過重載該函數來進行清理工作。
在單文檔結構中上面兩點尤其重要,因為軟件運行文檔對象和視對象只會被產生并刪除一次。所以應該將上面兩點和C++對象構造和構析分清楚。
最后將一下文檔模板(DocTemplate)的作用,文檔模板分為兩類單文檔模板和多文檔模板,分別由CSingleDocTemplate和CMultiDocTemplate表示,模板的作用在于記錄文檔,視,框架之間的對應關系。還有一點就是模板可以記錄應用程序可以打開的文件的類型,當打開文件時會根據文檔模板中的信息選擇正確的文檔和視。模板是一個比較抽想的概念,一般來說是不需要我們直接進行操作的。
當使用者通過視修改了數據時,應該調用GetDocument()->SetModifiedFlag(TRUE)通知文檔數據已經被更新,這樣在關閉文檔時會自動詢問用戶是否保存數據。
3.5 利用序列化進行文件讀寫
在很多應用中我們需要對數據進行保存,或是從介質上讀取數據,這就涉及到文件的操作。我們可以利用各種文件存取方法完成這些工作,但MFC中也提供了一種讀寫文件的簡單方法——“序列化”。序列化機制通過更高層次的接口功能向開發者提供了更利于使用和透明于字節流的文件操縱方法,舉一個例來講你可以將一個字串寫入文件而不需要理會具體長度,讀出時也是一樣。你甚至可以對字符串數組進行操作。在MFC提供的可自動分配內存的類的支持下你可以更輕松的讀/寫數據。你也可以根據需要編寫你自己的具有序列化功能的類。
序列化在最低的層次上應該被需要序列化的類支持,也就是說如果你需要對一個類進行序列化,那么這個類必須支持序列化。當通過序列化進行文件讀寫時你只需要該類的序列化函數就可以了。
怎樣使類具有序列化功能呢?你需要以下的工作:
該類從CObject派生。?
在類聲明中包括DECLARE_SERIAL宏定義。?
提供一個缺省的構造函數。?
在類中實現Serialze函數?
使用IMPLEMENT_SERIAL指明類名和版本號
下面的代碼建立了一個簡單身份證記錄的類,同時也能夠支持序列化。
in H
struct strPID
{
char szName[10];
char szID[16];
struct strPID* pNext;
};
class CAllPID : public CObject
{
public:
DECLARE_SERIAL(CAllPID)
CAllPID();
~CAllPID();
public:// 序列化相關?
struct strPID* pHead;
//其他的成員函數
void Serialize(CArchive& ar);
};
in CPP
IMPLEMENT_SERIAL(CAllPID,CObject,1) // version is 1,版本用于讀數據時的檢測
void CAllPID::Serialize(CArchive& ar)
{
int iTotal;
if(ar.IsStoring())
{//保存數據
iTotal=GetTotalID();//得到鏈表中的記錄數量
arr<26;i++)
ar<<&(((BYTE*)pItem)+i);//寫一個strPID中所有的數據
}
}
else
{//讀數據
ar>>iTotal;
for(int i=0;i26;j++)
ar>>*(((BYTE*)pID)+j);//讀一個strPID中所有的數據
//修改鏈表
}
}
}
當然上面的代碼很不完整,但已經可以說明問題。這樣CAllPID就是一個可以支持序列化的類,并且可以根據記錄的數量動態分配內存。在序列化中我們使用了CArchive類,該類用于在序列化時提供讀寫支持,它重載了<<和>>運算符號,并且提供Read和Write函數對數據進行讀寫。
下面看看如何在文檔中使用序列化功能,你只需要修改文檔類的Serialize(CArchive& ar)函數,并調用各個進行序列化的類的Serial進行數據讀寫就可以了。當然你也可以在文檔類的內部進行數據讀寫,下面的代碼利用序列化功能讀寫數據:
class CYourDoc : public CDocument
{
void Serialize(CArchive& ar);
CString m_szDesc;
CAllPID m_allPID;
......
}
void CYourDoc::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{//由于CString對CArchive定義了<<和>>操作符號,所以可以直接利用>>和<<
ar<>m_szDesc;
}
m_allPID.Serialize(ar);//調用數據類的序列化函數
}?
3.6 MFC中所提供的各種視類介紹
MFC中提供了豐富的視類供開發者使用,下面對各個類進行介紹:
CView類是最基本的視類只支持最基本的操作。
CScrollView類提供了滾動的功能,你可以利用void CScrollView::SetScrollSizes( int nMapMode, SIZE sizeTotal, const SIZE& sizePage = sizeDefault, const SIZE& sizeLine = sizeDefault )設置滾動尺寸,和坐標映射模式。但是在繪圖和接收用戶輸入時需要對坐標進行轉換。請參見3.2 接收用戶輸入。
CFormView類提供用戶在資源文件中定義界面的能力,并可以將子窗口和變量進行綁定。通過UpdateData函數讓數據在變量和子窗口間交換。
CTreeView類利用TreeCtrl界面作為視界面,通過調用CTreeCtrl&?CTreeView::GetTreeCtrl( ) const得到CTreeCtrl的引用。
CListView類利用ListCtrl界面作為視界面,通過調用CTreeCtrl&?CTreeView::GetTreeCtrl( ) const得到CListCtrl的引用。
CEditView類利用Edit接收用戶輸入,它具有輸入框的一切功能。通過調用CEdit& CEditView::GetEditCtrl( ) const得到Edit&的引用。void CEditView::SetPrinterFont( CFont* pFont )可以設置打印字體。
CRichEditView類作為Rich Text Edit(富文本輸入)的視類,提供了可以按照格式顯示文本的能力,在使用時需要CRichEditDoc的支持。
4.1 Button
按鈕窗口(控件)在MFC中使用CButton表示,CButton包含了三種樣式的按鈕,Push Button,Check Box,Radio Box。所以在利用CButton對象生成按鈕窗口時需要指明按鈕的風格。
創建按鈕:BOOL CButton::Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );其中lpszCaption是按鈕上顯示的文字,dwStyle為按鈕風格,除了Windows風格可以使用外(如WS_CHILD|WS_VISUBLE|WS_BORDER)還有按鈕專用的一些風格。
BS_AUTOCHECKBOX 檢查框,按鈕的狀態會自動改變 Same as a check box, except that a check mark appears in the check box when the user selects the box; the check mark disappears the next time the user selects the box.
BS_AUTORADIOBUTTON 圓形選擇按鈕,按鈕的狀態會自動改變 Same as a radio button, except that when the user selects it, the button automatically highlights itself and removes the selection from any other radio buttons with the same style in the same group.
BS_AUTO3STATE 允許按鈕有三種狀態即:選中,未選中,未定 Same as a three-state check box, except that the box changes its state when the user selects it.
BS_CHECKBOX 檢查框 Creates a small square that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style).
BS_DEFPUSHBUTTON 默認普通按鈕 Creates a button that has a heavy black border. The user can select this button by pressing the ENTER key. This style enables the user to quickly select the most likely option (the default option).
BS_LEFTTEXT 左對齊文字 When combined with a radio-button or check-box style, the text appears on the left side of the radio button or check box.
BS_OWNERDRAW 自繪按鈕 Creates an owner-drawn button. The framework calls the DrawItem member function when a visual aspect of the button has changed. This style must be set when using the CBitmapButton class.
BS_PUSHBUTTON 普通按鈕 Creates a pushbutton that posts a WM_COMMAND message to the owner window when the user selects the button.
BS_RADIOBUTTON 圓形選擇按鈕 Creates a small circle that has text displayed to its right (unless this style is combined with the BS_LEFTTEXT style). Radio buttons are usually used in groups of related but mutually exclusive choices.
BS_3STATE 允許按鈕有三種狀態即:選中,未選中,未定 Same as a check box, except that the box can be dimmed as well as checked. The dimmed state typically is used to show that a check box has been disabled.?
rect為窗口所占據的矩形區域,pParentWnd為父窗口指針,nID為該窗口的ID值。
獲取/改變按鈕狀態:對于檢查按鈕和圓形按鈕可能有兩種狀態,選中和未選中,如果設置了BS_3STATE或BS_AUTO3STATE風格就可能出現第三種狀態:未定,這時按鈕顯示灰色。通過調用int CButton::GetCheck( ) 得到當前是否被選中,返回0:未選中,1:選中,2:未定。調用void CButton::SetCheck( int nCheck );設置當前選中狀態。
處理按鈕消息:要處理按鈕消息需要在父窗口中進行消息映射,映射宏為ON_BN_CLICKED( id, memberFxn )id為按鈕的ID值,就是創建時指定的nID值。處理函數原型為afx_msg void memberFxn( );
4.2 Static Box
靜態文本控件的功能比較簡單,可作為顯示字符串,圖標,位圖用。創建一個窗口可以使用成員函數:?
BOOL CStatic::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );?
其中dwStyle將指明該窗口的風格,除了子窗口常用的風格WS_CHILD,WS_VISIBLE外,你可以針對靜態控件指明專門的風格。
SS_CENTER,SS_LEFT,SS_RIGHT 指明字符顯示的對齊方式。?
SS_GRAYRECT 顯示一個灰色的矩形?
SS_NOPREFIX 如果指明該風格,對于字符&將直接顯示,否則&將作為轉義符,&將不顯示而在其后的字符將有下劃線,如果需要直接顯示&必須使用&&表示。?
SS_BITMAP 顯示位圖?
SS_ICON 顯示圖標?
SS_CENTERIMAGE 圖象居中顯示
控制顯示的文本利用成員函數SetWindowText/GetWindowText用于設置/得到當前顯示的文本。
控制顯示的圖標利用成員函數SetIcon/GetIcon用于設置/得到當前顯示的圖標。
控制顯示的位圖利用成員函數SetBitmap/GetBitmap用于設置/得到當前顯示的位圖。下面一段代碼演示如何創建一個顯示位圖的靜態窗口并設置位圖
CStatic* pstaDis=new CStatic;
pstaDis->Create("",WS_CHILD|WS_VISIBLE|SS_BITMAP|SSCENTERIMAGE,
CRect(0,0,40,40),pWnd,1);
CBitmap bmpLoad;
bmpLoad.LoadBitmap(IDB_TEST);
pstaDis->SetBitmap(bmpLoad.Detach());
4.3 Edit Box
Edit窗口是用來接收用戶輸入最常用的一個控件。創建一個輸入窗口可以使用成員函數:?
BOOL CEdit::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );?
其中dwStyle將指明該窗口的風格,除了子窗口常用的風格WS_CHILD,WS_VISIBLE外,你可以針對輸入控件指明專門的風格。
ES_AUTOHSCROLL,ES_AUTOVSCROLL 指明輸入文字超出顯示范圍時自動滾動。?
ES_CENTER,ES_LEFT,ES_RIGHT 指定對齊方式?
ES_MULTILINE 是否允許多行輸入?
ES_PASSWORD 是否為密碼輸入框,如果指明該風格則輸入的文字顯示為*?
ES_READONLY 是否為只讀?
ES_UPPERCASE,ES_LOWERCASE 顯示大寫/小寫字符
控制顯示的文本利用成員函數SetWindowText/GetWindowText用于設置/得到當前顯示的文本。
通過GetLimitText/SetLimitText可以得到/設置在輸入框中輸入的字符數量。
由于在輸入時用戶可能選擇某一段文本,所以通過void CEdit::GetSel( int& nStartChar, int& nEndChar )得到用戶選擇的字符范圍,通過調用void CEdit::SetSel( int nStartChar, int nEndChar, BOOL bNoScroll = FALSE )可以設置當前選擇的文本范圍,如果指定nStartChar=0 nEndChar=-1則表示選中所有的文本。void ReplaceSel( LPCTSTR lpszNewText, BOOL bCanUndo = FALSE )可以將選中的文本替換為指定的文字。
此外輸入框還有一些和剪貼板有關的功能,void Clear( );刪除選中的文本,void Copy( );可將選中的文本送入剪貼板,void Paste( );將剪貼板中內容插入到當前輸入框中光標位置,void Cut( );相當于Copy和Clear結合使用。
最后介紹一下輸入框幾種常用的消息映射宏:
ON_EN_CHANGE 輸入框中文字更新后產生?
ON_EN_ERRSPACE 輸入框無法分配內存時產生?
ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在輸入框失去/得到輸入焦點時產生?
使用以上幾種消息映射的方法為定義原型如:afx_msg void memberFxn( );的函數,并且定義形式如ON_Notification( id, memberFxn )的消息映射。如果在對話框中使用輸入框,Class Wizard會自動列出相關的消息,并能自動產生消息映射代碼。
4.5 List Box/Check List Box
ListBox窗口用來列出一系列的文本,每條文本占一行。創建一個列表窗口可以使用成員函數:?
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );?
其中dwStyle將指明該窗口的風格,除了子窗口常用的風格WS_CHILD,WS_VISIBLE外,你可以針對列表控件指明專門的風格。
LBS_MULTIPLESEL 指明列表框可以同時選擇多行?
LBS_EXTENDEDSEL 可以通過按下Shift/Ctrl鍵選擇多行?
LBS_SORT 所有的行按照字母順序進行排序
在列表框生成后需要向其中加入或是刪除行,可以利用:?
int AddString( LPCTSTR lpszItem )添加行,?
int DeleteString( UINT nIndex )刪除指定行,?
int InsertString( int nIndex, LPCTSTR lpszItem )將行插入到指定位置。?
void ResetContent( )可以刪除列表框中所有行。?
通過調用int GetCount( )得到當前列表框中行的數量。
如果需要得到/設置當前被選中的行,可以調用int GetCurSel( )/int SetCurSel(int iIndex)。如果你指明了選擇多行的風格,你就需要先調用int GetSelCount( )得到被選中的行的數量,然后int GetSelItems( int nMaxItems, LPINT rgIndex )得到所有選中的行,參數rgIndex為存放被選中行的數組。通過調用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框內指定行的字符串。
此外通過調用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在當前所有行中查找指定的字符傳的位置,nStartAfter指明從那一行開始進行查找。?
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以選中包含指定字符串的行。
在MFC 4.2版本中添加了CCheckListBox類,該類是由CListBox派生并擁有CListBox的所有功能,不同的是可以在每行前加上一個檢查框。必須注意的是在創建時必須指明LBS_OWNERDRAWFIXED或LBS_OWNERDRAWVARIABLE風格。
通過void SetCheckStyle( UINT nStyle )/UINT GetCheckStyle( )可以設置/得到檢查框的風格,關于檢查框風格可以參考4.1 Button中介紹。通過void SetCheck( int nIndex, int nCheck )/int GetCheck( int nIndex )可以設置和得到某行的檢查狀態,關于檢查框狀態可以參考4.1 Button中介紹。
最后介紹一下列表框幾種常用的消息映射宏:
ON_LBN_DBLCLK 鼠標雙擊?
ON_EN_ERRSPACE 輸入框無法分配內存時產生?
ON_EN_KILLFOCUS / ON_EN_SETFOCUS 在輸入框失去/得到輸入焦點時產生?
ON_LBN_SELCHANGE 選擇的行發生改變?
使用以上幾種消息映射的方法為定義原型如:afx_msg void memberFxn( );的函數,并且定義形式如ON_Notification( id, memberFxn )的消息映射。如果在對話框中使用列表框,Class Wizard會自動列出相關的消息,并能自動產生消息映射代碼。
4.6 Combo Box
組合窗口是由一個輸入框和一個列表框組成。創建一個組合窗口可以使用成員函數:?
BOOL CListBox::Create( LPCTSTR lpszText, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID = 0xffff );?
其中dwStyle將指明該窗口的風格,除了子窗口常用的風格WS_CHILD,WS_VISIBLE外,你可以針對列表控件指明專門的風格。
CBS_DROPDOWN 下拉式組合框?
CBS_DROPDOWNLIST 下拉式組合框,但是輸入框內不能進行輸入?
CBS_SIMPLE 輸入框和列表框同時被顯示?
LBS_SORT 所有的行按照字母順序進行排序
由于組合框內包含了列表框,所以列表框的功能都能夠使用,如可以利用:?
int AddString( LPCTSTR lpszItem )添加行,?
int DeleteString( UINT nIndex )刪除指定行,?
int InsertString( int nIndex, LPCTSTR lpszItem )將行插入到指定位置。?
void ResetContent( )可以刪除列表框中所有行。?
通過調用int GetCount( )得到當前列表框中行的數量。
如果需要得到/設置當前被選中的行的位置,可以調用int GetCurSel( )/int SetCurSel(int iIndex)。通過調用int GetLBText( int nIndex, LPTSTR lpszText )得到列表框內指定行的字符串。
此外通過調用int FindString( int nStartAfter, LPCTSTR lpszItem )可以在當前所有行中查找指定的字符傳的位置,nStartAfter指明從那一行開始進行查找。?
int SelectString( int nStartAfter, LPCTSTR lpszItem )可以選中包含指定字符串的行。
此外輸入框的功能都能夠使用,如可以利用:?
DWORD GetEditSel( ) /BOOL SetEditSel( int nStartChar, int nEndChar )得到或設置輸入框中被選中的字符位置。?
BOOL LimitText( int nMaxChars )設置輸入框中可輸入的最大字符數。?
輸入框的剪貼板功能Copy,Clear,Cut,Paste動可以使用。
最后介紹一下列表框幾種常用的消息映射宏:
ON_CBN_DBLCLK 鼠標雙擊?
ON_CBN_DROPDOWN 列表框被彈出?
ON_CBN_KILLFOCUS / ON_CBN_SETFOCUS 在輸入框失去/得到輸入焦點時產生?
ON_CBN_SELCHANGE 列表框中選擇的行發生改變?
ON_CBN_EDITUPDATE 輸入框中內容被更新?
使用以上幾種消息映射的方法為定義原型如:afx_msg void memberFxn( );的函數,并且定義形式如ON_Notification( id, memberFxn )的消息映射。如果在對話框中使用組合框,Class Wizard會自動列出相關的消息,并能自動產生消息映射代碼。
4.7 Tree Ctrl
樹形控件TreeCtrl和下節要講的列表控件 ListCtrl在系統中大量被使用,例如Windows資源管理器就是一個典型的例子。
樹形控件可以用于樹形的結構,其中有一個根接點(Root)然后下面有許多子結點,而每個子結點上有允許有一個或多個或沒有子結點。MFC中使用CTreeCtrl類來封裝樹形控件的各種操作。通過調用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );創建一個窗口,dwStyle中可以使用以下一些樹形控件的專用風格:
TVS_HASLINES 在父/子結點之間繪制連線?
TVS_LINESATROOT 在根/子結點之間繪制連線?
TVS_HASBUTTONS 在每一個結點前添加一個按鈕,用于表示當前結點是否已被展開?
TVS_EDITLABELS 結點的顯示字符可以被編輯?
TVS_SHOWSELALWAYS 在失去焦點時也顯示當前選中的結點?
TVS_DISABLEDRAGDROP 不允許Drag/Drop?
TVS_NOTOOLTIPS 不使用ToolTip顯示結點的顯示字符?
在樹形控件中每一個結點都有一個句柄(HTREEITEM),同時添加結點時必須提供的參數是該結點的父結點句柄,(其中根Root結點只有一個,既不可以添加也不可以刪除)利用
HTREEITEM InsertItem( LPCTSTR lpszItem, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST );可以添加一個結點,pszItem為顯示的字符,hParent代表父結點的句柄,當前添加的結點會排在hInsertAfter表示的結點的后面,返回值為當前創建的結點的句柄。下面的代碼會建立一個如下形式的樹形結構: +--- Parent1 +--- Child1_1 +--- Child1_2 +--- Child1_3 +--- Parent2 +--- Parent3 /*假設m_tree為一個CTreeCtrl對象,而且該窗口已經創建*/ HTREEITEM hItem,hSubItem; hItem = m_tree.InsertItem("Parent1",TVI_ROOT); 在根結點上添加Parent1 hSubItem = m_tree.InsertItem("Child1_1",hItem); //在Parent1上添加一個子結點 hSubItem = m_tree.InsertItem("Child1_2",hItem,hSubItem);//在Parent1上添加一個子結點,排在Child1_1后面 hSubItem = m_tree.InsertItem("Child1_3",hItem,hSubItem); hItem = m_tree.InsertItem("Parent2",TVI_ROOT,hItem); hItem = m_tree.InsertItem("Parent3",TVI_ROOT,hItem); 如果你希望在每個結點前添加一個小圖標,就必需先調用CImageList* SetImageList( CImageList * pImageList, int nImageListType );指明當前所使用的ImageList,nImageListType為TVSIL_NORMAL。在調用完成后控件中使用圖片以設置的ImageList中圖片為準。然后調用
HTREEITEM InsertItem( LPCTSTR lpszItem, int nImage, int nSelectedImage, HTREEITEM hParent = TVI_ROOT, HTREEITEM hInsertAfter = TVI_LAST);添加結點,nImage為結點沒被選中時所使用圖片序號,nSelectedImage為結點被選中時所使用圖片序號。下面的代碼演示了ImageList的設置。 /*m_list 為CImageList對象 IDB_TREE 為16*(16*4)的位圖,每個圖片為16*16共4個圖標*/ m_list.Create(IDB_TREE,16,4,RGB(0,0,0)); m_tree.SetImageList(&m_list,TVSIL_NORMAL); m_tree.InsertItem("Parent1",0,1);//添加, 選中時顯示圖標1,未選中時顯示圖標0
此外CTreeCtrl還提供了一些函數用于得到/修改控件的狀態。?
HTREEITEM GetSelectedItem( );將返回當前選中的結點的句柄。BOOL SelectItem( HTREEITEM hItem );將選中指明結點。?
BOOL GetItemImage( HTREEITEM hItem, int& nImage, int& nSelectedImage ) / BOOL SetItemImage( HTREEITEM hItem, int nImage, int nSelectedImage )用于得到/修改某結點所使用圖標索引。?
CString GetItemText( HTREEITEM hItem ) /BOOL SetItemText( HTREEITEM hItem, LPCTSTR lpszItem );用于得到/修改某一結點的顯示字符。?
BOOL DeleteItem( HTREEITEM hItem );用于刪除某一結點,BOOL DeleteAllItems( );將刪除所有結點。
此外如果想遍歷樹可以使用下面的函數:?
HTREEITEM GetRootItem( );得到根結點。?
HTREEITEM GetChildItem( HTREEITEM hItem );得到子結點。?
HTREEITEM GetPrevSiblingItem/GetNextSiblingItem( HTREEITEM hItem );得到指明結點的上/下一個兄弟結點。?
HTREEITEM GetParentItem( HTREEITEM hItem );得到父結點。
樹形控件的消息映射使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產生該消息的窗口ID,memberFxn為處理函數,函數的原型如同void OnXXXTree(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數據結構,在具體使用時需要轉換成其他類型的結構。對于樹形控件可能取值和對應的數據結構為:
TVN_SELCHANGED 在所選中的結點發生改變后發送,所用結構:NMTREEVIEW?
TVN_ITEMEXPANDED 在某結點被展開后發送,所用結構:NMTREEVIEW?
TVN_BEGINLABELEDIT 在開始編輯結點字符時發送,所用結構:NMTVDISPINFO?
TVN_ENDLABELEDIT 在結束編輯結點字符時發送,所用結構:NMTVDISPINFO?
TVN_GETDISPINFO 在需要得到某結點信息時發送,(如得到結點的顯示字符)所用結構:NMTVDISPINFO?
關于ON_NOTIFY有很多內容,將在以后的內容中進行詳細講解。
關于動態提供結點所顯示的字符:首先你在添加結點時需要指明lpszItem參數為:LPSTR_TEXTCALLBACK。在控件顯示該結點時會通過發送TVN_GETDISPINFO來取得所需要的字符,在處理該消息時先將參數pNMHDR轉換為LPNMTVDISPINFO,然后填充其中item.pszText。但是我們通過什么來知道該結點所對應的信息呢,我的做法是在添加結點后設置其lParam參數,然后在提供信息時利用該參數來查找所對應的信息。下面的代碼說明了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //添加結點 HTREEITEM hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 0 ); hItem = m_tree.InsertItem(LPSTR_TEXTCALLBACK,...) m_tree.SetItemData(hItem, 1 ); //處理消息 void CParentWnd::OnGetDispInfoTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; pTVDI->item.pszText=szOut[pTVDI->item.lParam];//通過lParam得到 需要顯示的字符在數組中的位置 *pResult = 0; }
關于編輯結點的顯示字符:首先需要設置樹形控件的TVS_EDITLABELS風格,在開始編輯時該控件將會發送TVN_BEGINLABELEDIT,你可以通過在處理函數中返回TRUE來取消接下來的編輯,在編輯完成后會發送TVN_ENDLABELEDIT,在處理該消息時需要將參數pNMHDR轉換為LPNMTVDISPINFO,然后通過其中的item.pszText得到編輯后的字符,并重置顯示字符。如果編輯在中途中取消該變量為NULL。下面的代碼說明如何處理這些消息: //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.lParam==0);//判斷是否取消該操作 *pResult = 1; else *pResult = 0; } //處理消息 TVN_BEGINLABELEDIT void CParentWnd::OnBeginEditTree(NMHDR* pNMHDR, LRESULT* pResult) { TV_DISPINFO* pTVDI = (TV_DISPINFO*)pNMHDR; if(pTVDI->item.pszText==NULL);//判斷是否已經取消取消編輯?m_tree.SetItemText(pTVDI->item.hItem,pTVDI->pszText);//重置顯示字符 *pResult = 0; } 上面講述的方法所進行的消息映射必須在父窗口中進行(同樣WM_NOTIFY的所有消息都需要在父窗口中處理)。
4.8 List Ctrl
列表控件可以看作是功能增強的ListBox,它提供了四種風格,而且可以同時顯示一列的多中屬性值。MFC中使用CListCtrl類來封裝列表控件的各種操作。通過調用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );創建一個窗口,dwStyle中可以使用以下一些列表控件的專用風格:
LVS_ICON LVS_SMALLICON LVS_LIST LVS_REPORT 這四種風格決定控件的外觀,同時只可以選擇其中一種,分別對應:大圖標顯示,小圖標顯示,列表顯示,詳細報表顯示?
LVS_EDITLABELS 結點的顯示字符可以被編輯,對于報表風格來講可編輯的只為第一列。?
LVS_SHOWSELALWAYS 在失去焦點時也顯示當前選中的結點?
LVS_SINGLESEL 同時只能選中列表中一項?
首先你需要設置列表控件所使用的ImageList,如果你使用大圖標顯示風格,你就需要以如下形式調用:?
CImageList* SetImageList( CImageList* pImageList, LVSIL_NORMAL);?
如果使用其它三種風格顯示而不想顯示圖標你可以不進行任何設置,否則需要以如下形式調用:?
CImageList* SetImageList( CImageList* pImageList, LVSIL_SMALL);
通過調用int InsertItem( int nItem, LPCTSTR lpszItem );可以在列表控件中nItem指明位置插入一項,lpszItem為顯示字符。除LVS_REPORT風格外其他三種風格都只需要直接調用InsertItem就可以了,但如果使用報表風格就必須先設置列表控件中的列信息。
通過調用int InsertColumn( int nCol, LPCTSTR lpszColumnHeading, int nFormat , int nWidth, int nSubItem);可以插入列。iCol為列的位置,從零開始,lpszColumnHeading為顯示的列名,nFormat為顯示對齊方式,nWidth為顯示寬度,nSubItem為分配給該列的列索引。
在有多列的列表控件中就需要為每一項指明其在每一列中的顯示字符,通過調用?
BOOL SetItemText( int nItem, int nSubItem, LPTSTR lpszText );可以設置每列的顯示字符。nItem為設置的項的位置,nSubItem為列位置,lpszText為顯示字符。下面的代碼演示了如何設置多列并插入數據: m_list.SetImageList(&m_listSmall,LVSIL_SMALL);//設置ImageList m_list.InsertColumn(0,"Col 1",LVCFMT_LEFT,300,0);//設置列 m_list.InsertColumn(1,"Col 2",LVCFMT_LEFT,300,1); m_list.InsertColumn(2,"Col 3",LVCFMT_LEFT,300,2); m_list.InsertItem(0,"Item 1_1");//插入行 m_list.SetItemText(0,1,"Item 1_2");//設置該行的不同列的顯示字符 m_list.SetItemText(0,2,"Item 1_3");
此外CListCtrl還提供了一些函數用于得到/修改控件的狀態。?
COLORREF GetTextColor( )/BOOL SetTextColor( COLORREF cr );用于得到/設置顯示的字符顏色。?
COLORREF GetTextBkColor( )/BOOL SetTextBkColor( COLORREF cr );用于得到/設置顯示的背景顏色。?
void SetItemCount( int iCount );用于得到添加進列表中項的數量。?
BOOL DeleteItem(int nItem);用于刪除某一項,BOOL DeleteAllItems( );將刪除所有項。?
BOOL SetBkImage(HBITMAP hbm, BOOL fTile , int xOffsetPercent, int yOffsetPercent);用于設置背景位圖。?
CString GetItemText( int nItem, int nSubItem );用于得到某項的顯示字符。
列表控件的消息映射同樣使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產生該消息的窗口ID,memberFxn為處理函數,函數的原型如同void OnXXXList(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數據結構,在具體使用時需要轉換成其他類型的結構。對于列表控件可能取值和對應的數據結構為:
LVN_BEGINLABELEDIT 在開始某項編輯字符時發送,所用結構:NMLVDISPINFO?
LVN_ENDLABELEDIT 在結束某項編輯字符時發送,所用結構:NMLVDISPINFO?
LVN_GETDISPINFO 在需要得到某項信息時發送,(如得到某項的顯示字符)所用結構:NMLVDISPINFO?
關于ON_NOTIFY有很多內容,將在以后的內容中進行詳細講解。
關于動態提供結點所顯示的字符:首先你在項時需要指明lpszItem參數為:LPSTR_TEXTCALLBACK。在控件顯示該結點時會通過發送TVN_GETDISPINFO來取得所需要的字符,在處理該消息時先將參數pNMHDR轉換為LPNMLVDISPINFO,然后填充其中item.pszText。通過item中的iItem,iSubItem可以知道當前顯示的為那一項。下面的代碼演示了這種方法: char szOut[8][3]={"No.1","No.2","No.3"}; //添加結點 m_list.InsertItem(LPSTR_TEXTCALLBACK,...) m_list.InsertItem(LPSTR_TEXTCALLBACK,...) //處理消息 void CParentWnd::OnGetDispInfoList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; pLVDI->item.pszText=szOut[pTVDI->item.iItem];//通過iItem得到需要 顯示的字符在數組中的位置 *pResult = 0; }
關于編輯某項的顯示字符:(在報表風格中只對第一列有效)首先需要設置列表控件的LVS_EDITLABELS風格,在開始編輯時該控件將會發送LVN_BEGINLABELEDIT,你可以通過在處理函數中返回TRUE來取消接下來的編輯,在編輯完成后會發送LVN_ENDLABELEDIT,在處理該消息時需要將參數pNMHDR轉換為LPNMLVDISPINFO,然后通過其中的item.pszText得到編輯后的字符,并重置顯示字符。如果編輯在中途中取消該變量為NULL。下面的代碼說明如何處理這些消息: //處理消息 LVN_BEGINLABELEDIT void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; if(pLVDI->item.iItem==0);//判斷是否取消該操作 *pResult = 1; else *pResult = 0; } //處理消息 LVN_BEGINLABELEDIT void CParentWnd::OnBeginEditList(NMHDR* pNMHDR, LRESULT* pResult) { LV_DISPINFO* pLVDI = (LV_DISPINFO*)pNMHDR; if(pLVDI->item.pszText==NULL);//判斷是否已經取消取消編輯?m_list.SetItemText(pLVDI->item.iItem,0,pLVDI->pszText);//重置顯示字符 *pResult = 0; } 上面講述的方法所進行的消息映射必須在父窗口中進行(同樣WM_NOTIFY的所有消息都需要在父窗口中處理)。
如何得到當前選中項位置:在列表控件中沒有一個類似于ListBox中GetCurSel()的函數,但是可以通過調用GetNextItem( -1, LVNI_ALL | LVNI_SELECTED);得到選中項位置。
4.9 Tab Ctrl
Tab屬性頁控件可以在一個窗口中添加不同的頁面,然后在頁選擇發生改變時得到通知。MFC中使用CTabCtrl類來封裝屬性頁控件的各種操作。通過調用
BOOL Create( DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );創建一個窗口,dwStyle中可以使用以下一些屬性頁控件的專用風格:
TCS_BUTTONS 使用按鈕來表示頁選擇位置?
TCS_MULTILINE 分行顯示頁選擇位置?
TCS_SINGLELINE 只使用一行顯示頁選擇位置?
在控件創建后必需向其中添加頁面才可以使用,添加頁面的函數為:?
BOOL InsertItem( int nItem, LPCTSTR lpszItem );nItem為位置,從零開始,lpszItem為頁選擇位置上顯示的文字。如果你希望在頁選擇位置處顯示一個圖標,你可以調用?
BOOL InsertItem( int nItem, LPCTSTR lpszItem, int nImage );nImage指明所使用的圖片位置。(在此之前必須調用CImageList * SetImageList( CImageList * pImageList );設置正確的ImageList)
此外CTabCtrl還提供了一些函數用于得到/修改控件的狀態。?
int GetCurSel( )/int SetCurSel( int nItem );用于得到/設置當前被選中的頁位置。?
BOOL DeleteItem( int nItem )/BOOL DeleteAllItems( );用于刪除指定/所有頁面。?
void RemoveImage( int nImage );用于刪除某頁選擇位置上的圖標。
屬性頁控件的消息映射同樣使用ON_NOTIFY宏,形式如同:ON_NOTIFY( wNotifyCode, id, memberFxn ),wNotifyCode為通知代碼,id為產生該消息的窗口ID,memberFxn為處理函數,函數的原型如同void OnXXXTab(NMHDR* pNMHDR, LRESULT* pResult),其中pNMHDR為一數據結構,在具體使用時需要轉換成其他類型的結構。對于列表控件可能取值和對應的數據結構為:
TCN_SELCHANGE 在當前頁改變后發送,所用結構:NMHDR?
TCN_SELCHANGING 在當前頁改變時發送可以通過返回TRUE來禁止頁面的改變,所用結構:NMHDR
一般來講在當前頁發生改變時需要隱藏當前的一些子窗口,并顯示其它的子窗口。下面的偽代碼演示了如何使用屬性頁控件:
CParentWnd::OnCreate(...)
{
m_tab.Create(...);
m_tab.InsertItem(0,"Option 1");
m_tab.InsertItem(1,"Option 2");
Create a edit box as the m_tab's Child
Create a static box as the m_tab's Child
edit_box.ShowWindow(SW_SHOW); // edit box在屬性頁的第一頁
static_box.ShowWindow(SW_HIDE); // static box在屬性頁的第二頁
}
void CParentWnd::OnSelectChangeTab(NMHDR* pNMHDR, LRESULT* pResult)
{//處理頁選擇改變后的消息
if(m_tab.GetCurSel()==0)
{//根據當前頁顯示/隱藏不同的子窗口
edit_box.ShowWindow(SW_SHOW);
static_box.ShowWindow(SW_HIDE);
}
else
{//
edit_box.ShowWindow(SW_HIDE);
static_box.ShowWindow(SW_SHOW);
}
}
4.A Tool Bar
工具條也是常用的控件。MFC中使用CToolBar類來封裝工具條控件的各種操作。通過調用
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_TOP, UINT nID = AFX_IDW_TOOLBAR );創建一個窗口,dwStyle中可以使用以下一些工具條控件的專用風格:
CBRS_TOP 工具條在父窗口的頂部?
TCBRS_BOTTOM 工具條在父窗口的底部?
CBRS_FLOATING 工具條是浮動的?
創建一個工具條的步驟如下:先使用Create創建窗口,然后使用BOOL LoadToolBar( LPCTSTR lpszResourceName );直接從資源中裝入工具條,或者通過裝入位圖并指明每個按鈕的ID,具體代碼如下:
UINT uID[5]={IDM_1,IDM_2,IDM_3,IDM_4,IDM_5};
m_toolbar.Create(pParentWnd);
m_toolbar.LoadBitmap(IDB_TOOLBAR);
m_toolbar.SetSizes(CSize(20,20),CSize(16,16));//設置按鈕大尺寸
和按鈕上位圖的尺寸
m_toolbar.SetButtons(uID,5);
AppWizard在生成代碼時也會同時生成工具條的代碼,同時還可以支持停靠功能。所以一般是不需要直接操作工具條對象。
工具條上的按鈕被按下時發送給父窗口的消息和菜單消息相同,所以可以使用ON_COMMAND宏進行映射,同樣工具條中的按鈕也支持ON_UPDATE_COMMAND_UI的相關操作,如SetCheck,Enable,你可以將按鈕的當作菜單上的一個具有相同ID菜單項。
在以后的章節4.D 利用AppWizard創建并使用ToolBar StatusBar Dialog Bar會給出使用的方法。
4.B Status Bar
狀態條用于顯示一些提示字符。MFC中使用CStatusBar類來封裝狀態條控件的各種操作。通過調用
BOOL Create( CWnd* pParentWnd, DWORD dwStyle = WS_CHILD | WS_VISIBLE | CBRS_BOTTOM, UINT nID = AFX_IDW_STATUS_BAR );創建一個窗口,dwStyle中可以使用以下一些狀態條控件的專用風格:
CBRS_TOP 狀態條在父窗口的頂部?
TCBRS_BOTTOM 狀態條在父窗口的底部?
創建一個狀態條的步驟如下:先使用Create創建窗口,然后調用BOOL SetIndicators( const UINT* lpIDArray, int nIDCount );設置狀態條上各部分的ID,具體代碼如下:
UINT uID[2]={ID_SEPARATOR,ID_INDICATOR_CAPS};
m_stabar.Create(pParentWnd);
m_stabar.SetIndicators(uID,2);
通過CString GetPaneText( int nIndex )/BOOL SetPaneText( int nIndex, LPCTSTR lpszNewText, BOOL bUpdate = TRUE )可以得到/設置狀態條上顯示的文字。
Tip:在創建狀態條時最好將狀態條中所有的部分ID(除MFC自定義的幾個用于狀態條的ID外)都設置為ID_SEPARATOR,在生成后調用
void SetPaneInfo( int nIndex, UINT nID, UINT nStyle, int cxWidth );改變其風格,ID和寬度。
AppWizard在生成代碼時也會同時生成狀態條的代碼。所以一般是不需要直接創建狀態條對象。此外狀態條上會自動顯示菜單上的命令提示(必須先在資源中定義),所以也不需要人為設置顯示文字。
狀態條支持ON_UPDATE_COMMAND_UI的相關操作,如SetText,Enable。
在以后的章節4.D 利用AppWizard創建并使用ToolBar StatusBar Dialog Bar會給出使用的方法。
4.C Dialog Bar
Dialog Bar類似一個靜態的附在框架窗口上的對話框,由于Dialog Bar可以使用資源編輯器進行編輯所以使用起來就很方便,在設計時就可以對Dialog Bar上的子窗口進行定位。用于顯示一些提示字符。MFC中使用CDialogBar類來Dialog Bar控件的各種操作。通過調用
BOOL Create( CWnd* pParentWnd, UINT nIDTemplate, UINT nStyle, UINT nID );創建一個窗口,nIDTemplate為對話框資源,nID為該Dialog Bar對應的窗口ID,nStyle中可以使用以下一些狀態條控件的專用風格:
CBRS_TOP Dialog Bar在父窗口的頂部?
TCBRS_BOTTOM Dialog Bar在父窗口的底部?
CBRS_LEFT Dialog Bar在父窗口的左部?
CBRS_RIGHT Dialog Bar在父窗口的右部?
對于Dialog Bar的所產生消息需要在父窗口中進行映射和處理,例如Dialog Bar上的按鈕,需要在父窗口中進行ON_BN_CLICKED或ON_COMMAND映射,Dialog Bar上的輸入框可以在父窗口中進行ON_EN_CHANGE,ON_EN_MAXTEXT等輸入框對應的映射。
Dialog Bar支持ON_UPDATE_COMMAND_UI的相關操作,如SetText,Enable。
在以后的章節4.D 利用AppWizard創建并使用ToolBar StatusBar Dialog Bar會給出使用的方法。
4.D 利用AppWizard創建并使用ToolBar StatusBar Dialog Bar
運行時程序界面如界面圖,該程序擁有一個工具條用于顯示兩個命令按鈕,一個用于演示如何使按鈕處于檢查狀態,另一個根據第一個按鈕的狀態來禁止/允許自身。(設置檢查狀態和允許狀態都通過OnUpdateCommand實現)此外Dialog Bar上有一個輸入框和按鈕,這兩個子窗口的禁止/允許同樣是根據工具條上的按鈕狀態來確定,當按下Dialog Bar上的按鈕時將顯示輸入框中的文字內容。狀態條的第一部分用于顯示各種提示,第二部分用于利用OnUpdateCommand顯示當前時間。同時在程序中演示了如何設置菜單項的命令解釋字符(將在狀態條的第一部分顯示)和如何設置工具條的提示字符(利用一個小的ToolTip窗口顯示)。
生成應用:利用AppWizard生成一個MFC工程,圖例,并設置為單文檔界面圖例,最后選擇工具條,狀態條和ReBar支持,圖例
修改菜單:利用資源編輯器刪除多余的菜單并添加一個新的彈出菜單和三個子菜單,圖例,分別是: 名稱 ID 說明字符?
Check IDM_CHECK SetCheck Demo/nSetCheck Demo?
Disable IDM_DISABLE Disable Demo/nDisable Demo?
ShowText on DialogBar IDM_SHOW_TXT ShowText on DialogBar Demo/nShowText on DialogBar
/n前的字符串將顯示在狀態條中作為命令解釋,/n后的部分將作為具有相同ID的工具條按鈕的提示顯示在ToolTip窗口中。
修改Dialog Bar:在Dialog Bar中添加一個輸入框和按鈕,按鈕的ID為IDM_SHOW_TXT與一個菜單項具有相同的ID,這樣可以利用映射菜單消息來處理按鈕消息(當然使用不同ID值也可以利用ON_COMMAND來映射Dialog Bar上的按鈕消息,但是ClassWizard沒有提供為Dialog Bar上按鈕進行映射的途徑,只能手工添加消息映射代碼)。圖例
修改工具條:在工具條中添加兩個按鈕,ID值為IDM_CHECK和IDM_DISABLE和其中兩個菜單項具有相同的ID值。圖例
利用ClassWizard為三個菜單項添加消息映射和更新命令。圖例
修改MainFrm.h文件
//添加一個成員變量來記錄工具條上Check按鈕的檢查狀態。
protected:
BOOL m_fCheck;
//手工添加狀態條第二部分用于顯示時間的更新命令,
和用于禁止/允許輸入框的更新命令
//{{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnCheck();
afx_msg void OnUpdateCheck(CCmdUI* pCmdUI);
afx_msg void OnDisable();
afx_msg void OnUpdateDisable(CCmdUI* pCmdUI);
afx_msg void OnShowTxt();
afx_msg void OnUpdateShowTxt(CCmdUI* pCmdUI);
//}}AFX_MSG
//上面的部分為ClassWizard自動產生的代碼
afx_msg void OnUpdateTime(CCmdUI* pCmdUI); //顯示時間
afx_msg void OnUpdateInput(CCmdUI* pCmdUI); //禁止/允許輸入框
修改MainFrm.cpp文件
//修改狀態條上各部分ID
#define ID_TIME 0x705 //作為狀態條上第二部分ID
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_SEPARATOR, //先設置為ID_SEPARATOR,
在狀態條創建后再進行修改
};
//修改消息映射
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_COMMAND(IDM_CHECK, OnCheck)
ON_UPDATE_COMMAND_UI(IDM_CHECK, OnUpdateCheck)
ON_COMMAND(IDM_DISABLE, OnDisable)
ON_UPDATE_COMMAND_UI(IDM_DISABLE, OnUpdateDisable)
ON_COMMAND(IDM_SHOW_TXT, OnShowTxt)
ON_UPDATE_COMMAND_UI(IDM_SHOW_TXT, OnUpdateShowTxt)
//}}AFX_MSG_MAP
//以上部分為ClassWizard自動生成代碼
ON_UPDATE_COMMAND_UI(ID_TIME, OnUpdateTime) 顯示時間
ON_UPDATE_COMMAND_UI(IDC_INPUT_TEST, OnUpdateInput)?
//禁止/允許輸入框
//修改OnCreate函數,重新設置狀態條第二部分ID值
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
....
// by wenyy 修改狀態條上第二部分信息
m_wndStatusBar.SetPaneInfo(1,ID_TIME,SBPS_NORMAL,60);
//set the width
return 0;
}
//修改經過映射的消息處理函數代碼
void CMainFrame::OnCheck()?
{
//在Check按鈕被按下時改變并保存狀態
m_fCheck=!m_fCheck;
}
void CMainFrame::OnUpdateCheck(CCmdUI* pCmdUI)?
{
//Check按鈕是否設置為檢查狀態
pCmdUI->SetCheck(m_fCheck);
}
void CMainFrame::OnDisable()?
{
//Disable按鈕被按下
AfxMessageBox("you press disable test");
}
void CMainFrame::OnUpdateDisable(CCmdUI* pCmdUI)?
{
//根據Check狀態決定自身禁止/允許狀態
pCmdUI->Enable(m_fCheck);
}
void CMainFrame::OnShowTxt()?
{
//得到Dialog Bar上輸入框中文字并顯示
CEdit* pE=(CEdit*)m_wndDlgBar.GetDlgItem(IDC_INPUT_TEST);
CString szO;
pE->GetWindowText(szO);
AfxMessageBox(szO);
}
void CMainFrame::OnUpdateShowTxt(CCmdUI* pCmdUI)?
{
//Dialog Bar上按鈕根據Check狀態決定自身禁止/允許狀態
pCmdUI->Enable(m_fCheck);
}
void CMainFrame::OnUpdateInput(CCmdUI* pCmdUI)?
{
//Dialog Bar上輸入框根據Check狀態決定自身禁止/允許狀態
pCmdUI->Enable(m_fCheck);
}
void CMainFrame::OnUpdateTime(CCmdUI* pCmdUI)?
{
//根據當前時間設置狀態條上第二部分文字
CTime timeCur=CTime::GetCurrentTime();
char szOut[20];
sprintf( szOut, "%02d:%02d:%02d", timeCur.GetHour(),?
timeCur.GetMinute(),timeCur.GetSecond());
pCmdUI->SetText(szOut);
}
4.E General Window
從VC提供的MFC類派生圖中我們可以看出窗口的派生關系,派生圖,所有的窗口類都是由CWnd派生。所有CWnd的成員函數在其派生類中都可以使用。本節介紹一些常用的功能給大家。
改變窗口狀態:
BOOL EnableWindow( BOOL bEnable = TRUE );可以設置窗口的禁止/允許狀態。BOOL IsWindowEnabled( );可以查詢窗口的禁止/允許狀態。?
BOOL ModifyStyle( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 )/BOOL ModifyStyleEx( DWORD dwRemove, DWORD dwAdd, UINT nFlags = 0 );可以修改窗口的風格,而不需要調用SetWindowLong?
BOOL IsWindowVisible( ) 可以檢查窗口是否被顯示。?
BOOL ShowWindow( int nCmdShow );將改變窗口的顯示狀態,nCmdShow可取如下值:
SW_HIDE 隱藏窗口?
SW_MINIMIZE SW_SHOWMAXIMIZED 最小化窗口?
SW_RESTORE 恢復窗口?
SW_SHOW 顯示窗口?
SW_SHOWMINIMIZED 最大化窗口
改變窗口位置:
void MoveWindow( LPCRECT lpRect, BOOL bRepaint = TRUE );可以移動窗口。
void GetWindowRect( LPRECT lpRect ) ;可以得到窗口的矩形位置。
BOOL IsIconic( ) ;可以檢測窗口是否已經縮為圖標。
BOOL SetWindowPos( const CWnd* pWndInsertAfter, int x, int y, int cx, int cy, UINT nFlags );可以改變窗口的Z次序,此外還可以移動窗口位置。
使窗口失效,印發重繪:
void Invalidate( BOOL bErase = TRUE );使整個窗口失效,bErase將決定窗口是否產生重繪。
void InvalidateRect( LPCRECT lpRect, BOOL bErase = TRUE )/void InvalidateRgn( CRgn* pRgn, BOOL bErase = TRUE );將使指定的矩形/多邊形區域失效。
窗口查找:?
static CWnd* PASCAL FindWindow( LPCTSTR lpszClassName, LPCTSTR lpszWindowName );可以以窗口的類名和窗口名查找窗口。任一參數設置為NULL表對該參數代表的數據進行任意匹配。如FindWindow("MyWnd",NULL)表明查找類名為MyWnd的所有窗口。?
BOOL IsChild( const CWnd* pWnd ) 檢測窗口是否為子窗口。?
CWnd* GetParent( ) 得到父窗口指針。?
CWnd* GetDlgItem( int nID ) 通過子窗口ID得到窗口指針。?
int GetDlgCtrlID( ) 得到窗口ID值。?
static CWnd* PASCAL WindowFromPoint( POINT point );將從屏幕上某點坐標得到包含該點的窗口指針。?
static CWnd* PASCAL FromHandle( HWND hWnd );通過HWND構造一個CWnd*指針,但該指針在空閑時會被刪除,所以不能保存供以后使用。
時鐘:
UINT SetTimer( UINT nIDEvent, UINT nElapse, void (CALLBACK EXPORT* lpfnTimer)(HWND, UINT, UINT, DWORD) );可以創建一個時鐘,如果lpfnTimer回調函數為NULL,窗口將會收到WM_TIMER消息,并可以在afx_msg void OnTimer( UINT nIDEvent );中安排處理代碼?
BOOL KillTimer( int nIDEvent );刪除一個指定時鐘。
可以利用重載來添加消息處理的虛函數:?
afx_msg int OnCreate( LPCREATESTRUCT lpCreateStruct );窗口被創建時被調用?
afx_msg void OnDestroy( );窗口被銷毀時被調用?
afx_msg void OnGetMinMaxInfo( MINMAXINFO FAR* lpMMI );需要得到窗口尺寸時被調用?
afx_msg void OnSize( UINT nType, int cx, int cy );窗口改變大小后被調用?
afx_msg void OnMove( int x, int y );窗口被移動后時被調用?
afx_msg void OnPaint( );窗口需要重繪時時被調用,你可以填如繪圖代碼,對于視圖類不需要重載OnPaint,所有繪圖代碼應該在OnDraw中進行?
afx_msg void OnChar( UINT nChar, UINT nRepCnt, UINT nFlags );接收到字符輸入時被調用?
afx_msg void OnKeyDown/OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );鍵盤上鍵被按下/放開時被調用?
afx_msg void OnLButtonDown/OnRButtonDown( UINT nFlags, CPoint point );鼠標左/右鍵按下時被調用?
afx_msg void OnLButtonUp/OnRButtonUp( UINT nFlags, CPoint point );鼠標左/右鍵放開時被調用?
afx_msg void OnLButtonDblClk/OnRButtonDblClk( UINT nFlags, CPoint point );鼠標左/右鍵雙擊時被調用?
afx_msg void OnMouseMove( UINT nFlags, CPoint point );鼠標在窗口上移動時被調用
5.1 使用資源編輯器編輯對話框
在Windows開發中彈出對話框是一種常用的輸入/輸出手段,同時編輯好的對話框可以保存在資源文件中。Visual C++提供了對話框編輯工具,利用編輯工具可以方便的添加各種控件到對話框中,而且利用ClassWizard可以方便的生成新的對話框類和映射消息。
首先資源列表中按下右鍵,可以在彈出菜單中選擇“插入對話框”,如圖1。然后再打開該對話框進行編輯,你會在屏幕上看到一個控件板,如圖2。你可以將所需要添加的控件拖到對話框上,或是先選中后再在對話框上用鼠標畫出所占的區域。
接下來我們在對話框上產生一個輸入框,和一個用于顯示圖標的圖片框。之后我們使用鼠標右鍵單擊產生的控件并選擇其屬性,如圖3。我們可以在屬性對話框中編輯控件的屬性同時也需要指定控件ID,如圖4,如果在選擇對話框本身的屬性那么你可以選擇對話框的一些屬性,包括字體,外觀,是否有系統菜單等等。最后我們編輯圖片控件的屬性,如圖5,我們設置控件的屬性為顯示圖標并指明一個圖標ID。
接下來我們添加一些其他的控件,最后的效果如圖6。按下Ctrl-T可以測試該對話框。此外在對話框中還有一個有用的特性,就是可以利用Tab鍵讓輸入焦點在各個控件間移動,要達到這一點首先需要為控件設置在Tab鍵按下時可以接受焦點移動的屬性Tab Stop,如果某一個控件不打算利用這一特性,你需要清除這一屬性。然后從菜單“Layout”選擇Tab Order來確定焦點移動順序,如圖7。使用鼠標依此點擊控件就可以重新規定焦點移動次序。最后按下Ctrl-T進行測試。
最后我們需要為對話框產生新的類,ClassWizard可以替我們完成大部分的工作,我們只需要填寫幾個參數就可以了。在編輯好的對話框上雙擊,然后系統回詢問是否添加新的對話框,選擇是并在接下來的對話框中輸入類名就可以了。ClassWizard會為你產生所需要的頭文件和CPP文件。然后在需要使用的地方包含相應的頭文件,對于有模式對話框使用DoModal()產生,對于無模式對話框使用Create()產生。相關代碼如下;
void CMy51_s1View::OnCreateDlg()?
{//產生無模式對話框
CTestDlg *dlg=new CTestDlg;
dlg->Create(IDD_TEST_DLG);
dlg->ShowWindow(SW_SHOW);
}
void CMy51_s1View::OnDoModal()?
{//產生有模式對話框
CTestDlg dlg;
int iRet=dlg.DoModal();
TRACE("dlg return %d/n",iRet);
}
下載例子。如果你在調試這個程序時你會發現程序在退出后會有內存泄漏,這是因為我沒有釋放無模式對話框所使用的內存,這一問題會在以后的章節5.3 創建無模式對話框中專門講述。
關于在使用對話框時Enter鍵和Escape鍵的處理:在使用對話框是你會發現當你按下Enter鍵或Escape鍵都會退出對話框,這是因為Enter鍵會引起CDialog::OnOK()的調用,而Escape鍵會引起CDialog::OnCancel()的調用。而這兩個調用都會引起對話框的退出。在MFC中這兩個成員函數都是虛擬函數,所以我們需要進行重載,如果我們不希望退出對話框那么我們可以在函數中什么都不做,如果需要進行檢查則可以添加檢查代碼,然后調用父類的OnOK()或OnCancel()。相關代碼如下;
void CTestDlg::OnOK()
{
AfxMessageBox("你選擇確定");
CDialog::OnOK();
}
void CTestDlg::OnCancel()
{
AfxMessageBox("你選擇取消");
CDialog::OnCancel();
}
5.2 創建有模式對話框
使用有模式對話框時在對話框彈出后調用函數不會立即返回,而是等到對話框銷毀后才會返回(請注意在對話框彈出后其他窗口的消息依然會被傳遞)。所以在使用對話框時其他窗口都不能接收用戶輸入。創建有模式對話框的方法是調用CDialog::DoModal()。下面的代碼演示了這種用法:
CYourView::OnOpenDlg()
{
CYourDlg dlg;
int iRet=dlg.DoModal();
}
CDialog::DoModal()的返回值為IDOK,IDCANCEL。表明操作者在對話框上選擇“確認”或是“取消”。由于在對話框銷毀前DoModal不會返回,所以可以使用局部變量來引用對象。在退出函數體后對象同時也會被銷毀。而對于無模式對話框則不能這樣使用,下節5.3 創建無模式對話框中會詳細講解。
你需要根據DoModal()的返回值來決定你下一步的動作,而得到返回值也是使用有模式對話框的一個很大原因。
使用有模式對話框需要注意一些問題,比如說不要在一些反復出現的事件處理過程中生成有模式對話框,比如說在定時器中產生有模式對話框,因為在上一個對話框還未退出時,定時器消息又會引起下一個對話框的彈出。
同樣的在你的對話框類中為了向調用者返回不同的值可以調用CDialog::OnOK()或是CDialog::OnCancel()以返回IDOK或IDCANCEL,如果你希望返回其他的值,你需要調用?
CDialog::EndDialog( int nResult );其中nResult會作為DoModal()調用的返回值。
下面的代碼演示了如何使用自己的函數來退出對話框:
void CMy52_s1View::OnLButtonDown(UINT nFlags, CPoint point)?
{//創建對話框并得到返回值
CView::OnLButtonDown(nFlags, point);
CTestDlg dlg;
int iRet=dlg.DoModal();
CString szOut;
szOut.Format("return value %d",iRet);
AfxMessageBox(szOut);
}
//重載OnOK,OnCancel
void CTestDlg::OnOK()
{//什么也不做
}
void CTestDlg::OnCancel()
{//什么也不做
}
//在對話框中對三個按鈕消息進行映射
void CTestDlg::OnExit1()?
{
CDialog::OnOK();
}
void CTestDlg::OnExit2()?
{
CDialog::OnCancel();
}
void CTestDlg::OnExit3()?
{
CDialog::EndDialog(0XFF);
}
由于重載了OnOK和OnCancel所以在對話框中按下Enter鍵或Escape鍵時都不會退出,只有按下三個按鈕中的其中一個才會返回。
此外在對話框被生成是會自動調用BOOL CDialog::OnInitDialog(),你如果需要在對話框顯示前對其中的控件進行初始化,你需要重載這個函數,并在其中填入相關的初始化代碼。利用ClassWizard可以方便的產生一些默認代碼,首先打開ClassWizard,選擇相應的對話框類,在右邊的消息列表中選擇WM_INITDIALOG并雙擊,如圖,ClassWizard會自動產生相關代碼,代碼如下:
BOOL CTestDlg::OnInitDialog()?
{
/*先調用父類的同名函數*/
CDialog::OnInitDialog();
/*填寫你的初始化代碼*/?
return TRUE;?
}
VC小知識總結:
(1)?如何通過代碼獲得應用程序主窗口的 指針?
主窗口的 指針保存在CWinThread::m_pMainWnd中,調用AfxGetMainWnd實現。
AfxGetMainWnd() ->ShowWindow(SW_SHOWMAXMIZED)
//使程序最大化.
(2)?確定應用程序的路徑
Use GetModuleFileName 獲得應用程序的路徑,然后去掉可執行文件名。
Example:
TCHAR
exeFullPath[MAX_PATH] // MAX_PATH在API中定義了吧,好象是
128
GetModuleFileName(NULL,exeFullPath,MAX_PATH)
(3)?如何在程序中獲得其他程序的 圖標?
兩種方法:
(1) SDK函數 SHGetFileInfo 或使用 ExtractIcon獲得圖標資源的 handle,
(2) SDK函數 SHGetFileInfo 獲得有關文件的很多信息,如大小圖標,屬性, 類型等.
Example(1):
在程序窗口左上角顯示 NotePad圖標.
void CSampleView:
OnDraw(CDC * pDC)
{
if( :: SHGetFileInfo(_T("c://pwin95//notepad.exe"),0,
&stFileInfo,sizeof(stFileInfo),SHGFI_ICON))
{
pDC ->DrawIcon(10,10,stFileInfo.hIcon)
}
}
Example(2):同樣功能,Use ExtractIcon Function
void CSampleView:: OnDraw(CDC *pDC)
{
HICON hIcon=:: ExtractIcon(AfxGetInstanceHandle(),_T
("NotePad.exe"),0)
if (hIcon &&hIcon!=(HICON)-1)
pDC->DrawIcon(10,10,hIcon)
}
????說明: 獲得notepad.exe的路徑正規上來說用GetWindowsDirectory函數得到, 如果是調用 win95下的畫筆,應該用訪問注冊表的方法獲得其路徑,要作成一個比較考究的程序,考慮應該全面點.
(4)?獲得各種目錄信息
Windows目錄: Use "GetWindowsDirectory"
Windows下的system目錄: Use "GetSystemDirectory"
temp目錄: Use "GetTempPath"
當前目錄: Use "GetCurrentDirectory"
請注意前兩個函數的第一個參數為目錄變量名,后一個為緩沖區后兩個相反.
(5)?如何自定義消息
1) 手工定義消息,可以這么寫
#define WM_MY_MESSAGE(WM_USER+100),
MS 推薦的至少是 WM_USER+100
(2)寫消息處理函數,用
WPARAM,LPARAM返回LRESULT.
LRESULT CMainFrame::OnMyMessage(WPARAM wparam,LPARAM lParam)
{
temp目錄: Use "GetTempPath"
//加入你的處理函數 irectory"
}
(6)?如何改變窗口的圖標?
向窗口發送 WM_SECTION消息。
Example:
HICON hIcon=AfxGetApp() ->LoadIcon(IDI_ICON)
ASSERT(hIcon)
AfxGetMainWnd() ->SendMessage(WM_SECTION,TRUE,(LPARAM)hIcon)
(7)?如何改變窗口的缺省風格?
重載 CWnd:: PreCreateWindow 并修改CREATESTRUCT結構來指定窗口風格和其他創建信息.
Example: Delete "Max" Button and Set Original
Window's Position and Size
BOOL CMainFrame:: PreCreateWindow
(CREATESTRUCT &cs)
{
cs.style &=~WS_MAXINIZEMOX
cs.x=cs.y=0
cs.cx=GetSystemMetrics(SM_CXSCREEN/2)
cs.cy=GetSystemMetrics(SM_CYSCREEN/2)
return CMDIFramewnd ::PreCreateWindow(cs)
}
(8)?如何將窗口居中顯示?
Call Function CWnd::
Center Windows
Example(1):
Center Window( ) //Relative to it's parent
// Relative
to Screen
Example(2):
Center Window(CWnd:: GetDesktopWindow( ))
//Relative to
Application's MainWindow
AfxGetMainWnd( ) ->
Center Window( )
(9)?如何讓窗口和 MDI窗口一啟動就最大化和最小化?
先說窗口。
在 InitStance 函數中設定 m_nCmdShow的取值.
m_nCmdShow=SW_SHOWMAXMIZED //最大化
m_nCmdShow=SW_SHOWMINMIZED //最小化
m_nCmdShow=SW_SHOWNORMAL //正常方式
MDI窗口:
如果是創建新的應用程序,可以用MFC AppWizard 的Advanced 按鈕并在MDI子窗口風格組中檢測最大化或最小化還可以重載 MDI Window 的PreCreateWindow函數,設置WS_MAXMIZE or WS_MINMIZE
如果從 CMDIChildWnd派生,調用 OnInitialUpdate函數中的 CWnd::Show Window來指定 MDI Child Window的風格。
(10)?如何限制窗口的大小?
也就是 FixedDialog形式。 Windows發送 WM_GETMAXMININFO消息來跟蹤, 響應它,在 OnGetMAXMININFO 中寫代碼:
(11)?如何使窗口不可見?
很簡單,用SW_HIDE 隱藏窗口,可以結合 FindWindow,ShowWindow控制.
(12)?如何創建一個字回繞的CEditView
重載CWnd : : PreCreateWindow和修改CREATESTRUCT結構,關閉CEditView對象的ES_AUTOHSCROLL和WS_HSCROLL風格位, 由于CEditView : : PreCreateWindow顯示設置cs. style,調用基類函數后要修改cs . style。
BOOL CSampleEDitView : : PreCreateWindow (CREATESTRUCT&cs)
{
//First call basse class function .
BOOL bResutl =CEditView : : PreCreateWindow (cs)
// Now specify the new window style .
cs.style &= ~ (ES_AUTOHSCROLL |WS_HSCROLL)
return bResult
}
(13)?如何使程序保持極小狀態?
這么辦: 在恢復程序窗體大小時,Windows會發送WM_QUERY-OPEN消息,用 ClassWizard設置成員函數
OnQueryOpen() ,add following code:
Bool CMainFrame:: OnQueryOpen( )
{
Return false
}
(14)?移動窗口
調用CWnd : : SetWindowPos并指定SWP_NOSIZE標志。目的位置與父窗口有關(頂層窗口與屏幕有關)。調用CWnd : : MoveWindow時必須要指定窗口的大小。
//Move window to positoin 100 , 100 of its parent window .
SetWindowPos (NULL, 100 , 100 , 0 , 0 , SWP_NOSIZE |SWP_NOAORDER)
(15)?通用控件的顯示窗口
MFC提供了幾個CView派生的視窗類, 封裝了通用控件的功能,但仍然使用工作框文檔顯示窗口體系結構:CEditView封裝了編輯控件,CTreeView保持了樹列表控件,CListView封裝了列表顯示窗口控件,CRichEditView可以處理多種編輯控件。
(16)?重置窗口的大小
調用CWnd: : SetWindowPos并指定SWP_NOMOVE標志, 也可調用CWnd : : MoveWindow 但必須指定窗口的位置。
// Get the size of the window .
Crect reWindow
GetWindowRect (reWindow )
//Make the window twice as wide and twice as tall .
SetWindowPos (NULL , 0 , 0 , reWindow . Width ( ) *2,
reWindow . Height () * 2,
SWP_NOMOVE |SWP_NOZORDER )
(17)?如何單擊除了窗口標題欄以外的區域使窗口移動
當窗口需要確定鼠標位置時Windows向窗口發送WM_NCHITTEST信息,可以處理該信息使Windows認為鼠標在窗口標題上。對于對話框和基于對話的應用程序,可以使用ClassWizard處理該信息并調用基類函數, 如果函數返回HTCLIENT 則表明鼠標在客房區域,返回HTCAPTION表明鼠標在Windows的標題欄中。
UINT CSampleDialog : : OnNcHitTest (Cpoint point )
{
UINT nHitTest =Cdialog: : OnNcHitTest (point )
return (nHitTest = =HTCLIENT)? HTCAPTION : nHitTest
}
上述技術有兩點不利之處,
其一是在窗口的客戶區域雙擊時,窗口將極大;
其二, 它不適合包含幾個視窗的主框窗口。
還有一種方法,當用戶按下鼠標左鍵使主框窗口認為鼠標在其窗口標題上,使用ClassWizard在視窗中處理WM_LBUTTODOWN信息并向主框窗口發送一個WM_NCLBUTTONDOWN信息和一個單擊測試HTCAPTION。
void CSampleView : : OnLButtonDown (UINT nFlags , Cpoint point
)
{
CView : : OnLButtonDow (nFlags , pont )
//Fool frame window into thinking somene clicked
on
its caption bar .
GetParentFrame ( ) —> PostMessage (
WM_NCLBUTTONDOWN ,
HTCAPTION , MAKELPARAM (poitn .x , point .y) )
}
該技術也適用于對話框和基于對的應用程序,只是不必調用
CWnd: :GetParentFrame 。
void CSampleDialog : : OnLbuttonDown (UINT nFlags, Cpoint point )
{
Cdialog : : OnLButtonDow (nFlags, goint )
//Fool dialog into thinking simeone clicked on its
caption bar .
PostMessage (WM_NCLBUTTONDOWN , HTCAPTION , MAKELPARM (point.x
, point. y
) )
}
(18)?如何改變視窗的背景顏色
Windows向窗口發送一個WM_ERASEBKGND消息通知該窗口擦除背景,可以使用ClassWizard重載該消息的缺省處理程序來擦除背景(實際是畫),并返回TRUE以防止Windows擦除窗口。
//Paint area that needs to be erased.
BOOL CSampleView : : OnEraseBkgnd (CDC* pDC)
{
// Create a pruple brush.
CBrush Brush (RGB (128 , 0 , 128) )
// Select the brush into the device context .
CBrush* pOldBrush = pDC—>SelcetObject (&brush)
// Get the area that needs to be erased .
CRect reClip
pDC—>GetCilpBox (&rcClip)
//Paint the area.
pDC—> PatBlt (rcClip.left , rcClip.top , rcClip.Width ( ) , rcClip.Height( ) , PATCOPY )
//Unselect brush out of device context .
pDC—>SelectObject (pOldBrush )
// Return nonzero to half fruther processing .
return TRUE
}
(19)?如何改變窗口標題
調用CWnd : : SetWindowText可以改變任何窗口(包括控件)的標題。
//Set title for application's main frame window .
AfxGetMainWnd ( ) —> SetWindowText (_T("Application title") )
//Set title for View's MDI child frame window .
GetParentFrame ( ) —> SetWindowText ("_T ("MDI Child Frame new title")
)
//Set title for dialog's push button control.
GetDigitem (IDC_BUTTON) —> SetWindowText (_T ("Button new title ") )
如果需要經常修改窗口的標題(注:控件也是窗口),應該考慮使用半文檔化的函數AfxSetWindowText。該函數在AFXPRIV.H中說明,在WINUTIL.CPP中實現,在聯機幫助中找不到它,它在AFXPRIV.H中半文檔化, 在以后發行的MFC中將文檔化。
AfxSetWindowText的實現如下:
voik AFXAPI AfxSetWindowText (HWND hWndCtrl , LPCTSTR IpszNew )
{
itn nNewLen= Istrlen (Ipaznew)
TCHAR szOld [256]
//fast check to see if text really changes (reduces
flash in the
controls )
if (nNewLen >_contof (szOld)
|| : : GetWindowText (hWndCrtl, szOld , _countof (szOld) !=nNewLen
|| Istrcmp (szOld , IpszNew)! = 0
{
//change it
: : SetWindowText(hWndCtrl , IpszNew )
}
}
(20)?如何防止主框窗口在其說明中顯示活動的文檔名
創建主框窗口和MDI子窗口進通常具有FWS_ADDTOTITLE風格位,如果不希望在說明中自動添加文檔名, 必須禁止該風格位, 可以使用ClassWizard重置
CWnd: : PreCreateWindow并關閉FWS_ADDTOTITLE風格。
BOOL CMainFrame : : PreCreateWindow (CREATESTRUCT&cs)
{
//Turn off FWS_ADDTOTITLE in main frame .
cs.styel & = ~FWS_ADDTOTITLE
return CMDIFrameWnd : : PreCreateWindow (cs )
}
關閉MDI子窗口的FWS _ADDTOTITLE風格將創建一個具有空標題的窗口,可以調用CWnd: : SetWindowText來設置標題。記住自己設置標題時要遵循接口風格指南。
(21)?如何獲取有關窗口正在處理的當前消息的信息
調用CWnd: : GetCurrentMessage可以獲取一個MSG指針。例如,可以使用ClassWizard將幾個菜單項處理程序映射到一個函數中,然后調用GetCurrentMessage來確定所選中的菜單項。
viod CMainFrame : : OnCommmonMenuHandler ( )
{
//Display selected menu item in debug window .
TRACE ("Menu item %u was selected . /n" ,
(22)?如何在代碼中獲取工具條和狀態條的指針
缺省時, 工作框創建狀態條和工具條時將它們作為主框窗口的子窗口,狀態條有一個AFX_IDW_STATUS_BAR標識符,工具條有一個AFX_IDW_TOOLBAR標識符,下例說明了如何通過一起調用CWnd: : GetDescendantWindow和AfxGetMainWnd來獲取這些子窗口的指針:
//Get pointer to status bar .
CStatusBar * pStatusBar = (CStatusBar *) AfxGetMainWnd ( )
—> GetDescendantWindow(AFX_IDW_STUTUS_BAR)
//Get pointer to toolbar .
CToolBar * pToolBar = (CToolBar * ) AfxGetMainWnd ( )
—> GetDescendantWindow(AFX_IDW_TOOLBAR)
(23)?如何使能和禁止工具條的工具提示
如果設置了CBRS_TOOLTIPS風格位,工具條將顯示工具提示,要使能或者禁止工具提示,需要設置或者清除該風格位。下例通過調用CControlBar : : GetBarStyle和CControlBar : : SetBarStyle建立一個完成此功能的成員函數:
void CMainFrame : : EnableToolTips ( BOOL bDisplayTips )
{
ASSERT_VALID (m_wndToolBar)
DWORD dwStyle = m _wndToolBar.GetBarStyle ( )
if (bDisplayTips) dwStyle |=CBRS_TOOLTIPS
else
dwStyle & = ~CBRS_TOOLTIPS
m_wndToolBar.SetBarStyle (dwStyle )
}
(24)?如何創建一個不規則形狀的窗口
可以使用新的SDK函數SetWindowRgn。該函數將繪畫和鼠標消息限定在窗口的一個指定的區域,實際上使窗口成為指定的不規則形狀。 使用AppWizard創建一個基于對的應用程序并使用資源編輯器從主對話資源中刪除所在的缺省控件、標題以及邊界。
給對話類增加一個CRgn數據成員,以后要使用該數據成員建立窗口區域。
Class CRoundDlg : public CDialog
{
…
private :
Crgn m_rgn : // window region
…
}
修改OnInitDialog函數建立一個橢圓區域并調用SetWindowRgn將該區域分配給窗口:
BOOL CRoundDlg : : OnInitDialog ( )
{
CDialog : : OnInitDialog ( )
//Get size of dialog .
CRect rcDialog
GetClientRect (rcDialog )
// Create region and assign to window .
m_rgn . CreateEllipticRgn (0 , 0 , rcDialog.Width( ) , rcDialog.Height ( ) )
SetWindowRgn (GetSafeHwnd ( ) , (HRGN) m_ rgn ,TRUE )
return TRUE
}
通過建立區域和調用SetWindowRgn,已經建立一個不規則形狀的窗口,下面的例子程序是修改OnPaint函數使窗口形狀看起來象一個球形體。
voik CRoundDlg : : OnPaint ( )
{
CPaintDC de (this) // device context for painting
.
//draw ellipse with out any border
dc. SelecStockObject (NULL_PEN)
//get the RGB colour components of the sphere color
COLORREF color= RGB( 0 , 0 , 255)
BYTE byRed =GetRValue (color)
BYTE byGreen = GetGValue (color)
BYTE byBlue = GetBValue (color)
// get the size of the view window
Crect rect
GetClientRect (rect)
// get minimun number of units
int nUnits =min (rect.right , rect.bottom )
//calculate he horiaontal and vertical step size
float fltStepHorz = (float) rect.right /nUnits
float fltStepVert = (float) rect.bottom /nUnits
int nEllipse = nUnits/3 // calculate how many to
draw
int nIndex
// current ellipse that is being draw
CBrush brush
// bursh used for ellipse fill color
CBrush *pBrushOld // previous
brush that was selected into dc
//draw ellipse , gradually moving towards upper-right
corner
for (nIndex = 0 nIndes < + nEllipse nIndes++)
{
//creat solid brush
brush . CreatSolidBrush (RGB ( ( (nIndex*byRed ) /nEllipse ).
( ( nIndex * byGreen ) /nEllipse ), ( (nIndex * byBlue)
/nEllipse ) ) )
//select brush into dc
pBrushOld= dc .SelectObject (&brhsh)
//draw ellipse
dc .Ellipse ( (int) fltStepHorz * 2, (int) fltStepVert * nIndex ,
rect. right -( (int) fltStepHorz * nIndex )+ 1,
rect . bottom -( (int) fltStepVert * (nIndex *2) ) +1)
//delete the brush
brush.DelecteObject ( )
}
}
最后,處理WM_NCHITTEST消息,使當擊打窗口的任何位置時能移動窗口。
UINT CRoundDlg : : OnNchitTest (Cpoint point )
{
//Let user move window by clickign anywhere on thewindow .
UINT nHitTest = CDialog : : OnNcHitTest (point)
rerurn (nHitTest = = HTCLIENT)? HTCAPTION: nHitTest
}
(25)?如何獲取應用程序的 實例句柄?
應用程序的實例句柄保存在CWinApp m_hInstance 中,可以這么調用AfxGetInstancdHandle獲得句柄.
Example: HANDLE hInstance=AfxGetInstanceHandle()
(26)?如何編程結束應用程序?
這是個很簡單又是編程中經常要遇到的問題.
向窗口發送 WM_CLOSE消息,調用 CWnd::OnClose成員函數.允許對用戶提示是否保存修改過的數據.
Example: AfxGetMainWindow()->SendMessage(WM_CLOSE)
還可以創建一個自定義的函數 Terminate Window
void Terminate Window(LPCSTR pCaption)
{
CWnd *pWnd=Cwnd::FindWindow(NULL,pCaption)
if (pWnd)
pWnd ->SendMessage(WM_CLOSE)
}
????說明: FindWindow函數不是提倡的做法,因為它無法處理標題欄自動改變,比如我們要檢測 Notepad是不是已運行而事先不知道Notepad的標題欄,這時FindWindow就無能為力了,可以通過枚舉 windows任務列表的辦法來實現。在機械出版社"Windows 95 API開發人員指南"一書有比較詳細的介紹,這里就不再多說樂。
(27)?如何創建和使用無模式對話框
MFC將模式和無模式對話封裝在同一個類中,但是使用無模式對話需要幾個對話需要幾個額處的步驟。首先,使用資源編輯器建立對話資源并使用ClassWizard創建一個CDialog的派生類。模式和無模式對話的中止是不一樣的:模式對話通過調用CDialog : : EndDialog 來中止,無模式對話則是調用CWnd: : DestroyWindow來中止的,函數CDialog : : OnOK和CDialog : : OnCancel調用EndDialog ,所以需要調用DestroyWindow并重置無模式對話的函數。
void CSampleDialog : : OnOK ( )
{
// Retrieve and validate dialog data .
if (! UpdateData (TRUE) )
{
// the UpdateData rountine
will set focus to correct item TRACEO (" UpdateData failed during dialog termination ./n")
return
}
//Call DestroyWindow instead of EndDialog .
DestroyWindow ( )
}
void CSampleDialog : : OnCancel ( )
{
//Call DestroyWindow instead of EndDialog .
DestroyWindow ( )
}
其次,需要正確刪除表示對話的C++對象。對于模式對來說,這很容易,需要創建函數返回后即可刪除C++對象;無模式對話不是同步的,創建函數調用后立即返回,因而用戶不知道何時刪除C++對象。撤銷窗口時工作框調用CWnd : : PostNcDestroy,可以重置該函數并執行清除操作,諸如刪除this指針。
void CSampleDialog : : PostNcDestroy ( )
{
// Declete the C++ object that represents this dialog.
delete this
最后,要創建無模式對話。可以調用CDialog : : DoModal創建一個模式對放,要創建一個無模式對話則要調用CDialog: : Create。下面的例子說明 了應用程序是如何創建無模式對話的: 象;無模式對話不是同步的,創建函數調用后立即返回,
void CMainFrame : : OnSampleDialog ( )
{
//Allocate a modeless dialog object .
CSampleDilog * pDialog =new CSampleDialog
ASSERT_VALID (pDialog) Destroy ( )
//Create the modeless dialog . represents this dialog.
BOOL bResult = pDialog —> Creste (IDD_IDALOG)
ASSERT (bResult )
}
(28)?如何防止主框窗口在其說明中顯示活動的文檔名
創建主框窗口和MDI子窗口進通常具有FWS_ADDTOTITLE風格位,如果不希望在說明中自動添加文檔名, 必須禁止該風格位, 可以使用ClassWizard重置
CWnd: : PreCreateWindow并關閉FWS_ADDTOTITLE風格。
BOOL CMainFrame : : PreCreateWindow (CREATESTRUCT&cs)
{
//Turn off FWS_ADDTOTITLE in main frame .
cs.styel & = ~FWS_ADDTOTITLE
return CMDIFrameWnd : : PreCreateWindow (cs )
}
關閉MDI子窗口的FWS _ADDTOTITLE風格將創建一個具有空標題的窗口,可以調用CWnd: : SetWindowText來設置標題。記住自己設置標題時要遵循接口風格指南。
(29)?如何在代碼中獲取工具條和狀態條的指針
缺省時, 工作框創建狀態條和工具條時將它們作為主框窗口的子窗口,狀態條有一個AFX_IDW_STATUS_BAR標識符,工具條有一個AFX_IDW_TOOLBAR標識符,下例說明了如何通過一起調用CWnd: : GetDescendantWindow和AfxGetMainWnd來獲取這些子窗口的指針:
//Get pointer to status bar .
CStatusBar * pStatusBar = (CStatusBar *) AfxGetMainWnd ( )
—> GetDescendantWindow(AFX_IDW_STUTUS_BAR)
//Get pointer to toolbar .
CToolBar * pToolBar = (CToolBar * ) AfxGetMainWnd ( )
—> GetDescendantWindow(AFX_IDW_TOOLBAR)
(30)?怎樣加載其他的應用程序?
三個SDK函數 winexec, shellexecute,createprocess可以使用。
WinExec最簡單,兩個參數,前一個指定路徑,后一個指定顯示方式.后一個參數值得說一下,比如泥用 SW_SHOWMAXMIZED方式去加載一個無最大化按鈕的程序,就是Neterm,calc等等,就不會出現正常的窗體,但是已經被加到任務列表里了。
ShellExecute較 WinExex靈活一點,可以指定工作目錄,下面的Example就是直接打開 c:/temp/1.txt,而不用加載與 txt文件關聯的應用程序,很多安裝程序完成后都會打開一個窗口,來顯示Readme or Faq,我猜就是這么作的啦.
ShellExecute(NULL,NULL,_T("1.txt"),NULL,_T("c://temp"),SW_SHOWMAXMIZED)
CreateProcess最復雜,一共有十個參數,不過大部分都可以用NULL代替,它可以指定進程的安全屬性,繼承信息,類的優先級等等.來看個很簡單的Example:
STARTUPINFO stinfo
//啟動窗口的信息
PROCESSINFO procinfo //進程的信息
CreateProcess(NULL,_T("notepad.exe"),NULL,NULL.FALSE,
NORMAL_PRIORITY_
CLASS,NULL,NULL, &stinfo,&procinfo)
(31)?如何在代碼中獲取工具條和狀態條的指針
缺省時, 工作框創建狀態條和工具條時將它們作為主框窗口的子窗口,狀態條有一個AFX_IDW_STATUS_BAR標識符,工具條有一個AFX_IDW_TOOLBAR標識符,下例說明了如何通過一起調用CWnd: : GetDescendantWindow和AfxGetMainWnd來獲取這些子窗口的指針:
//Get pointer to status bar .
CStatusBar * pStatusBar = (CStatusBar *) AfxGetMainWnd ( )
—> GetDescendantWindow(AFX_IDW_STUTUS_BAR)
(32)?如何使能和禁止工具條的工具提示
如果設置了CBRS_TOOLTIPS風格位,工具條將顯示工具提示,要使能或者禁止工具提示,需要設置或者清除該風格位。下例通過調用CControlBar : : GetBarStyle和CControlBar : : SetBarStyle建立一個完成此功能的成員函數:
void CMainFrame : : EnableToolTips ( BOOL bDisplayTips )
{
ASSERT_VALID (m_wndToolBar)
DWORD dwStyle = m _wndToolBar.GetBarStyle ( )
if (bDisplayTips) dwStyle |=CBRS_TOOLTIPS
else
dwStyle & = ~CBRS_TOOLTIPS
m_wndToolBar.SetBarStyle (dwStyle )
}
//Get pointer to toolbar .
CToolBar * pToolBar = (CToolBar * ) AfxGetMainWnd ( )
—> GetDescendantWindow(AFX_IDW_TOOLBAR)
(33)?如何設置工具條標題
工具條是一個窗口,所以可以在調用CWnd : : SetWindowText來設置標題,例子如下:
int CMainFrame : : OnCreate (LPCREATESTRUCT lpCreateStruct )
{
…
// Set the caption of the toolbar .
m_wndToolBar.SetWindowText (_T "Standdard")
(34)?如何使窗口始終在最前方?
BringWindowToTop(Handle)
SetWindowPos函數,指定窗口的 最頂風格,用WS_EX_TOPMOST擴展窗口的風格
Example:
void ToggleTopMost(
CWnd *pWnd)
{
ASSERT_VALID(pWnd)
pWnd ->SetWindowPos(pWnd-> GetStyle( ) &WS_EX_TOPMOST)?
&wndNoTopMOST: &wndTopMost,0,0,0,0,SSP_NOSIZE|WSP_NOMOVE)
}
(35)?如何在對話框中顯示一個位圖
這要歸功于Win 32先進的靜態控件和Microsoft的資源編輯器,在對話框中顯示位圖是很容易的, 只需將圖形控件拖到對話中并選擇適當屬性即可,用戶也可以顯示圖標、位圖以及增強型元文件。
(36)?如何改變對話或窗體視窗的背景顏色
調用CWinApp : : SetDialogBkColor可以改變所有應用程序的背景顏色。第一個參數指定了背景顏色,第二個參數指定了文本顏色。下例將應用程序對話設置為藍色背景和黃色文本。
BOOL CSampleApp : : InitInstance ( )
{
…
//use blue dialog with yellow text .
SetDialogBkColor (RGB (0, 0, 255 ), RGB ( 255 ,255 , 0 ) )
…
}
需要重畫對話(或對話的子控件)時,Windows向對話發送消息WM_CTLCOLOR,通常用戶可以讓Windows選擇繪畫背景的刷子,也可重置該消息指定刷子。下例說明了創建一個紅色背景對話的步驟。
首先,給對話基類增加一人成員變量
CBursh :class CMyFormView : public CFormView
{
…
private :
CBrush m_ brush // background brush
…
}
其次, 在類的構造函數中將刷子初始化為所需要的背景顏色。
CMyFormView : : CMyFormView ( )
{
// Initialize background brush .
m_brush .CreateSolidBrush (RGB ( 0, 0, 255) )
}
最后,使用ClassWizard處理WM_CTLCOLOR消息并返回一個用來繪畫對話背景的刷子句柄。注意:由于當重畫對話控件時也要調用該函數,所以要檢測nCtlColor參量。
HBRUSH CMyFormView : : OnCtlColor (CDC* pDC , CWnd*pWnd , UINT nCtlColor
)
{
// Determine if drawing a dialog box . If we are, return +handle to
//our own background brush . Otherwise let windows handle it .
if (nCtlColor = = CTLCOLOR _ DLG )
return (HBRUSH) m_brush.GetSafeHandle ( )
return CFormView : : OnCtlColor (pDC, pWnd , nCtlColor
)
}
(37)?如何獲取一個對話控件的指針
有兩種方法。其一,調用CWnd: : GetDlgItem,獲取一個CWnd*指針調用成員函數。下例調用GetDlgItem,將返回值傳給一個CSpinButtonCtrl*以便調用CSpinButtonCtrl : : SetPos 函數:
BOOL CSampleDialog : : OnInitDialog ( )
{
CDialog : : OnInitDialog ( )
//Get pointer to spin button .
CSpinButtonCtrl * pSpin - ( CSpinButtonCtrl *) GetDlgItem(IDC_SPIN)
ASSERT _ VALID (pSpin)
//Set spin button's default position .
pSpin —> SetPos (10)
return TRUE
}
其二, 可以使用ClassWizard將控件和成員變量聯系起來。在ClassWizard中簡單地選擇Member Variables標簽,然后選擇Add Variable …按鈕。如果在對話資源編輯器中,按下Ctrl鍵并雙擊控件即可轉到Add Member Variable對話。
(38)?如何禁止和使能控件
控件也是窗口,所以可以調用CWnd : : EnableWindow使能和禁止控件。
//Disable button controls .
m_wndOK.EnableWindow (FALSE )
m_wndApply.EnableWindow (FALSE )
(39)?如何改變控件的字體
由于控件是也是窗口,用戶可以調用CWnd: : SetFont指定新字體。該函數用一個Cfont指針,要保證在控件撤消之前不能撤消字體對象。下例將下壓按鈕的字體改為8點Arial字體:
//Declare font object in class declaration (.H file ).
private : Cfont m_font
// Set font in class implementation (.Cpp file ). Note m_wndButton is a
//member variable added by ClassWizard.DDX routines hook the member
//variable to a dialog button contrlo.
BOOL CSampleDialog : : OnInitDialog ( )
{
…
//Create an 8-point Arial font
m_font . CreateFont (MulDiv (8 , -pDC
—> GetDeviceCaps(LOGPIXELSY) ,72). 0 , 0 , 0 , FW_NORMAL , 0 , 0,0, ANSI_CHARSER, OUT_STROKE_PRECIS ,
CLIP_STROKE _PRECIS , DRAFT _QUALITY
VARIABLE_PITCH |FF_SWISS, _T("Arial") )
//Set font for push button .
m_wndButton . SetFont (&m _font )
…
}
(40)?如何在OLE控件中使用OLE_COLOR數據類型
諸如COleControl : : GetFortColor和COleControl : : GetBackColor等函數返回OLE _COLOR數據類型的顏色,而GDI對象諸如筆和刷子使用的是COLORREF數據類型,調用COleControl : : TranslateColor可以很容易地將OLE_COLOR類型改為COLORREF類型。下例創建了一個當前背景顏色的刷子:
void CSampleControl : : OnDraw (CDC* pdc
const Crect& rcBounds , const Crect& rcInvalid
)
{
//Create a brush of the cuttent background color.
CBrush brushBack (TranslateColor (GetBackColor () ) )
//Paint the background using the current backgroundcolor .
pdc—> FilllRect (rcBounds , &brushBack)
//other drawign commands
…
}
(41)?在不使用通用文件打開對話的情況下如何顯示一個文件列表
調用CWnd: : DlgDirList或者CWnd: : DlgDirListComboBox,Windows 將自動地向列表框或組合框填充可用的驅動器名或者指定目錄中的文件,下例將Windows目錄中的文件填充在組合框中:
BOOL CSampleDig : : OnInitDialog ( )
{
CDialog : : OnInitDialog ( )
TCHAR szPath [MAX_PATH] = {"c://windows"}
int nReslt = DlgDirListComboBox (szPath, IDC_COMBO , IDC_CURIDIR, DDL_READWRITE |DDL_READONLY|DDL_HIDDEN| DDL_SYSTEM|DDL_ARCHIVE)
return TRUE
}
(42)?為什么旋轉按鈕控件看起來倒轉
需要調用CSpinCtrl : : SetRange 設置旋轉按鈕控件的范圍,旋轉按鈕控件的缺省上限為0,缺省下限為100,這意味著增加時旋轉按控件的值由100變為0。下例將旋轉按鈕控件的范圍設置為0到100:
BOOL CAboutDlg : : OnInitDialog ( )
{
CDialog : : OnInitDialog ( )
//set the lower and upper limit of the spin button
m_wndSpin . SetRange ( 0 ,100 )
return TRUE
}
Visual C++ 4.0 Print對話中的Copise旋轉按鈕控件也有同樣的問題:按下Up按鈕時拷貝的數目減少,而按下Down 按鈕時拷貝的數目增加。
(43)?為什么旋轉按鈕控件不能自動地更新它下面的編輯控件
如果使用旋轉按鈕的autu buddy特性, 則必須保證在對話的標記順序中buddy窗口優先于旋轉按鈕控件。從Layout菜單中選擇Tab Order菜單項(或者按下Crtl+D)可以設置對話的標簽順序。
(44)?如何用位圖顯示下壓按鈕
Windows 95按鈕有幾處新的創建風格,尤其是BS_BITMAP和BS_ICON,要想具有位圖按鈕,創建按鈕和調用CButton : : SetBitmap或CButton : : SetIcon時要指定BS_BITMAP或BS_ICON風格。
首先,設置按鈕的圖標屬性。然后,當對話初始化時調用CButton: : SetIcon。注意:下例用圖標代替位圖,使用位圖時要小心,因為不知道背景所有的顏色——并非每個人都使用淺灰色。
BOOL CSampleDlg : : OnInitDialog ( )
{
CDialog : : OnInitDialog ( )
//set the images for the push buttons .
BOOL CSampleDlg : : OnInitDialog ( )
{
CDialog : : OnInitDialog ( )
//set the images for the push buttons .
m_wndButton1.SetIcon (AfxGetApp ( ) —> LoadIcon (IDI _ IPTION1))
m_wndButton2.SetIcon (AfxGetApp ( ) —> LoadIcon (IDI _ IPTION2))
m_wndButton3.SetIcon (AfxGetApp ( ) —> LoadIcon (IDI _ IPTION3))
return TRUE
}
(45)?如何一個創建三態下壓按鈕
可以使用新的BS_PUSHBUTTON 風格位和檢測框以及按鈕來創建一個三態下壓按鈕。這很容易,只需將檢測框和按鈕拖拉到對話中并指定屬性Push—like即可。不用任何附加程序就可以成為三態下壓按鈕。
(46)?如何動態創建控件
分配一個控件對象的實例并調用其Create成員函數。開發者最容易忽略兩件事:忘記指定WS_VISBLE標簽和在棧中分配控件對象。下例動態地創建一個下壓按鈕控件:
//In class declaration (.H file ).
private : CButton* m _pButton
//In class implementation (.cpp file ) .
m_pButton =new CButton
ASSERT_VALID (m_pButton)
m_pButton —>Create (_T ("Button Title ") , WS_CHILD |WS_VISIBLE |BS_PUSHBUTTON. Crect ( 0, 0, 100 , 24) , this , IDC _MYBUTTON )
(47)?如何限制編輯框中的準許字符
如果用戶在編輯控件中只允許接收數字,可以使用一個標準的編輯控件并指定新的創建標志ES_NUMBERS,它是Windows 95新增加的標志,該標志限制 編輯控件只按收數字字符。如果用戶需要復雜的編輯控件,可以使用Microsoft 的屏蔽編輯控件,它是一個很有用的OLE定制控件。
如果希望不使用OLE 定制控件自己處理字符,可以派生一個CEdit類并處理WM_CHAR消息,然后從編輯控件中過濾出特定的字符。首先,使用ClassWizard建立一個 CEdit的派生類,其次,在對話類中指定一個成員變量將編輯控件分類在OnInitdialog 中調用CWnd: : SubclassDlgItem .
//In your dialog class declaration (.H file )
private : CMyEdit m_wndEdit // Instance of your new edit control .
//In you dialog class implementation (.CPP file )
BOOL CSampleDialog : : OnInitDialog ( )
{
…
//Subclass the edit lontrod .
m_wndEdit .SubclassDlgItem (IDC_EDIT,this)
…
}
使用ClassWizard處理WM_CHAR消息,計算nChar參量并決定所執行的操作,用戶可以確定是否修改、傳送字符。下例說明了如何顯示字母字符,如果字符是字母字符,則調用CWnd OnChar,否則不調用OnChar.
//Only display alphabetic dharacters .
void CMyEdit : : OnChar (UINT nChar , UINT nRepCnt , UITN nFlags )
{
//Determine if nChar is an alphabetic character.
if (: : IsCharAlpha ( ( TCHAR) nChar ) )
CEdit : : OnChar (nChar, nRepCnt , nFlags )
}
如果要修改字符,則不能僅僅簡單地用修改過的nChar調用CEdit: : OnChar,然后CEdit: : OnChar調用CWnd: : Default獲取原來的wParam 和lParam 的值,這樣是不行的。要修改一個字符,需要首先修改nChar,然后用修改過的nChar調用CWnd: : DefWindowProc。下例說明了如何將字符轉變為大寫:
//Make all characters uppercase
void CMyEdit : : OnChar (UINT nChar , UINT nRepCnt , UINT nFlags )
{
//Make sure character is uppercase .
if (: : IsCharAlpha ( .( TCHAR) nChar)
nChar=: : CharUpper(nChar )
//Bypass default OnChar processing and directly call
//default window proc.
DefWindProc (WM_CHAR, nChar , MAKELPARAM (nRepCnt, nFlags ))
}
(48)?如何改變控件的顏色
有兩種方法。其一,可以在父類中指定控件的顏色,或者利用MFC4.0新的消息反射在控件類中指定顏色。 當控件需要重新著色時,工作框調用父窗口(通常是對話框)的CWnd: : OnCrtlColor,可以在父窗口類中重置該函數并指定控件的新的繪畫屬性。例如,下述代碼將對話中的所有編輯控件文本顏色改為紅色:
HBRUSH CAboutDig : : OnCtlColor (CDC * pDCM , CWnd * pWnd , UINT nCtlColor)
{
HBRUSH hbr = CDialog : : OnCtlColor (pDC, pWnd , nCtlColor )
//Draw red text for all edit controls .
if (nCtlColor= = CTLCOLOR_EDIT )
pDC —> SetTextColor (RGB (255, 0 , 0 , ) )
return hbr
}
然而,由于每個父窗口必須處理通知消息并指定每個控件的繪畫屬性,所以,這種方法不是完全的面向對象的方法。控件處理該消息并指定繪畫屬性更合情合理。消息反射允許用戶這樣做。通知消息首先發送給父窗口,如果父窗口沒有處理則發送給控件。創建一個定制彩色列表框控件必須遵循下述步驟。
首先,使用ClassWizard 創建一個CListBox 的派生類并為該類添加下述數據成員。
class CMyListBox publilc CListBox
{
…
private
COLORREF m_clrFor // foreground color
COLORREF m_clrBack //background color
Cbrush m_brush //background brush
…
}
其次,在類的構造函數中,初始化數據中。
CMyListBox : : CMyListBox ()
{
//Initialize data members .
m_clrFore =RGB (255 , 255 , 0) //yellow text
m_clrBack=RGB (0 , 0 , 255) // blue background
m_brush . CreateSolidBrush (m _clrBack )
}
最后,使用ClassWizard處理反射的WM_CTLCOLOR(=WM_CTLCOLOR)消息并指定新的繪畫屬性。
HBRUSH CMyListBox : : CtlColor (CDC* pDC, UINT nCtlColor )
{
pDC—>SetTextColor (m_clrFore)
pDC—>SetBkColor (m_clrBack)
return (HBRUSH) m_brush.GetSafeHandle ()
}
現在,控件可以自己決定如何繪畫,與父窗口無關。
(49)?當向列表框中添加多個項時如何防止閃爍
調用CWnd::SetRedraw 清除重畫標志可以禁止CListBox(或者窗口)重畫。當向列表框添加幾個項時,用戶可以清除重畫標志,然后添加項,最后恢復重畫標志。為確保重畫列表框的新項,調用SetRedraw (TRUE) 之后調用CWnd::Invalidate。
//Disable redrawing.
pListBox->SetRedraw (FALSE)
//Fill in the list box gere
//Enable drwing and make sure list box is redrawn.
pListBox->SetRedraw (TRUE)
pListBox->Invalidate ()
(50)?如何向編輯控件中添加文本
由于沒有CEdit:: AppendText函數,用戶只好自己做此項工作。調用CEdit:: SetSel移動到編輯控件末尾,然后調用CEdit:: ReplaceSel添加文本。下例是AppendText 的一種實現方法:
void CMyEdit:: AppendText (LPCSTR pText)
{
int nLen=GetWindowTextLength ()
SetFocus ()
SetSel (nLen, nLen)
ReplaceSel (pText)
}
(51)?如何訪問預定義的GDI對象
可以通過調用CDC:: SlectStockObject使用Windows的幾個預定義的對象,諸如刷子、筆以及字體。下例使用了Windows預定義的筆和刷子GDI對象在視窗中畫一個橢圓。
//Draw ellipse using stock black pen and gray brush.
void CSampleView:: OnDraw (CDC* pDC)
{
//Determine size of view.
CRect rcView
GetClientRect (rcView)
//Use stock black pen and stock gray brush to draw ellipse.
pDC->SelectStockObject (BLACK_PEN)
pDC->SelectStockObject (GRAY_BRUSH)
//Draw the ellipse.
pDC->Ellipse (reView)
}
也可以調用新的SDK函數GetSysColorBrush獲取一個系統顏色刷子,下例用背景色在視窗中畫一個橢圓:
void CsampleView:: OnDraw (CDC* pDC)
{
//Determine size of view.
CRect rcView
GetClientRect (rcView)
//Use background color for tooltips brush.
CBrush * pOrgBrush=pDC->SelectObject ( CBrush ::FromHandle( ::GetSysColorBrush (COLOR_INFOBK)))
//Draw the ellipse.
pDC->Ellipse (rcView)
//Restore original brush.
pDC->SelectObject (pOrgBrush)
}
(52)?如何獲取GDI對象的屬性信息
可以調用GDIObject:: GetObject。這個函數將指定圖表設備的消息寫入到緩沖區。下例創建了幾個有用的輔助函數。
//Determine if font is bold.
BOOL IsFontBold (const CFont&font)
{
LOGFONT stFont
font.GetObject (sizeof (LOGFONT), &stFont)
return (stFont.lfBold)? TRUE: FALSE
}
//Return the size of a bitmap.
CSize GetBitmapSize (const CBitmap&bitmap)
{
BITMAP stBitmap
bitmap.GetObject (sizeof (BITMAP), &stBitmap)
return CSize (stBitmap.bmWidth, stBitmap.bmHeight)
}
//Create a pen with the same color as a brush.
BOOL CreatePenFromBrush (Cpen&pen, cost Cbrush&brush)
{
LOGBRUSH stBrush
brush.Getobject (sizeof (LOGBRUSH), &stBrush)
return pen. Createpen (PS_SOLID, 0, stBrush.ibColor)
}
(53)?如何實現一個橡皮區矩形
CRectTracker是一個很有用的類,可以通過調用CRectTracker::TrackRubberBand 響應WM_LBUTTONDOWN消息來創建一個橡皮區矩形。
下例表明使用CRectTracker移動和重置視窗中的藍色橢圓的大小是很容易的事情。
首先,在文件檔中聲明一個CRectTracker數據成員:
class CSampleView : Public CView
{
…
public :
CrectTracker m_tracker
…
}
其次,在文檔類的構造函數中初始化CRectTracker 對象:
CSampleDoc:: CSampleDOC ()
{
//Initialize tracker position, size and style.
m_tracker.m_rect.SetRect (0, 0, 10, 10)
m_tracker.m_nStyle=CRectTracker:: resizeInside | CRectTracker ::dottedLine
}
然后,在OnDraw函數中畫橢圓和蹤跡矩形:
void CSampleView:: OnDraw (CDC* pDC)
{
CSampleDoc* pDoc=GetDocument ()
ASSERT_VALID (pDoc)
//Select blue brush into device context.
CBrush brush (RGB (0, 0, 255))
CBrush* pOldBrush=pDC->SelectObject (&brush)
//draw ellipse in tracking rectangle.
Crect rcEllipse
pDoc->m_tracker.GetTrueRect (rcEllipse)
pDC->Ellipse (rcEllipse)
//Draw tracking rectangle.
pDoc->m_tracker.Draw (pDC)
//Select blue brush out of device context.
pDC->Selectobject (pOldBrush)
}
最后,使用ClassWizard處理WM_LBUTTONDOWN消息,并增加下述代碼。該段代碼根據鼠標擊鍵情況可以拖放、移動或者重置橢圓的大小。
void CSampleView::OnLButtonDown (UINT nFlags, CPoint point)
{
//Get pointer to document.
CSampleDoc* pDoc=GetDocument ()
ASSERT_VALID (pDoc)
//If clicked on ellipse, drag or resize it.Otherwise create a
//rubber-band rectangle nd create a new ellipse.
BOOL bResult=pDoc->m_tracker.HitTest (point)!= CRectTracker::hitNothing
//Tracker rectangle changed so update views.
if (bResult)
{
pDoc->m_tracker.Track (this,point,TRue)
pDoc->SetModifiedFlag ()
pDoc->UpdateAllViews (NULL)
}
else
pDoc->m-tracker.TrackRubberBand(this,point,TRUE)
CView:: onLButtonDown (nFlags,point)
}
(54)?如何更新翻轉背景顏色的文本
調用CDC:: SetBkmode并傳送OPAQUE用當前的背景顏色填充背景,或者調用CDC::SetBkMode并傳送TRANSPAARENT使背景保持不變,這兩種方法都可以設置背景模式。下例設置背景模式為TRANSPARENT,可以兩次更新串,用花色帶黑陰影更新文本。黑色串在紅色串之后,但由于設置了背景模式仍然可見。
void CSampleView:: OnDraw (CDC* pDC)
{
//Determint size of view.
CRect rcView
GetClientRect (rcVieew)
//Create sample string to display.
CString str (_T ("Awesome Shadow Text..."))
//Set the background mode to transparent.
pDC->SetBKMode (TRANSPARENT)
//Draw black shadow text.
rcView.OffsetRect (1, 1)
pDc->SetTextColor (RGB (0, 0, 0))
pDC->DrawText (str, str.GetLength (), rcView, DT_SINGLELINE | DT_CENTER | DT_VCENTER)
//Draw red text.
rcView.OffsetRect (-1,-1)
pDc->SetTextColor (RGB (255, 0, 0))
pDC->DrawText (str, str.GetLength (), rcView, DT_SINGLELINE | DT_CENTER | DT_VCENTER)
}
(55)?如何創建一個具有特定點大小的字體
可以指定字體邏輯單位的大小,但有時指定字體的點的大小可能會更方便一些。可以如下將字體的點轉換為字體的高度:
int nHeigth=mulDiv (nPointSize, -dc.GetDeviceCaps (LOGPIXELSY), 72)
下例創建了一個8點的Apial字體:
…
CClientDC dc (AqfxGetMainWnd ())
m_font. CreateFont (MulDiv (8, -dc.GetDeviceCaps (LOGPIXELSY), 72), 0, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET, OUT_STROKE_PRECIS, CLIP_STROKE_PRECIS, DRAFT_QUALITY, VARIABLE_PITCH | FF-SWISS,_T("Arial"))
(56)?如何計算一個串的大小
函數CDC:: Det text Extent 根據當前選擇的字體計算一個串的高度和寬度。如果使用的不是系統字體而是其他字體,則在調用GetTextExtent之前將字體選進設備上下文中是很重要的,否則計算高度和寬度時將依據系統字體,由此得出的結果當然是不正確的。下述樣板程序當改變下壓按鈕的標題時動態調整按鈕的大小,按鈕的大小由按鈕的字體和標題的大小而定。響應消息WM_SETTEXT時調用OnSetText,該消息使用ON_MESSAE宏指令定義的用戶自定義消息。
LRESULT CMyButton:: OnSettext (WPARAM wParam, LPARAM lParam)
{
//Pass message to window procedure.
LRESULT bResult=CallWindowProc (*GetSuperWndProcAddr(), m_hWnd, GetCurrentMessage() ->message,wParam,lParam)
//Get title of push button.
CString strTitle
GetWindowText (strTitle)
//Select current font into device context.
CDC* pDC=GetDc ()
CFont*pFont=GetFont ()
CFont*pOldFont=pDC->SelectObject (pFont)
//Calculate size of title.
CSize size=pDC->GetTextExent (strTitle,strTitle.GetLength())
//Adjust the button's size based on its title.
//Add a 5-pixel border around the button.
SetWindowPos (NULL, 0, 0, size.cx+10, size.cy+10, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE)
//Clean up.
pDC->SelectFont (pOldFont)
ReleaseDC (pDC)
return bResult
}
(57)?如何顯示旋轉文本
只要用戶使用TrueType或者GDI筆或字體就可以顯示旋轉文本(有些硬件設備也支持旋轉光柵字體)。LOGFONT結構中的ifEscapement成員指定了文本行和x軸的角度,角度的單位是十分之一度而不是度,例如,ifEscapement為450表示字體旋轉45度。為確保所有的字體沿坐標系統的同一方向旋轉,一定要設置ifEscapement成員的CLIP_LH_ANGLES位,否則,有些字體可能反向旋轉。下例使用了14點Arial字體每間隔15度畫一個串。
void CSampleView:: OnDraw (CDC* pDC)
{
//Determine the size of the window.
CRect rcClient
GetClientRect (rcClient)
//Create sample string.
CString str (_T ("Wheeee...I am rotating!"))
//Draw transparent, red text.
pDC->SetBkMode (TRANSPARENT)
pDC->SetTextColor (RGB (255,0,0))
CFont font
//font object
LOGFONT stFont //font definition
//Set font attributes that will not change.
memset (&stFont, 0, sizeof (LOGFONT))
stFont.ifheight=MulDiv (14, -pDC->GetDeviceCaps(LOGPIXELSY), 72)
stFont.ifWeight=FW_NORMAL
stFont.ifClipPrecision=LCIP_LH_ANGLES
strcpy (stFont.lfFaceName, "Arial")
//Draw text at 15degree intervals.
for (int nAngle=0 nAngle<3600 nAngle+=150)
{
//Specify new angle.
stFont.lfEscapement=nAngle
//Create and select font into dc.
font.CreateFontIndirect(&stfont)
CFont* pOldFont=pDC ->SelectObject(&font)
//Draw the text.
pDC->SelectObject(pOldFont)
font.DelectObjext()
}
}
(58)?如何正確顯示包含標簽字符的串
調用GDI文本繪畫函數時需要展開標簽字符,這可以通過調用CDC:: TabbedTextOut或者CDC:: DrawText并指定DT_EXPANDTABS標志來完成。TabbedTextOut函數允許指定標簽位的數組,下例指定每20設備單位展開一個標簽:
void CSampleView:: OnDraw (CDC* pDC)
{
CTestDoc* pDoc=GetDocument ()
ASSERT_VALID (pDoC)
CString str
str.Format (_T ("Cathy/tNorman/tOliver"))
int nTabStop=20 //tabs are every 20 pixels
pDC->TabbedtextOut (10, 10, str, 1, &nTabStop, 10)
}
(59)?如何快速地格式化一個CString對象
調用CString:: Format,該函數和printf函數具有相同的參數,下例說明了如何使用Format函數:
//Get size of window.
CRect rcWindow
GetWindowRect (rcWindow)
//Format message string.
CString strMessage
strMessage.Format (_T ("Window Size (%d, %d)"),
rcWindow.Width (), rcWindow.Height ())
//Display the message.
MessageBox (strmessage)
(60)?串太長時如何在其末尾顯示一個省略號
調用CDC:: DrawText并指定DT_END_ELLIPSIS標志,這樣就可以用小略號取代串末尾的字符使其適合于指定的邊界矩形。如果要顯示路徑信息,指定DT_END_ELLIPSIS標志并省略號取代串中間的字符。
void CSampleView:: OnDraw (CDC* pDC)
{
CTestDoc* pDoc=GetDocument ()
ASSERT_VALID (pDoc)
//Add ellpsis to end of string if it does not fit
pDC->Drawtext (CString ("This is a long string"), CRect (10, 10, 80, 30), DT_LEFT | DT_END_ELLIPSIS)
//Add ellpsis to middle of string if it does not fit
pDC->DrawText (AfxgetApp () ->m_pszhelpfilePath, CRect (10, 40, 200, 60), DT_LEFT | DT_PATH_ELLIPSIS)
}
(61)?為什么即使調用EnableMenuItem菜單項后,菜單項還處于禁止狀態
需要將CFrameWnd:: m_bAutomenuEnable設置為FALSE,如果該數據成員為TRUE(缺省值),工作框將自動地禁止沒有ON_UPDATE_COMMAND_UI或者ON_COMMAND的菜單項。
//Disable MFC from automatically disabling menu items.
m_bAuoMenuEnable=FALSE
//Now enable the menu item.
CMenu* pMenu=GetMenu ()
ASSERT_VALID (pMenu)
pMenu->EnableMenuItem (ID_MENU_ITEM,MF_BYCOMMAND | MF_ENABLED)
(62)?如何給系統菜單添加一個菜單項
給系統菜單添加一個菜單項需要進行下述三個步驟:
首先,使用Resource Symbols對話(在View菜單中選擇Resource Symbols...可以顯示該對話)定義菜單項ID,該ID應大于0x0F而小于0xF000;
其次,調用CWnd::GetSystemMenu獲取系統菜單的指針并調用CWnd:: Appendmenu將菜單項添加到菜單中。下例給系統菜單添加兩個新的
int CMainFrame:: OnCreate (LPCREATESTRUCT lpCreateStruct)
{
…
//Make sure system menu item is in the right range.
ASSERT (IDM_MYSYSITEM &0xFFF0)==IDM_MYSYSITEM)
ASSERT (IDM-MYSYSITEM<0xF000)
//Get pointer to system menu.
CMenu* pSysmenu=GetSystemmenu (FALSE)
ASSERT_VALID (pSysMenu)
//Add a separator and our menu item to system menu.
CString StrMenuItem (_T ("New menu item"))
pSysMenu->Appendmenu (MF_SEPARATOR)
pSysMenu->AppendMenu (MF_STRING, IDM_MYSYSITEM, strMenuitem)
…
}
現在,選擇系統菜單項時用戶應進行檢測。使用ClassWizard處理WM_SYSCOMMAND消息并檢測用戶菜單的nID參數:
void CMainFrame:: OnSysCommand (UINT nID,LPARAM lParam)
{
//Determine if our system menu item was selected.
if ( (nID & 0xFFF0)==IDM_MYSYSITEM)
{
//TODO-process system menu item
}
else
CMDIFrameWnd ::OnSysCommand (nID, lParam)
}
最后,一個設計良好的UI應用程序應當在系統菜單項加亮時在狀態條顯示一個幫助信息,這可以通過增加一個包含系統菜單基ID的串表的入口來實現。
(63)?如何確定頂層菜單所占據的菜單行數
這可以通過簡單的減法和除法來實現。首先,用戶需要計算主框窗口的高度和客戶區;其次,從主框窗口的高度中減去客戶區、框邊界以及標題的高度;最后,除以菜單欄的高度。下例成員函數是一個計算主框菜單所占據的行數的代碼實現。
int CMainFrame:: GetMenuRows ()
{
CRect rcFrame,rcClient
GetWindowRect (rcFrame)
GetClientRect (rcClient)
return (rcFrame.Height () -rcClient.Height () - :: GetSystemMetrics(SM_CYCAPTION) - (:: getSystemMetrics(SM_CYFRAME) *2)) / :: GetSystemMetrics(SM_CYMENU)
}
(64)?在用戶環境中如何確定系統顯示元素的顏色
調用SDK函數GetSysColor可以獲取一個特定顯示元素的顏色。下例說明了如何在MFC函數CMainFrameWnd:: OnNcPaint中調用該函數設置窗口標題顏色。
void CMiniFrameWnd:: OnNcPaint ()
{
…
dc.SetTextColor (:: GetSysColor (m_bActive ? COLOR_CAPTIONTEXT : COLOR_INACTIVECAPTIONTEXT))
…
(65)?如何查詢和設置系統參數
在Windows 3.1 SDK中介紹過SDK函數SystemParametersInfo,調用該函數可以查詢和設置系統參數,諸如按鍵的重復速率設置、鼠標雙擊延遲時間、圖標字體以及桌面覆蓋位圖等等。
//Create a font that is used for icon titles.
LOGFONT stFont
∶: SystemParametersInfo (SPIF_GETICONTITLELOGFONT, sizeof (LOGFONT), &stFont, SPIF_SENDWININICHANGE)
m_font.CreateFontIndirect (&stFont)
//Change the wallpaper to leaves.bmp.
∶ : SystemParametersInfo (SPI_SETDESKWALLPAPER, 0, _T (" forest.bmp"), SPIF_UPDATEINIFILE)
(66)?如何確定當前屏幕分辨率
調用SDK函數GetSystemMetrics,該函數可以檢索有關windows顯示信息,諸如標題大小、邊界大小以及滾動條大小等等。
//Initialize CSize object with screen size.
CSize sizeScreen (GetSystemMetrics (SM_CXSCREEN),
GetSystemMetrics (SM_CYSCREEN))
(67)?如何使用一個預定義的Windows光標
調用CWinApp:: LoadStandardCursor并傳送光標標識符。
BOOL CSampleDialog:: OnSetCursor (CWnd* pWnd,
UINT nHitTest, UINT
message)
{
//Display wait cursor if busy.
if (m_bBusy)
{
SetCursor (AfxGetApp () ->LoadStandardCursor (IDC_WAIT))
return TRUE
}
return CDialog:: OnSetCursor (pWnd. nHitTest,message)
}
(68)?如何檢索原先的Task Manager應用程序使用的任務列表
原先的Task Manager應用程序顯示頂層窗口的列表。為了顯示該列表,窗口必須可見、包含一個標題以及不能被其他窗口擁有。調用CWnd:: GetWindow可以檢索頂層窗口的列表,調用IsWindowVisible、GetWindowTextLength以及GetOwner可以確定窗口是否應該在列表中。下例將把TaskManager窗口的標題填充到列表中。
void GetTadkList (CListBox&list)
{
CString strCaption
//Caption of window.
list.ResetContent ()
//Clear list box.
//Get first Window in window list.
ASSERT_VALID (AfxGetMainWnd ())
CWnd* pWnd=AfxGetMainWnd () ->GetWindow (GW_HWNDFIRST)
//Walk window list.
while (pWnd)
{
// I window visible, has a caption, and does not have an owner?
if (pWnd ->IsWindowVisible()
&& pWnd ->GetWindowTextLength ()
&&! pWnd ->GetOwner ())
{
//Add caption o window to list box.
pWnd ->GetWindowText (strCaption)
list.AddString (strCaption)
}
//Get next window in window list.
pWnd=pWnd ->GetWindow(GW_HWNDNEXT)
}
}
(69)?如何確定Windows和Windows系統目錄
有兩個SDK函數可以完成該功能。GetWindowsDirectory和GetSystemDirectory,下例說明了如何使用這兩個函數:
TCHAR szDir [MAX_PATH]
//Get the full path of the windows directory.
∶ : GetWindowsDirectory (szDir, MAX_PATH)
TRACE ("Windows directory %s/n", szDir)
//Get the full path of the windows system directory.
∶ : GetSystemDirectory (szDir, MAX_PATH)
TRACE ("Windows system directory %s/n", szDir)
(70)?在哪兒創建臨文件
調用SDK函數GetTemPath可以確定臨時文件的目錄,該函數首先為臨時路徑檢測TMP環境變量:如果沒有指定TMP,檢測TMP環境變量,然后返回到當前目錄。下例說明了如何創建一個臨時文件。
…
//get unique temporary file.
CString strFile
GetUniqueTempName (strFile)
TRY
{
//Create file and write data.Note that file is closed
//in the destructor of the CFile object.
CFile file (strFile,CFile ::modeCreate | CFile:: modeWrite)
//write data
}
CATCH (CFileException, e)
{
//error opening file
}
END_CATCH
…
Void GetuniqueTempName (CString& strTempName)
{
//Get the temporary files directory.
TCHAR szTempPath [MAX_PATH]
DWORD dwResult=:: GetTempPath (MAX_PATH, szTempPath)
ASSERT (dwResult)
//Create a unique temporary file.
TCHAR szTempFile [MAX_PATH]
UINT nResult=GetTempFileName (szTempPath, _T ("~ex"),0,szTempfile)
ASSERT (nResult)
strTempName=szTempFile
}
(71)?我怎樣才能建立一個等待光標?
調 用 BeginWaitCursor 函 數 來 啟 動 等 待 光 標,調 用 EndWaitCursor 函 數 來 結 束 等 待 光 標。要 注 意,二 者 都 要 調 用 app 的 成 員 函 數,如 下 所 示:
????AfxGetApp()->BeginWaitCursor();
????// 要做的事
????AfxGetApp()->EndWaitCursor();
(72)?我在MDI框架中有個 form 視窗。它有個取消按鈕,我需要當用戶按取消按鈕時可關閉form視窗。我應該如何關閉該文檔?
調 用 OnCloseDocument 函 數。
(73)?如何訪問桌面窗口
靜態函數CWnd:: GetDesktopWindow 返回桌面窗口的指針。下例說明了MFC函數CFrameWnd::BeginModalStae是如何使用該函數進入內部窗口列表的。
void CFrameWnd::BeginModalState ()
{
…
//first count all windows that need to be disabled
UINT nCount=0
HWND hWnd= :: GetWindow (:: GetDesktopWindow(), GW_CHILD)
while (hWnd!=NULL)
{
if (:: IsWindowEnabled (hwnd)
&& CWnd::FromHandlePermanent (hWnd)!=NULL
&& AfxIsDescendant (pParent->m_hWnd, hWnd)
&& :: SendMessage (hWnd, WM_DISABLEMODAL, 0, 0)==0)
{
++nCount
}
hWnd=:: GetWindow (hWnd, GW_HWNDNEXT)
}
…
(74)?什么是COLORREF? 我該怎樣用它?
COLORREF 是 一 個 32-bit 整 型 數 值,它 代 表 了 一 種 顏 色。你 可 以 使 用 RGB 函 數 來 初 始 化 COLORREF。例 如:
????COLORREF color = RGB(0, 255, 0);
RGB 函 數 接 收 三 個 0-255 數 值,一 個 代 表 紅 色, 一 個 代 表 綠 色, 一 個 代 表 藍 色。在 上 面的 例 子 中, 紅 色 和 藍 色 值 都 為 0,所 以 在 該 顏 色 中 沒 有 紅 色 和 藍 色。綠 色 為 最 大 值 255。所 以 該 顏 色 為 綠 色。0,0,0 為 黑 色,255,255,255 為 白 色。
另 一 種 初 始 化 COLORREF 的 方 法 如 下 所 示:
????CColorDialog colorDialog;
????COLORREF color;
????if( colorDialog.DoModal() == IDOK )
????{
????????color = colorDialog.GetColor();
????}
這 段 代 碼 使 用 了 MFC 中 的 顏 色 對 話 框,它 需 要 文 件。
(75)?AppWizard所產生的STDAFX文件是干什么用的?
它 主 要 是 協 助 產 生 預 編 譯 頭 文 件 的。通 常 你 是 不 需 要 修 改 它 的。
(76)?我在我的程序中是了CDWordArray。我向它添加了約10,000個整數,這使得它變得非常非常慢。為什么會這么糟?
CDWordArray 是 很 好 用 的,只 是 因 為 你 沒 有 指 定 數 組 的最大尺寸。因 此,當 你 添 加 新 元 素 時,該 類 會 從 堆 中 重 新 分 配 空 間。不 幸 的 是,該 類 會 在 每 次 插 入 新 元 素 時 都 為 數 組 重 新 分 配 空 間。如 果 你 向 它 添 加 了 很 多 新 元 素,所 有 這 些 分 配 和 復 制 數 組 的 操 作 會 就 會 使 它 變 慢。解 決 該 問 題 的 方 法 是,你 可 以 使 用 SetSize 函 數 的 第 二 個 參 數 來 改 變 這 種 重 新 分 配 的 頻 率。例 如,如 果 你 把 該 參 數 設 置 為 500,則 每 次 數 組 空 間 超 出 時 它 才 重 新 分 配 并 添 加 500 個 新 空 間,而 不 是 1 個。這 樣 一 來,你 就 可 以 不 用 重 新 分 配 而 添 加 了 另 外 499 個 元 素 空 間,這 也 會 大 大 提 高 程 序 的 運 行 速 度。
(77)?我該如何改變MDI框架窗口的子窗口的大小以使在窗口以一定的大小打開?
在 視 中 的 OnInitialUpdate 函 數 中 調 用 GetParentFrame 函 數。GetParentFrame 會 返 回 一 指 向 一 保 存 有 該 視 的 框 架 窗 口 的 指 針。然 后 調 用 在 框 架 窗 口 上 調 用 MoveWindow。
(78)?在我的程序的某些部分,我可以調用 MessageBox 函數來建立一個信息對話框,例如在視類中。但是,在其它部分我卻不能,如文檔類中。為什么?我怎樣才能在我的應用程序類中建立一個信息對話框?
MessageBox 函 數 來 自 CWnd 類,所 以 你 只 能 在 從 CWnd 繼 承 的 類 ( 如 CView ) 中 調 用 它。但 是,MFC 也 提 供 了 AfxMessageBox 函 數,你 可 以 在 任 何 地 方 調 用 它。
(79)?我需要在我的程序中設置全局變量,以使文檔中的所有類都能訪問。我應該吧它放到哪兒?
把 該 變 量 放 到 該 應 用 程 序 類 的 頭 文 件 中 的 attribute 處。然 后,在 程 序 的 任 何 地 方,你 都 可 以 用 下 面 的 方 法 來 訪 問 該 變 量:
????CMyApp *app = (CMyApp *)AfxGetApp();
????app->MyGlobalVariable = ...
(80)?我聽說MFC可以發現內存漏洞,我怎樣使用該特性?
如 果 你 在 Debug 菜 單 中 的 Go 選 項 ( 不 是 Project 菜 單 中 的 Execute 選 項 ) 來 運 行 你 的 應 用 程 序,MFC 應 該 在 程 序 終 止 時 報 告 內 存 漏 洞。如 果 沒 有,那 么 試 試 運 行 MFC Tracer 工 具 程 序 ( 在 VC++ 程 序 組 中 ),并 啟 動 跟 蹤。然 后 返 回 應 用 程 序。
(81)?我怎樣才能在我的應用程序中循環瀏覽已經打開的文檔?
使用CDocTemplate中未公開的GetFirstDocPosition()和GetNextDoc()函數。
(82)才能在我的應用程序中循環瀏覽已經打開的視?
使 用 CDocument 中 未 公 開 的 GetFirstViewPosition() 和 GetNextView() 函 數。
(83)數PreCreateWindow是干什么用的?
PreCreateWindow 允 許 你 在 調 用 CreateWindow 之 前 來 改 變 窗 口 屬 性。
(84)該怎樣防止MFC在窗口標題欄上把文檔名預置成應用程序名?
在 PreCreateWindow 函 數 中 刪 除 FWS_PREFIXTITLE 標 志 的 窗 口 樣 式:
????cs.style &= ~FWS_PREFIXTITLE;
(85)?我應該怎樣防止MFC在窗口標題欄上添加文檔名?
在 PreCreateWindow 函 數 中 刪 除 FWS_ADDTOTITLE 標 志 的 窗 口 樣 式:
????cs.style &= ~FWS_ADDTOTITLE ;
(86)?我應該如何改變視窗口的大小?
因 為 視 窗 口 實 際 上 是 框 架 窗 口 的 子 窗 口,所 以 你 必 須 改 變 框 架 窗 口 的 大 小,而 不 是 改 變 視 窗 口。使 用 CView 類 中 的 GetParentFrame() 函 數 獲 得 指 向 框 架 窗 口 的 指 針,然 后 調 用 MoveWindow() 函 數 來 改 變 框 架 的 大 小。這 會 使 變 尺 寸 的 視 充 滿 框 架 窗 口。
(87)?我有一無模式對話框。我怎樣才能在窗口退出時刪除CDialog對象?
把“delete this”加 到 PostNcDestroy 中。這 主 要 用 在 需 要 自 動 刪 除 對 象 的 場 合。
(88)?為什么把“delete this”放在PostNcDestroy中而不是OnNcDestroy?
OnNcDestroy 只 被 已 建 立 的 窗 口 調 用。如 果 建 立 窗 口 失 敗 ( 如 PreCreateWindow ),則 沒 有 窗 口 處 來 發 送 WM_NCDESTROY 消 息。PostNcDestroy 是 在 對 象 窗 口 被 完 全 刪 除,在 OnNcDestroy 后,甚 至 在 窗 口 建 立 失 敗 之 后 調 用 的。
(89)?File菜單中的MRU列表是從哪兒來的?列表中的名字放在哪兒了?我怎樣才能改變列表中項目的最大值?
在 應 用 程 序 類 的 InitInstance 函 數 中 對 LoadStdProfileSettings 的 調 用 中。該 調 用 接 受 一 個 參 數 ( 在 缺 省 情 況 下 如 果 沒 有 傳 遞 值 則 為 4 )。MRU 文 件 名 是 從 INI 文 件 中 調 用 的。如 果 你 有 帶 有 ID_FILE_MRU_FILE1 的 ID 的 菜 單 選 項,它 會 為 調 入 的 MRU 列 表 所 替 換。如 果 你 改 變 傳 遞 給 LoadStdProfileSettings 的 數 值 ( 最 大 為 16 ),則 你 就 改 變 了 所 裝 如 文 件 名 的 最 大 值。
(90)?我在菜單中添加了新的項。但是,當我選該項時,在狀態欄上沒有出現任何提示信息。為什么?
打 開 資 源 文 件 中 的 菜 單 模 板。打 開 新 菜 單 選 項 的 屬 性 對 話 框。在 對 話 框 的 底 部 的 Prompt 編 輯 框 中 ,你 可 以 如 下 指 定 狀 態 欄 上 的 提 示 信 息 和 工 具 欄 上 的 提 示 信 息 ( 如 果 你 已 經 建 立 的 工 具 欄 按 鈕 ):
????Status bar string/nFlying tag
(91)?我怎樣才能在應用程序的缺省系統菜單中加上一些東西?
系 統 菜 單 與 其 它 菜 單 類 似,你 可 以 添 加 或 刪 除 項 目,這 需 要 使 用 CMenu 類 的 成 員 函 數。下 面 的 代 碼 在 你 的 系 統 菜 單 后 面 添 加 一 個 新 菜 單 項:
????CMenu *sysmenu;
????sysmenu = m_pMainWnd->GetSystemMenu(FALSE);
????sysmenu->AppendMenu(MF_STRING, 1000, "xxx");
參 見 MFC 幫 助 文 件 中 的 CMenu 類。
(92)?我建立了一個對話框。但是當我顯示該對話框時,第一個編輯框總是不能獲得焦點,我必須單擊它來使它獲得焦點。我怎樣才能使第一個編輯框在對話框打開時就獲得焦點?
打 開 資 源 編 輯 器 中 的 對 話 框 模 板。在 Layout 菜單 中 選 擇 Tab Order 選 項。按 你 的 需 求 單 擊 對 話 框 中 的 控 制 來 重 新 排 列 這 些 控 制 的 tab 順 序。
(93)?我怎樣才能使一個窗口具有“always on top”特性?
在 調 用 OnFileNew 后,在 你 的 InitInstance 函 數 中 加 上 下 面 的 代 碼:
m_pMainWnd->SetWindowPos(&CWnd::wndTopMost,0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);
(94)?? 我要為我的form view添加文檔模板。我先建立了對話框模板,然后使用ClassWizard建立了基于CFormView的新類,它也是從CDocument繼承來的。我還建立了相應的資源并在InitInstance中添加了新的文檔模板。但是,當我試圖運行該程序時,出現了Assertion信息。為什么?
form 的 對 話 框 模 板 需 要 些 特 殊 設 置 以 便 可 用 于 CFromView。確 保 這 些 設 置 的 最 簡 單 方 法 是 使 用 AppWizard 來 建 立 CFormView 應 用 程 序,并 查 看 AppWizard 所 建 立 的 對 話 框 模 板 所 選 擇 的Styles Properties。你 會 發 現 該 對 話 框 模 板 具 有 下 列 樣 式:沒 有 標 題 欄、不 可 見 和“Child”。把 你 的 form view 的 對 話 框 屬 性 變 成 這 樣 就 可 以 了。
(95)?? 我在一對話框中有一列表框,我需要tabbed列表框中的項目。但是,當我處理含有tab字符(用AddString添加的)的列表項時,tab被顯示成小黑塊而沒有展開。哪兒出錯了?
在 對 話 框 模 版 中,打 開 列 表 框 的 屬 性。確 保 選 擇 了“Use Tabstops” 樣 式。然 后,確 保 在 對 話 框 類 中 OnInitDialog 函 數 中 調 用 SetTabStops。
(96)??我建立了一個應用程序,并使用了CRecordset類。但是,當我運行該程序時,它試圖要訪問數據庫,并給出“Internal Application Error”對話框。我應該怎樣做?
通 常 情 況 下,當 你 的 程 序 中 向 數 據 庫 發 送 信 息 的 SQL 語 句 出 現 問 題 時 才 出 現 該 對 話 框。例 如,參 見 下 面 的 例 子:
????set.m_strFilter = "(ZipCode = '27111')";
如 果 ZipCode 列 被 定 義 為 字 符 串 時 不 會 出 現 問 題,如 果 定 義 為 long,則 會 出 現“Internal Application Error”對 話 框,這 是 由 于 類 型 不 匹 配 的 緣 故。如 果 你 刪 除 27111 的 單 引 號,則 不 會 出 現 問 題。當 你 看 到“Internal Application Error”時,最 好 檢 查 一 下 試 圖 要 發 送 給 數 據 庫 的 SQL 語 句。
(97)?? 我用ClassWizard建立了一個類。但是,我把名字取錯了,我想把它從項目中刪除,應該如何做?
在 ClassWizard 對 話 框 關 閉 后,用 文 件 管 理 器 刪 除 新 類 的 H 和 CPP 文 件。然 后 打 開 ClassWizard,它 會 提 示 丟 失 了 兩 個 文 件,并 詢 問 你 該 如 何 做。你 可 以 選 擇 從 項 目 中 刪 除 這 兩 個 問 的 按 鈕。
(98)???? 當我打開應用程序中的窗口時,我要傳遞該窗口的矩形尺寸。該矩形指定了窗口的外圍大小,但是當我調用GetClientRect時,所得到的尺寸要比所希望的值要小(因為工具欄和窗口邊框的緣故)。有其它方法來計算窗口的尺寸嗎?
參 見 CWnd::CalcWindowRect。
(99)?? 我在文檔類中設置了一個整型變量。但是,當我試圖把該變量寫入Serialize函數中的archive文件中時,出現了類型錯誤。而文檔中的其它變量沒有問題。為什么?
archive 類 只 重 載 某 些 類 型 的 >> 和 << 操 作 符。“int”類 型 沒 有 在 其 中,也 許 是 因 為 int 變 量 在 Windows 3.1 與 Windows NT/95 有 所 不 同 的 緣 故 吧。“long”類 型 得 到 了 支 持,所 以 你 可 以 把 int 類 型 改 成 long 型。參 見 MFC 幫 助 文 件 中 CArchive 類。
(100)??如何控制菜單的大小?
我用MFC的CMenu生成了一個動態菜單(例如File,Edit,View...Help), 我想控制這個菜單的大小(長+高).
方法一:查找 WM_MEASUREITEM 和 MEASUREITEMSTRUCT.
方法二:查詢系統::GetSystemMetric(SM_CXMENUSIZE).
???? /* 你可以通過如下代碼來獲得文本的大小:
????????(A)獲得被使用的字體 */
?????? NONCLIENTMETRICS ncm;
???? HFONT hFontMenu;
???? SIZE size;
???? size.cy = size.cy = 0;
???? memset(&ncm, 0, sizeof(NONCLIENTMETRICS));
???? ncm.cbSize = sizeof(NONCLIENTMETRICS);
???? if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0))
???? {
??????????hFontMenu = CreateFontIndirect(&ncm.lfMenuFont);
??????????/*
??????????(B) 獲得菜單項的文本: */
??????????char szText[_MAX_PATH];
??????????pMenu->GetMenuString(0, szText, _MAX_PATH, MF_BYPOSITION);
??????????/*
??????????然后,獲得菜單項文本的高度: */
??????????HFONT hFontOld;
??????????HDC hDC;
??????????hDC = ::GetDC(NULL);
??????????hFontOld = (HFONT) ::SelectObject(hDC, hFontMenu);
??????????GetTextExtentPoint32(hDC, szText, lstrlen(szText), &size);
??????????SelectObject(hDC, hFontOld);
??????????::ReleaseDC(NULL, hDC);
???? }
???? /*此時,size.cy即為高度,size.cx為寬度,你可以給菜單加上自定義的高度和寬度,通過比較,我發現寬度為4
比較合適。*/
(101)??改變LVIS_SELECTED的狀態顏色?
我想將CListCtrl項和CTreeCtrl項在LVIS_SELECTED狀態時的顏色變灰.
方法一:查找函數CustomDraw,它是IE4提供的公共控制,允許有你自己的代碼.
方法二:生成一個draw控件,然后在DrawItem中處理文本顏色.
(102)?? 如何只存儲文檔的某一部分?
我只想存儲文檔的某一部分,能否象使用文件一樣使用文檔?(也就是有定位函數).將每個CArchive類設置為CFile類的派生類,這樣你就能使用Seek等成員函數.
(103)?? 保存工具條菜單有bug嗎?
使用浮動菜單條時,SaveBarState和LoadBarState出現了問題.如果菜單是浮動的,重起應用程序時它會出現在左上角,而它固定在屏幕其它位置時,下一次啟動就會出現在該位置,這是什么原因?你試試這個PToolBar->Create(this,...,ID_MYTOOLBAR);
你的工具條需要包括id,而不是象默認的工具條那樣.
(104)?? Tip of the day的bug
我創建了一個簡單的mdi應用程序,使用.BSF(自定義的文檔擴展名)作為它的文檔我保存一個foo.bsf文檔后,可以在資源管理器中雙擊該文件打開mdi應用程序同時打開foo.bsf文檔.但當我給mdi應用程序加上a tip of the day組件之后,從資源管理器中雙擊foo.bsf后,就會給我一個警告:ASSERT(::IsWindow(m_hWnd)),然后mdi應用程序就死那了.
當從dde啟動應用程序(例如:雙擊相關文檔)時,"Tip of the Day"是有bug的.你可以看看函數"ShowTipAtStartup",它在"InitInstance"中調用,可以看到tip of the day作為一個模式對話框顯示,在處理其它消息時它一直進行消息循環你可心修改ShowTipAtStartup使其從dde啟動時不出現tip of the day.
void CTipOfApp::ShowTipAtStartup(void)
????????{
????????????????// CG: This function added by 'Tip of the Day' component.
????????????????CCommandLineInfo cmdInfo;
????????????????ParseCommandLine(cmdInfo);
????????????????if (
????????????????????????cmdInfo.m_bShowSplash &&
????????????????????????cmdInfo.m_nShellCommand != CCommandLineInf:FileDDE
????????????????????????)
????????????????{
????????????????????????CTipDlg dlg;
????????????????????????if (dlg.m_bStartup)
????????????????????????????????dlg.DoModal();
????????????????}
????????}
如果還有其它bug,你可以設定cmdInfo.m_nShellCommand的過濾.
(105)?? 如何可以讓我的程序可以顯示在其它的窗口上面?
讓用戶選擇"總是在最上面"最好是在系統菜單里加入一個選項.可以通過修改WM_SYSCOMMAND消息來發送用戶的選擇.菜單的命令標識(id)會作為一個參數傳給OnSysCommand().要定義標識(id),將如下代碼加入到CMainFrame.CPP中:
????#define WM_ALWAYSONTOP WM_USER + 1
將"總在最上面"的菜單項加入到系統菜單中,將如下代碼加入到函數CMainFrame::OnCreate()中:
??????CMenu* pSysMenu = GetSystemMenu(FALSE);
??????pSysMenu->AppendMenu(MF_SEPARATOR);
??????pSysMenu->AppendMenu(MF_STRING, WM_ALWAYSONTOP,
???????????????????? "&Always On Top");
使用ClassWizard,加入對WM_SYSCOMMAND消息的處理,你應該改變消息過濾器,使用系統可以處理這個消息.
void CMainFrame::OnSysCommand(UINT nID, LPARAM lParam)
{
????switch ( nID )
????{
????case WM_ALWAYSONTOP:
????????if ( GetExStyle() & WS_EX_TOPMOST )
????????{
????????????SetWindowPos(&wndNoTopMost, 0, 0, 0, 0,
????????????????SWP_NOSIZE | SWP_NOMOVE);
????????????GetSystemMenu(FALSE)->CheckMenuItem(WM_ALWAYSONTOP,
????????????????MF_UNCHECKED);
????????}
????????else
????????{
????????????SetWindowPos(&wndTopMost, 0, 0, 0, 0,
????????????????SWP_NOSIZE | SWP_NOMOVE);
????????????GetSystemMenu(FALSE)->CheckMenuItem(WM_ALWAYSONTOP,MF_CHECKED);
????????}
????????break;
????default:
????????CFrameWnd::OnSysCommand(nID, lParam);
????}
}
(106)????如何控制窗口框架的最大最小尺寸?
要控制一個框架的的最大最小尺寸,你需要做兩件事情.在CFrameWnd的繼承類中處理消息WM_GETMINMAXINFO,結構MINMAXINFO設置了整個窗口類的限制,因此記住要考慮工具條,卷動條等等的大小.
// 最大最小尺寸的象素點 - 示例
#define MINX 200
#define MINY 300
#define MAXX 300
#define MAXY 400
void CMyFrameWnd::OnGetMinMaxInfo(MINMAXINFO FAR* lpMMI)
{
????CRect rectWindow;
????GetWindowRect(&rectWindow);
????CRect rectClient;
????GetClientRect(&rectClient);
??????// get offset of toolbars, scrollbars, etc.
????int nWidthOffset = rectWindow.Width() - rectClient.Width();
????int nHeightOffset = rectWindow.Height() - rectClient.Height();
????lpMMI->ptMinTrackSize.x = MINX + nWidthOffset;
????lpMMI->ptMinTrackSize.y = MINY + nHeightOffset;
????lpMMI->ptMaxTrackSize.x = MAXX + nWidthOffset;
????lpMMI->ptMaxTrackSize.y = MAXY + nHeightOffset;
}
第二步,在CFrameWnd的繼承類的PreCreateWindow函數中去掉WS_MAXIMIZEBOX消息,否則在最大化時你將得不到預料的結果.
BOOL CMyFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
????cs.style &= ~WS_MAXIMIZEBOX;
????return CFrameWnd::PreCreateWindow(cs);
}
(107)????如何改變窗口框架的顏色?
MDI框架的客戶區被另一個窗口的框架所覆蓋.為了改變客戶區的背景色,你需要重畫這個客戶窗口.為了做到這點,你要處理消息WM_ERASEBKND產生一個新類,從CWnd繼承,姑且稱之為CMDIClient.給它加上一個成員變量,
#include "MDIClient.h"
class CMainFrame : public CMDIFrameWnd
{
...
protected:
CMDIClient m_wndMDIClient;
}
在CMainFrame中重載CMDIFrameWnd::OnCreateClient
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
????if ( CMDIFrameWnd::OnCreateClient(lpcs, pContext) )
????{
????????m_wndMDIClient.SubclassWindow(m_hWndMDIClient);
????????return TRUE;
????}
????else
????????return FALSE;
}
然后就可以加入對消息WM_ERASEBKGND的處理了.
(108)????如何將應用程序窗口置于屏幕正中?
要將你的應用程序窗口放置在屏幕正中央,只須在MainFrame的OnCreate函數中加入:
CenterWindow( GetDesktopWindow() );
總結