滑動控件是Windows中最常用的控件之一。一般而言它是由一個滑動條,一個滑塊和可選的刻度組成,用戶可以通過移動滑塊在相應的控件中顯示對應的值。通常,在滑動控件附近一定有標簽控件或編輯框控件,用于顯示相應的值。滑動控件在應用程序中用途級為廣泛,如在桌面的屬性中就可以看到。為此,讓我們一起來看一下它的實現方法。
?。?)在VC++ 6.0中新建一個對話框文檔的工程。
(2)打開資源管理器,在對話框中放置一個EDIT控件,然后在它旁邊放上一個Slider控件。基本的框架已經完成了。
(3)對Slider控件右擊,選擇“建立類向導”,對剛才的Slider控件定義一個變量m_Slider,類型為CSliderCtrl。
(4)在對話框初始化的代碼BOOL CMy601Dlg::OnInitDialog(),后添加相應的屬性。以下是常用的屬性設置函數:
* GetRange,SetRange函數
用于查詢和設置滑動條的取值范圍,默認為0~100。函數定義形式如下:
void GetRange(int &nMin,int &nMax) const;
void SetRange(int nMin,int nMax,BOOL bRedrGETaw=FALSE);
* GetPos,SetPos函數
用于查詢和設置滑動條的當前值。函數定義形式如下:
int GetPos() const;
int SetPos(int nPos);
* GetLineSize,SetLineSize函數
用于查詢和設置在按一下右或左箭頭時滑塊的移動量,默認為1個單位。函數定義形式如下:
int GetLineSize() const;
int SetLineSize(int nSize);
* GetPageSize,SetPageSize函數
用于查詢和設置函滑塊和塊移動量,塊移動量是指當按下PgUp或PgDown時滑塊的移動量。函數定 義形式如下:
int GetPageSize() const;
int SetPageSize(int nSize);
* SetTicFreq函數
用于設置滑動條刻度的頻度。默認為一個單位一個函數。函數定義形式如下:
void SetTicFreq(int nFreq);
* SetTic函數
用于在指定的位置設置刻度。Windows默認的刻度是均勻的。函數定義形式如下:
BOOL SetTic(int nTic);
* ClearTics函數
用于清除所有的刻度。函數定義形式如下:
void ClearTics(BOOL bRedraw=FALSE);
我們在初始化時寫入以下語句:
m_Slider.SetRange(-100,100); m_Slider.SetTicFreq(10); |
即:設置范圍為-100到100,刻度為每10個單位一個。
(5)現在我們加入事件過程代碼。
選擇Slider的“事件”然后選擇第一個過程(NM_CUSTOMDRAW)隨后加入以下代碼:
void CMy601Dlg::OnCustomdrawSlider1(NMHDR* pNMHDR, LRESULT* pResult) { UpdateData(TRUE); m_Int=m_Slider.GetPos(); UpdateData(FALSE); *pResult = 0; } |
其中m_Int是定義的EDIT控件的類型為INT的變量。至此我們的編輯工作結束了。
(6)編譯運行程序試試吧,很方便就使用了Slider 控件。

以上代碼在Windows2000,VC++ 6.0/VC++.net上調試通過。
在VC環境中除了我們所常用的Dialog、Menu和Bitmap等標準資源類型之外,它還支持自定義資源類型(Custom Resource),我們自定義的資源類型能做些什么呢?呵呵,用處多多。
- 默認的皮膚壓縮包或語言包。一些支持換膚的軟件特別是一些媒體播放器常常有自定義的皮膚文件(你可以嘗試將Media Player或千千靜聽等軟件的Skins目錄下的文件的擴展名改為.zip,然后使用WinZip打開看一下),但為了防止Skin文件丟失導致軟件無法顯示,他們常常會在EXE文件中內置一套Skin作為默認的皮膚文件。同樣,一些支持多語言的EXE文件中存在默認語言包也是這個道理(當然也可以使用"String Table"資源類型);
- 做為一些病毒/木馬程序的寄生方式。如果不小心執行了帶有病毒/木馬的程序,它們會在你運行時釋放出病毒/木馬文件。當然許多病毒是將自身寫入PE文件頭來實現;
- 合并EXE與它所需要的DLL文件。出于某些原因程序作者有時可能需要將DLL文件嵌入到可執行的EXE文件中,這可以通過使用自定義資源來實現;
- 其它需要在程序中播放一個AVI動畫等等,都可以通過將二進制的數據文件作為自定義資源加入到可執行文件中來實現;
二、添加
添加資源時選擇自定義,IDE會為你生成一個新的二進制資源,然后你就可以將你已經存在的二進制文件作為自定義的資源類型導入到項目中來了。
三、使用
要使用自定義資源,我們可能要用到的幾個API函數有FindResource、LoadResource和LockResource等,這里每一個函數的返回值分別作為下一個函數的參數,我來簡要介紹一下。
- FindResource用來在一個指定的模塊中定位所指定的資源:
HRSRC FindResource(
HMODULE hModule, //包含所需資源的模塊句柄,如果是程序本身,可以置為NULL
LPCTSTR lpName, //可以是資源名稱或資源ID
LPCTSTR lpType //資源類型,在這里也就是我們自己指定的資源類型
);
- LoadResource用來將所指定的資源加載到內存當中;
HGLOBAL LoadResource(
HMODULE hModule, //模塊句柄,同上
HRSRC hResInfo //需要加載的資源句柄,這里也就是FindResource的返回值
);
- LockResource用來鎖定內存中的資源數據塊,它的返回值也就是我們要使用的直系指向資源數據的內存指針;
LPVOID LockResource(
HGLOBAL hResData //指向內存中要鎖定的資源數據塊,這里也就是LoadResource的返回值
);
另外我們還需要用SizeofResource來確定資源的尺寸,我們在操作資源時要用到它。在資源使用完畢后我們不需要使用UnlockResource和FreeResource來手動地釋放資源,因為它們都是16位Windows遺留下來的,在Win32中,在使用完畢后系統會自動回收。它們的使用很簡單,大致上是這個樣子的:
BOOL UseCustomResource()
{
//定位我們的自定義資源,這里因為我們是從本模塊定位資源,所以將句柄簡單地置為NULL即可
HRSRC hRsrc = FindResource(NULL, MAKEINTRESOURCE(ITEMID), TEXT("MyType"));
if (NULL == hRsrc)
return FALSE;
//獲取資源的大小
DWORD dwSize = SizeofResource(NULL, hRsrc);
if (0 == dwSize)
return FALSE;
//加載資源
HGLOBAL hGlobal = LoadResource(NULL, hRsrc);
if (NULL == hGlobal)
return FALSE;
//鎖定資源
LPVOID pBuffer = LockResource(hGlobal);
if (NULL == pBuffer)
return FALSE;
//我們用剛才得到的pBuffer和dwSize來做一些需要的事情??梢灾苯釉趦却嬷惺?
//用,也可以寫入到硬盤文件。這里我們簡單的寫入到硬盤文件,如果我們的自定
//義資源是作為嵌入DLL來應用,情況可能要復雜一些。
BOOL bRt = FALSE;
FILE* fp = _tfopen(_T("demo.exe"), _T("wb"));
if (fp != NULL)
{
if (dwSize == fwrite(pBuffer, sizeof(char), dwSize, fp))
bRt = TRUE;
fclose(fp);
}
//FreeResource(hGlobal);
return bRt;
}
四、實例
下面我們準備用自定義資源來做兩件事情:一件是用我們生成的可執行文件的自定義資源中來釋放一個Hello World程序(類似于木馬程序釋放服務端);另一件是從自定義資源中解壓縮一個壓縮包(類似于換膚軟件釋放默認的皮膚文件)。這里我們模擬WinZip的Self-Extractor工具的界面和功能來完成它(呵呵,不過請不要誤會,WinZip嵌入到壓縮包的自解壓工具可不是像我們這樣實現的,這里只是來演示一下從自定義資源解壓縮多個文件的一個過程而已),具體的實現可以參考本文所附帶的源代碼。它最終運行起來大概是這么個樣子:

圖四 模仿Self-Extractor界面的運行結果
運行后,點擊"Release"按鈕會在當前目錄下釋放一個Win32版的Hello World程序;點擊"Unzip"按鈕則會在指定目錄釋放本Demo的工程文件及項目的所有源代碼,而編譯這個工程則恰恰得到了上面的可執行文件。
本工程在WinXP + VC.Net 2003 + WTL7.5環境下編譯并運行通過。
隨著計算機信息表示及實現的多媒體化,在許多學習軟件、游戲軟件,以及多媒體課件制作軟件中,經常使用各種圖形顯示技巧,如圖形的推拉、交錯、雨滴狀、百頁窗、積木隨機堆疊等顯示模式。這樣使畫面變得更為生動活潑,更能吸引用戶,也為更好地發揮軟件的功能奠定了基礎。本文就Visual C++ 6.0中實現圖形的各種顯示技巧的原理及具體方法做些探討。
基本原理
在Visual C++6.0中,顯示位圖的方法及過程如下:
1. 顯示程序資源中的位圖(位圖的所有數據均存在于可執行文件中)
(1)從資源中裝入位圖
● 定義位圖對象數據成員CBitmap m_Bitmap;
● 調用CBitmap成員函數LoadBitmap(),如m_Bitmap.LoadBitmap(IDB_BITMAP1);
● 傳入LoadBitmap的參數是位圖在圖形編輯器中生成或從位圖文件中引入時賦予的識別符。
(2)生成與位圖相聯系的內存設備情境對象
CDC MemDC;
MemDC.CreateCompatibleDC(NULL);
MemDC.SelectObject(&m_Bitmap);
(3)顯示位圖
CClientDC ClientDC(this);
BITMAP BM;
m_Bitmap.GetObject(sizeof(BM),&BM);
ClientDC.BitBlt
( X,Y, //目標設備邏輯橫、縱坐標
BM.bmWidth, BM.bmHeight, //顯示位圖的像素寬、高度
&MemDC,
//待顯示位圖數據的設備情境對象
0,0, //源數據中的橫、縱坐標
SRCCOPY); //位操作方式
這種方法顯示位圖速度快,但不是很靈活,而且會使可執行文件增大。
2. 顯示獨立文件方式的位圖(位圖的所有數據獨立于可執行文件)
HBITMAP *hBitmap; //定義位圖對象句柄
BITMAP BM;
CDC MemDC;
CClientDC ClientDC(this);
MemDC.CreateCompatibleDC(&ClientDC);
hBitmap=(HBITMAP*):: LoadImage
( AfxGetInstanceHandle(),
//取得應用程序句柄
“demo1.bmp”,
//位圖文件名
IMAGE_BITMAP,
//類型為Windows位圖
0,0,
LR_LOADFROMFILE);
//從文件中取位圖數據
MemDC.SelectObject(hBitmap);
:: GetObject(hBitmap,sizeof(BM),&BM);
ClientDC.BitBlt(……)
//使用格式與方法一同
這種方法顯示位圖速度較之前一種慢了一點,但其靈活性較大,可以任意變換位圖文件,而無需重新編譯源程序, 也減小了可執行文件的大小。
實現方法
下面介紹各種圖形顯示技巧的具體實現原理及方法。以下所有程序算法的實現均可放在視類(CView,也可視自己的需要放在其他類)中處理,且有必要進行如下的相關操作:
增加如下類成員變量:
BITMAP m_Bm;
//保存位圖的寬、高度等數據
HBITMAP *m_hBitmap;
//保存位圖數據句柄
CDC m_MemDC; //內存設備情境對象
在類構造函數中加入如下代碼:
m_MemDC.CreateCompatibleDC(NULL); //產生內存設備情境對象
m_hBitmap=(HBITMAP *)::LoadImage(
//從文件中裝入位圖數據
AfxGetInstanceHandle(),
“demo1.bmp”,
IMAGE_BITMAP,
0,0,
LR_LOADFROMFILE );
m_MemDC.SelectObject(m_hBitmap); //將位圖選入內存設備情境對象
::GetObject(m_hBitmap,sizeof(m_Bm),&m_Bm);
1. 水平交錯效果
原理:將內存設備情境對象(如MemDC)中的位圖數據拆分成奇、偶掃描線兩部分,其中奇數條掃描線由上往下移動,偶數條掃描線則由下往上移動,且兩者同時進行。屏幕上的效果為分別由上下兩端出現的較淡柵欄圖形,逐漸相互靠近,直至整個位圖完全清楚。垂直交錯效果的實現原理與之類似。
程序算法:
int i,j;
for ( i=0; i<=m_Bm.bmHeight; i+=2 )
{j = i;
while ( j>0 )
{ClientDC.StretchBlt(
//奇數,由上至下
0,j-1,
//目標設備邏輯橫、縱坐標
m_Bm.bmWidth,1,
//顯示位圖的像素寬、高度
&m_MemDC,
//源位圖設備情境對象
0,m_Bm.bmHeight-(i-j-1),
//源位圖的起始橫、縱坐標
m_Bm.bmWidth,1,
//源位圖的像素寬、高度
SRCCOPY);
ClientDC.StretchBlt(
//偶數,由下至上
0,m_Bm.bmHeight-j,
//目標設備邏輯橫、縱坐標
m_Bm.bmWidth,1,
//顯示位圖的像素寬、高度
&m_MemDC,
//源位圖設備情境對象
0,i-j,
//源位圖的起始橫、縱坐標
m_Bm.bmWidth,1,
//源位圖的像素寬、高度
SRCCOPY);
j-=2; }
// while ( j>0 )
Sleep(10);
}
//for ( i=0; i<=m_Bm.bmHeight; i+ =2 )
2. 雨滴效果
原理:將內存設備情境對象(如MemDC)中位圖數據的最后一條掃描線,順序地從目標設備(如ClientDC)中待顯示位圖的第一條掃描線所在位置移動至最后一條處,并保留此條掃描線在屏幕上移動時留下的軌跡。接著再把MemDC中位圖數據的倒數第二條掃描線,順序地從目標設備(如ClientDC)中待顯示位圖的第一條掃描線所在位置移動至倒數第二條處。其余的掃描線依此類推。
程序算法:
int i,j;
for ( i=0; i<=m_Bm.bmHeight; i++ )
{for ( j=0; j<=m_Bm.bmHeight-i; j++ )
ClientDC.StretchBlt(
0,j,
//目標設備邏輯橫、縱坐標
m_Bm.bmWidth,1,
//顯示位圖的像素寬、高度
&m_MemDC,
//源位圖設備情境對象
0,m_Bm.bmHeight-i,
//源位圖的起始橫、縱坐標
m_Bm.bmWidth,1,
//源位圖的像素寬、高度
SRCCOPY);
Sleep(20);
}
//for ( i=0; i<=m_Bm.bmHeight; i++ )
3. 百葉窗效果
原理:將內存設備情境對象(如MemDC)中的位圖數據分成若干組,然后分別從第一組到最后一組進行搬移,第一次搬移每組中第一條掃描線到目標設備(如ClientDC)中待顯示位圖的相應位置,第二次搬移每組中第二條掃描線,接著第三條、第四條掃描線。
程序算法:
int i,stepi,j;
stepi=m_Bm.bmHeight/10;
for ( i=0; i<=stepi; i++ )
{for ( j=0; j<10; j++ )
ClientDC.StretchBlt(
0,j*stepi+i,
//目標設備邏輯橫、縱坐標
m_Bm.bmWidth,1,
//顯示位圖的像素寬、高度
&m_MemDC,
//源位圖設備情境對象
0,j*stepi+i,
//源位圖的起始橫、縱坐標
m_Bm.bmWidth,1,
//源位圖的像素寬、高度
SRCCOPY);
Sleep(20);
} //for ( i=0; i<=stepi; i++ )
4. 隨機積木效果
原理:將內存設備情境對象(如MemDC)中的位圖數據分成縱橫十等份共一百組數據,然后隨機地取出這一百組數據中的某一組顯示到目標設備(如ClientDC)中待顯示位圖的相應位置,如此反復直到所有一百組數據均顯示完畢為止。
程序算法:
int i,j,stepx,stepy,dispnum,x,y;
int pxy[10][10];
//使用本數組記錄已顯示過的數據組
for ( i=0; i<10; i++ )
for ( j=0; j<10; j++ )
pxy[i][j]=0;
stepx=m_Bm.bmWidth/10;
stepy=m_Bm.bmHeight/10;
srand( (unsigned)time( NULL ) );
dispnum=0;
//記錄已顯示過的數據組的個數
while(1)
{ x=rand() % 10;
y=rand() % 10;
if ( pxy[x][y] )
//本組x,y所代表的數據組是否已顯示過?
continue;
pxy[x][y]=1;
//表明本組x,y所代表的數據組已顯示過
ClientDC.StretchBlt(
x*stepx, y*stepy,
//目標設備邏輯橫、縱坐標
stepx,stepy,
//顯示位圖的像素寬、高度
&m_MemDC,
//源位圖設備情境對象
x*stepx, y*stepy,
//源位圖的起始橫、縱坐標
stepx,stepy,
//源位圖的像素寬、高度
SRCCOPY);
dispnum++;
if ( dispnum >=100 )
break;
Sleep(30);
} // while(1)
結 語
以上程序代碼均在Visual C++ 6.0中調試通過,所有片斷均可編寫成獨立的函數,靈活使用。如果對以上幾種顯示效果進行變換,我們還可以實現多種其他特技效果。
使用啟動畫面一是可以減少等待程序加載過程中的枯燥感(尤其是一些大型程序);二是可以用來顯示軟件名稱和版權等提示信息。怎樣使用VC++制作應用程序的啟動畫面呢?本文提供四種方法,前三種適用于基于文檔的應用程序,第四種適用于基于對話框的應用程序。
1.利用組件庫中的Splash Screen組件實現
(1)用Photoshop等制作啟動畫面圖像,保存為bmp格式。
(2)用Appwizard建一個基于單文檔的工程Splash。
(3)在資源中插入位圖資源
打開VC++的資源編輯器,用鼠標右鍵單擊Resources文件夾,選擇Import命令,插入所制作的位圖。如果位圖超過256色,VC會彈出一個對話框,提示位圖已經插入但不能在位圖編輯器中顯示,確定即可。將位圖ID改為IDB_SPLASH。
(4)添加Splash Screen控件
?、龠x擇菜單“project”/“Add To Project”/“Conponents and Controls”打開對話框,在列表框中雙擊“Visual C++ Conponents”選項,選擇“Splash Screen”控件,然后單擊“Insert”。
②確認或修改類名和位圖資源ID,單擊OK確認。
③編譯、連接,漂亮的啟動畫面就顯示出來了。
(5)如果需要改變啟動畫面的停留時間,就修改SetTimer()函數的第二個參數,默認是750 毫秒。該函數所在位置:
int CSplashWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
...
// Set a timer to destroy the splash screen.
SetTimer(1, 750, NULL); //修改第二個參數以調整畫面停留時間
return 0;
}
2.利用無模式對話框顯示啟動畫面
(1)用Appwizard建一個基于單文檔的工程Splash。
(2)導入用作啟動畫面的圖片,更改ID為IDB_SPLASH。
(3)新建一個對話框,在其中添加啟動畫面。
在資源中新建一個對話框,創建對話框類CSplashDlg。在對話框中添加一個Picture控件,打開其“Properties”對話框,選General,在Type下拉列表中選擇Bitmap,在Image下拉列表中選前面導入的位圖資源ID值:IDB_SPLASH。
(4)修改對話框的顯示效果
?、僬{整對話框大小,去掉兩個自動生成的按鈕,并在“Properties”的“Styles”頁中去掉對Title bar的選取;
?、谶x中圖像,調整大小使之適應對話框的可編輯區,修改其“Properties”的“Styles”
使之居中。
(5)在CMainFrame類的OnCreate()函數中添加創建、顯示并銷毀無模式對話框的代碼。
#include “SplashDlg.h” //加到MainFrm.cpp文件的頭文件調用部位
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
CSplashDlg *dlg = new CSplashDlg(this);
dlg->Create(CSplashDlg::IDD,this); //創建對話框
dlg->ShowWindow(SW_SHOW); //顯示對話框
dlg->UpdateWindow();
Sleep(2000); //畫面顯示停留時間,單位為毫秒
…
dlg->DestroyWindow(); //銷毀對話框
return 0;
}
3.通過發送消息顯示和銷毀啟動畫面
?、僦貜头椒ǘ牟襟E1至步驟4。
?、谑褂肅lass Wizard為CMainFrame類添加消息響應函數WM_TIMER。
?、坌薷拇a,通過發送WM_TIMER消息啟動和銷毀啟動畫面
1)定義對話框類的變量
在MainFrm.h文件頭部添加#include "SplashDlg.h",并在CMainFram類的定義中加上公用變量CSplashDlg *Splash。
2)添加計時器消息相應函數代碼
void CMainFrame::OnTimer(UINT nIDEvent)
{
if(Splash->IsWindowVisible()){
Splash->SetActiveWindow(); //把啟動畫面設置為當前活動窗口
Splash->UpdateWindow();
Sleep(2000); //修改此處可更改畫面顯示時間
Splash->SendMessage(WM_CLOSE); //關閉對話框
}
else{
SetActiveWindow();
KillTimer(1) ; //清除WM_TIMER事件
消息映射、循環機制是Windows程序運行的基本方式。VC++ MFC 中有許多現成的消息句柄,可當我們需要完成其它的任務,需要自定義消息,就遇到了一些困難。在MFC ClassWizard中不允許添加用戶自定義消息,所以我們必須在程序中添加相應代碼,以便可以象處理其它消息一樣處理自定義消息。通常的做法是采取以下步驟:
第一步:定義消息。
推薦用戶自定義消息至少是WM_USER+100,因為很多新控件也要使用WM_USER消息。
#define WM_MY_MESSAGE (WM_USER+100)
第二步:實現消息處理函數。該函數使用WPRAM和LPARAM參數并返回LPESULT。
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
// TODO: 處理用戶自定義消息
...
return 0;
}
第三步:在類頭文件的AFX_MSG塊中說明消息處理函數:
class CMainFrame:public CMDIFrameWnd
{
...
// 一般消息映射函數
protected:
// {{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
}
第四步:在用戶類的消息塊中,使用ON_MESSAGE宏指令將消息映射到消息處理函數中。
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_WM_TIMER()
ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
如果用戶需要一個定義整個系統唯一的消息,可以調用SDK函數RegisterWindowMessage定義消息:
static UINT WM_MY_MESSAGE=RegisterWindowMessage("User");
并使用ON_REGISTERED_MESSAGE宏指令取代ON_MESSAGE宏指令,其余步驟同上。
當需要使用自定義消息時,可以在相應類中的函數中調用函數PostMessage或SendMessage發送消息PoseMessage(WM_MY_MESSAGE,O,O); 如果向其他進程發送消息可通過如下方法發送消息:
DWORD result;
SendMessageTimeout(wnd->m_hWnd, // 目標窗口
WM_MY_MESSAGE, // 消息
0, // WPARAM
0, // LPARAM
SMTO_ABORTIFHUNG |
SMTO_NORMAL,
TIMEOUT_INTERVAL,
&result);
以避免其它進程如果被阻塞而造成系統死等狀態。
可是如果需要向其它類(如主框架、子窗口、視類、對話框、狀態條、工具條或其他控件等)發送消息時,上述方法顯得無能為力,而在編程過程中往往需要獲取其它類中的某個識別信號,MFC框架給我們造成了種種限制,但是可以通過獲取某個類的指針而向這個類發送消息,而自定義消息的各種動作則在這個類中定義,這樣就可以自由自在的向其它類發送消息了。
下面舉的例子敘述了向視類和框架類發送消息的方法:
在主框架類中向視類發送消息:
視類中定義消息:
ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //定義消息映射
視類定義消息處理函數:
// 消息處理函數
LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
// TODO: 處理用戶自定義消息
...
return 0;
}
//發送消息的測試函數
void CMainFrame::OnTest()
{
CView * active = GetActiveView();//獲取當前視類指針
if(active != NULL)
active->PostMessage(WM_MY_MESSAGE,0,0);
}
在其它類中向視類發送消息:
//發送消息的測試函數
void CMainFrame::OnTest()
{
CMDIFrameWnd *pFrame;
CMDIChildWnd *pChild;
CView *pView;
//獲取主窗口指針
pFrame =(CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;
// 獲取子窗口指針
pChild = (CMDIChildWnd *) pFrame->GetActiveFrame();
//獲取視類指針
pView = pChild->GetActiveView();
if(pView != NULL)
pView->PostMessage(WM_MY_MESSAGE,0,0);//發送消息
}
其余步驟同上。
在視類中向主框架發送消息:
首先在主框架中定義相關的消息,方法同上,然后在發送消息的函數中添加代碼如下
//發送消息的測試函數
void CMessageView::OnTest()
{
CFrameWnd * active = GetActiveFrame();//獲取當前主窗口框架指針
if(active != this)
active->PostMessage(WM_MY_MESSAGE,0,0);
return 0;
}
在其它類中向不同的類發送消息可依次方法類推,這樣我們的程序就可以的不受限制向其它類和進程發送消息,而避免了種種意想不到的風險。
下面一個例子程序為多文檔程序里在一對話框中向視類發送消息,詳述了發送自定義消息的具體過程。
實現步驟:
第一步:在VC++中新建工程Message,所有ClassWizard步驟選項均為缺省,完成。
第二步:在主菜單中添加測試菜單為調出對話框,在框架類中建立相應函數OnTest()
第三步:在資源中建立對話框,通過ClassWizard添加新類TestDialog,添加測試按鈕,
在對話框類中建立相應函數OnDialogTest()
//通過對話框按鈕發送消息的函數
void TestDialog::OnDialogTest()
{
CMDIFrameWnd *pFrame;
CMDIChildWnd *pChild;
CView *pView;
//獲取主窗口指針
pFrame =(CMDIFrameWnd*)AfxGetApp()->m_pMainWnd;
// 獲取子窗口指針
pChild = (CMDIChildWnd *) pFrame->GetActiveFrame();
//獲取視類指針
pView = pChild->GetActiveView();
if(active != NULL)
active->PostMessage(WM_MY_MESSAGE,0,0);//發送消息
}
在Message.h頭文件中添加如下語句:
static UINT WM_MY_MESSAGE=RegisterWindowMessage("Message");
第四步:在視類中添加自定義消息:
在頭文件MessageView.h中添加消息映射
protected:
//{{AFX_MSG(CMessageView)
//}}AFX_MSG
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam); //此行為添加代碼
DECLARE_MESSAGE_MAP()
在視類文件MessageView.cpp中的消息映射中添加自定義消息映射
BEGIN_MESSAGE_MAP(CMessageView, CView)
//{{AFX_MSG_MAP(CMessageView)
//}}AFX_MSG_MAP
// Standard printing commands
ON_REGISTERED_MESSAGE(WM_MY_MESSAGE,OnMyMessage) //此行添加代碼定義唯一消息
END_MESSAGE_MAP()
添加相應的0消息處理函數
LRESULT CMessageView::OnMyMessage(WPARAM wParam, LPARAM lParam)
{
CRect rect;
GetClientRect(&rect);
InvalidateRect(&rect);
test=!test;
return 0;
}
在MessageView.h中添加布爾變量 public:BOOL test;
在視類構造函數中初始化 test變量:test=FALSE;
修改CMessageView::OnDraw()函數
void CMessageView::OnDraw(CDC* pDC)
{
CMessageDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// 以下程序顯示消息響應效果
if(test)
pDC->TextOut(0,0,"消息響應!");
}
第五步:顯示測試對話框
在MainFrame類中包含對話框頭文件:
#include "TestDialog.h";
OnTest()函數中添加代碼
void CMainFrame::OnTest()
{
TestDialog dialog;
dialog.DoModal();
}
運行程序,在測試菜單打開對話框,點擊測試按鈕即可看到結果。
Windows 應用程序所要做的每項工作幾乎都是基于消息處理的, Windows 系統消息分為常用 Windows 消息,控件通知消息和命令。然而,有時我們需要定義自己的消息來通知程序什么事情發生了,這就是用戶自定義消息。 ClassWizard 并沒有提供增加用戶自定義消息的功能,所以要使用用戶自定義消息,必須手工編寫代碼。然后 ClassWizard 才可以象處理其它消息一樣處理你自定義的消息。具體做法如下詳解:
第一步:定義消息。一個消息實際上是開發 Windows95 應用程序時, Microsoft 推薦用戶自定義消息至少是 WM_USER+100 ,因為很多新控件也要使用 WM_USER 消息。
第二步:實現消息處理函數。該函數使用 WPRAM 和 LPARAM 參數并返回 LPESULT 。
LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam){// TODO: 處理用戶自定義消息 AfxMessageBox(" 處理用戶自定義消息 "); return 0;}
第三步:在類頭文件的 AFX_MSG 塊中說明消息處理函數:
class CMainFrame:public CMDIFrameWnd{
...
// 一般消息映射函數
protected:
// {{AFX_MSG(CMainFrame)
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
//}}AFX_MSG
DECLARE_MESSAGE_MAP()}
第四步:在用戶類的消息塊中,使用 ON_MESSAGE 宏指令將消息映射到消息處理函數中。
BEGIN_MESSAGE_MAP(CMainFrame, CMDIFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_WM_TIMER()
ON_MESSAGE(WM_MY_MESSAGE, OnMyMessage)
//}}AFX_MSG_MAPEND_MESSAGE_MAP()
這樣,一個用戶自定義消息就可以使用了,如果用戶需要一個整個系統唯一的消息,可以調用 SDK 函數 RegisterWindowMessage 并使用 ON_REGISTER_MESSAGE 宏指令取代 ON_MESSAGE 宏指令,其余步驟同上。
VC++ 為程序員提供了一套功能強大、方便快捷的編程工具,它可以幫你方便的生成窗口、菜單等用戶界面,可惜就是做出來的東西都一樣,沒有一點個性。下面,就介紹一些方法,讓我們可以按照自己的設計定制出更加符合自己程序風格的窗口。
一、如何在多文檔界面下去掉開始的子窗口
在多文檔界面程序中,程序剛啟動的時候會自動打開一個新的子窗口,而一個實際的應用系統往往是由用戶操作后再生成新的窗口。下面是如何去掉開始的子窗口。
首先在應用程序的 App 類里找到
BOOL CMyMDIApp::InitInstance() 下面有:
// Parse command line for standard shell commands, DDE, file open
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
這是在處理命令行參數,在這幾句話后面加一行:
cmdInfo.m_nShellCommand=CCommandLineInfo::FileNothing; 就可以了。再運行程序,就會發現程序并沒有自動開啟一個子窗口,而只剩下主框架窗口了。
二、修改窗口標題欄
在缺省情況下,窗口標題欄中顯示的標題為程序名 + 當前文檔的文件名。比如 " MyProgram - 文檔 1 . t x t " ,那若要在標題欄顯示一個自己定義的字符串,而不是程序名,可以通過在程序里調用 CWnd::SetWindowText() 方法來實現,而如果我們還想要后面的文檔名自動顯示,這么做就不行了,這時可以用資源編輯器編輯字符串表( StringTable )資源,在 StringTable 中雙擊 IDR-MAIN-FRAME 項, caption 中顯示一字符串 xx\n\yy...... ,將第一個參數修改為用戶自己希望見到的主窗口標題即可。
如果你不想讓系統自動幫你把文檔的文件名添加到標題欄中,需要在 CMainFram 的 PreCreateWindow 函數中刪除 FWS_ADDTOTITLE 標志的窗口樣式:
cs.style &= ~FWS_ADDTOTITLE ;
這樣,程序運行起來,窗口標題就是 "MyProgram" 而沒有后面的 "- 文檔 1.txt" 這樣的字符串了。
三、修改主框架窗口、子窗口及其顯示風格
MFC 的 CWnd 類會在調用 CWnd::Create() 方法前先調用一下 PreCreateWindo() 方法,其參數是 CREATESTRUCT cs ,其中包括了創建窗口時各參數,例如大小,風格等等,我們可以通過重載這個成員函數來修改主窗口和子窗口的風格等屬性。 PreCreateWindow 函數的原型為: Virtual BOOL PreCreateWindow ( CREATESTRUCT cs )。重載 PreCreateWindow 函數以后,則在創建窗口前可以修改 CREATESTRUCT 結構以替換缺省參數。 CREATESTRUCT 結構存放窗口特征,如窗口坐標、風格等,還可以定義新窗口風格。
若想修改主框架窗口,則可以在 MainFrm.cpp 的下列成員函數中加入待修改的內容。例如:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT&cs)
{
// 通過修改 CREATESTRUCT 結構來修改窗口類或風格
cs.cx=450;
cs.cy=300;
// 定義新窗口的高度、寬度
// 定義新窗口風格為去掉主窗口名及最大化等按鈕
cs.style=ws-POPWINDO;
return CFrameWnd::PreCreateWindow(cs); }
定制子窗口的操作與上述主窗口相同,可在 ChildFrame.cpp 中加入以下內容:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT&cs)
{
// 通過修改 CREATESTRUCT 結構來修改窗口類或風格
return CMDIChildWnd::PreCreateWindow(cs);
}
要修改視圖窗口的顯示性質,則可在視圖文件 MyView.cpp 的下述成員函數中加入以下語句:
BOOL MyView::PreCreateWindow(CREATESTRUCT&cs)
{
// 在這里修改 cs 結構,改變 View 的風格。
cs.lpszClass=AfxRegisterWndClass(cs-HREDRAW|CS-VREDRAW,0,(HBRUSH))::GetStockObject(WHITE-BRUSH),0);
return CScrollView::PreCreateWindow(cs);
}
其中, cs 的參數 pszClass 用于存放 Windows 窗口類名稱。要想注冊 Windows 窗口類,則必須調用全局函數 AfxRegisterWndClass 。該函數原型為:
LPCTSTR AFXAPI AfxRegisterWndClass(UINTnClassStyle,HCURSOR hCursor=0,HBRUSH hbrBackground=0,HICON hIcon=0)
上述各參數用于定義風格,其含義分別為光標資源句柄、背景資源句柄、圖標資源句柄。上述增加的語句的作用是:改變窗口大小時重畫窗口、不顯示光標圖標、設置白色背景。
四、窗口的滾動
MFC 中的 CScrollView 可以幫助你自動實現窗口滾動的決大部分功能,使用 CscrollView 時, ClassWizard 生成 OnInitialUpdate() 成員函數為:
void CMyScrollView::OnInitialUpdat()
{
CScrollView::OnIntialUpdate();
CSize sizePage;
sizePage.cs=sizePage.cy=400;
SetScrollSizes(MM-TEXT,sizePage);
}
其中, cs 和 cy 分別為滾動窗口的水平、垂直分量,表明窗口的水平、垂直方向尺寸小于 400 像素單位時將出現水平方向滾動條和垂直方向滾動條。通過修改滾動尺寸,可改變出現滾動條的最小窗口。例如,若 sizePage.cx=600;sizePage.cy=800; ,則當窗口尺寸小于 600*800 時,就會出現滾動條。
五、窗口分割
該功能可將窗口分割成多個可滾動的幀,幀之間的邊界稱為分割條,可用分割條來調整每個幀的相對大小。要想增加窗口分割功能,則必須修改主窗口類。首先,在主窗口類的頭文件 MainFrm.h 中添加以下代碼:
CSplitterWnd m-SWnd;
Virtual BOOL OnCreateClient (LPCREATESTRUCTcs,CcreateContext *pContext);
再在 MainFrm.cpp 中添加成員函數 OnCreateClient 的定義:
BOOL CmainFrame::OnCreateCline(LPCREATESTRUCTcs,CcreateContext *p Context)
{
return m-SWnd.Creat(this,2,2,Csize(20,20),pContext);
}
新的 CSplitterWnd 類對象 m-SWnd 用于創建和管理分割窗口,該窗口中可以包含一個或多個幀。首次創建主窗口時,將調用成員函數 OnCreateClient 。在缺省情況下,該函數創建一個填充主框窗口客戶區的視圖窗口。覆蓋該函數后,將調用 CsplitterWnd 的成員函數 Create 來創建分割窗口。其中,第一個參數用于指定分割的父窗口(主窗口);第二個參數指定垂直方向上的幀個數為 2 ;第三個參數指定水平方向上的幀的個數;第四個參數用于設置每個幀的最小尺寸;第五個參數傳遞描述信息。上述分割窗口的每個幀都是由視圖類對象管理的,當用戶在某一幀內顯示文檔和圖形時,必須在其它幀中重新繪制,從而在多個幀中均顯示相同的內容。為此,必須調用顯示文檔類的 UpdateALLView 成員函數來更新其它幀。此時,只需加入 pdoc- > UpdataALLView(NULL) 即可。
vc中常用的幾個數據轉換方法-int char* float與CString 之間的轉換
1、int <->CString
1) int ->CString
int n = 1;
CString str;
str.Format("%d",n);
2) CString->int
CString str = "1";
int n = atoi(str.GetBuffer(0));
2. char* 與CString
1)char*->CString
char sz[128];
CString str;
str.Format("%s",sz);
2) CString -> char*
CString str;
int nLength = str.GetLength();
char* sz = new char[nLength];
sz = str.GetBuffer(0);
有人說這里有錯誤!會造成內存泄露;
char* sz = new char[nLength];
sz = str.GetBuffer(0);
應改為:
char* sz = str.GetBuffer(0);
3. float<->CString
1)float->CString
float f = 0.0;
CString str;
str.Format("%f",f);
2) CString->float
CString str = "0.0";
float f = atof(str.GetBuffer(0));
1.工具條和狀態條中控件的添加:
方法⑴.只能在ToolBar里創建控件:首先,在ToolBar中創建一個Button,其ID為ID_TOOL_COMBO(我們要將創建的控件放在該Button的位置上).
其次,新創建一個類CMainToolBar,要從CToolBar繼續(創建過程大概如下:選擇工程/增加到工程/新的類;也可以選擇工程的根,然后點擊右鍵,選擇新的類;或者CTL W,選擇增加類/新的類 --- 然后在class type里選擇Generic Class,在Name欄里輸入新類的名字,Base class里輸入CToolBar),創建成功后在該類里創建要增加的控件的對象,如:
CComboBox m_wndMyCombo;
CStatic m_wndCategory, m_wndCategoryPath;
CButton m_wndOpenButton;
Cedit m_wndEdit;
然后在構造函數里初始化如:
m_wndMyCombo.m_hWnd = NULL;
m_wndCategory.m_hWnd = NULL;
m_wndCategoryPath.m_hWnd = NULL;
m_wndOpenButton.m_hWnd = NULL;
m_wndEdit.m_hWnd = NULL;
接著在CMainframe的頭文件里創建CMainToolBar的一個對象m_wndToolBar,最后在.cpp文件的OnCreate函數的最后實現如下:
int index = 0;
CRect rect; // 可定義在頭文件當中
// ComboBox
{
//找到指定的工具項
while(m_wndToolBar.GetItemID(index)!=ID_TOOL_COMBO)
index ;
//設置指定工具項的寬度并獲取新的區域 120是寬度
m_wndToolBar.SetButtonInfo(index, ID_TOOL_COMBO, TBBS_SEPARATOR, 120);
m_wndToolBar.GetItemRect(index, &rect);
//設置位置
rect.top =1;
rect.bottom = 200;
// 創建并顯示控件
if(!m_wndToolBar.m_wndMyCombo.Create(WS_CHILD|WS_VISIBLE| CBS_AUTOHSCROLL|
CBS_DROPDOWNLIST | CBS_HASSTRINGS , rect, &m_wndToolBar, ID_TOOL_COMBO))
{
TRACE0("Failed to create combo-box\n");
return FALSE;
}
m_wndToolBar.m_wndMyCombo.ShowWindow(SW_SHOW);
//填充內容
m_wndToolBar.m_wndMyCombo.AddString("25%");
m_wndToolBar.m_wndMyCombo.AddString("50%");
m_wndToolBar.m_wndMyCombo.AddString("75%");
//選擇默認項
m_wndToolBar.m_wndMyCombo.SetCurSel(0);
//獲取到內容并MSGBOX顯示出來
CString strContent;
m_wndToolBar.m_wndMyCombo.GetWindowText(strContent);
index = 0;
}
其他控件都類似創建(只需要注重一下各自的Create函數的參數即可)。
方法⑵.這種方法創建不太輕易控制:直接在CMainframe的頭文件中創建要增加的控件的對象,如CButton 的對象m_wndAboutButton,然后創建CToolBar或者CstatusBar的對象,如:CstatusBar的對象_wndStatusBar;再增加幾個函數如下:
Protected:
virtual void RecalcLayout(BOOL bNotify = TRUE);
afx_msg void CMainFrame::OnViewStatusBar();
接著在.cpp文件中將StatusBar的ID和OnViewStatusBar 函數綁定在一起,如下所示:BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
// {{AFX_MSG_MAP(CMainFrame)
ON_COMMAND(ID_VIEW_STATUS_BAR, OnViewStatusBar)
ON_WM_CREATE()
// }}AFX_MSG_MAP
END_MESSAGE_MAP()
然后Create函數的最后(返回值之前)實現如下代碼:
CRect rc;
VERIFY(m_wndAboutButton.Create(_T("MyAbout"),
WS_VISIBLE,rc,this,ID_APP_ABOUT));
// TODO: Remove this if you don't want tool tips or a resizeable toolbar
m_wndToolBar.SetBarStyle(m_wndToolBar.GetBarStyle() |
CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC);
再在RecalcLayout函數里實現:
CRect rc;
if (m_wndStatusBar.m_hWnd)
{
m_wndStatusBar.GetWindowRect(&rc);
ScreenToClient(&rc);
rc.right -= 50;
m_wndStatusBar.SetWindowPos(NULL,rc.left,rc.top,rc.Width(),rc.Height(),
SWP_NOZORDER);
rc.left = rc.right;
rc.right = 50;
m_wndStatusBar.SetWindowPos(NULL,rc.left,rc.top,rc.Width(),rc.Height(),
SWP_NOZORDER);
}
最后在OnViewStatusBar()里實現:
BOOL bShow = m_wndStatusBar.GetStyle() & WS_VISIBLE;
m_wndAboutButton.SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOZORDER|SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE|
(bShow ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
ToolBar中的創建與此相同,只需更改一下句柄即可
2.使工具條上的按鈕點擊一次為按下,再點擊才彈起
bCheck=m_RtfEditToolBar.GetToolBarCtrl().IsButtonChecked(ID_TB_BOLD);
m_RtfEditToolBar.GetToolBarCtrl().CheckButton(ID_TB_BOLD, !bCheck);
3.如何隱藏工具欄
添加如下兩個函數
隱藏:
void CMainFrame::OnHide()
{
if(m_wndToolBar.IsWindowVisible())
m_wndToolBar.ModifyStyle(WS_VISIBLE,0);
SendMessage(WM_SIZE);
}
顯示:
void CMainFrame::OnShow()
{
if(!m_wndToolBar.IsWindowVisible())
m_wndToolBar.ModifyStyle(0,WS_VISIBLE);
SendMessage(WM_SIZE);
}
4.如何動態獲取工具條指針并給工具條加標題?
[問題提出]
工具條也是窗口,是窗口就有標題,如何給工具條加標題?
[程序實現]
不想動態改變工具條的標題就在CMainFrame::OnCreate()中:
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
......
m_wndToolBar.SetWindowText(_T("Standdard"));
return 0;
}
若想動態改變工具條的標題,如下:
聲明一個菜單,并響應事件,如響應:OnMyToolBar()函數
void CMainFrame::OnMyToolBar()
{
// TODO: Add your command handler code here
CToolBar *pToolBar = (CToolBar *)AfxGetMainWnd()->GetDescendantWindow(AFX_IDW_TOOLBAR);
pToolBar->SetWindowText (_T("Standdard"));
}
不要在TooBar懸浮時做OnMyToolBar()會出錯的.
順便提一下如何獲得狀態條的指針:
CStatusBar * pStatusBar =(CStatusBar *)AfxGetMainWnd()->GetDescendantWindow(AFX_IDW_STATUS_BAR);
5.在狀態條中顯示鼠標的設備坐標與邏輯坐標
顯示器的設備坐標系的原點在客戶區的左上角,x軸向右增長,y軸向下增長。我們要設置的邏輯坐標系的原點則在客戶區的中心,x軸向右增長,y軸向上增長,如一個笛卡爾坐標系一般。
為CChildView添加一個成員函數void OnPrepareDC(CDC * pDC, CPrintInfo * pInfo = NULL);
void OnPrepareDC(CDC * pDC, CPrintInfo * pInfo){
CRect rect;
// 設置映射模式為LOMETRIC (0.1mm),右上為增長方向
pDC->SetMapMode (MM_LOMETRIC);
// 將坐標原點定在客戶區的中心
GetClientRect(rect);
pDC->SetViewportOrg(rect.Width()/2, rect.Height()/2);
}
為CChildView響應鼠標移動消息,并在狀態條中顯示鼠標的坐標值。m_ptMouse數據成員是原打算做十字交叉線用的,在此使用沒有實際意義。
void CChildView::OnMouseMove(UINT nFlags, CPoint point){
CClientDC dc(this);
CString str;
OnPrepareDC(&dc);
//要訪問類CMainFrame,需要將mainfrm.h文件引入
CMainFrame * pFrame = (CMainFrame *) AfxGetApp()->m_pMainWnd;
//要訪問CMainFrame的數據成員m_wndStatusBar,需要手工修改mainfrm.h,public這個數據成員
CStatusBar * pStatus = (CStatusBar *) &pFrame->m_wndStatusBar;
m_ptMouse = point;
str.Format ("設備坐標 X=%i pixel, Y=%i pixel", m_ptMouse.x, m_ptMouse.y);
pStatus->SetPaneText(1, str);
dc.DPtoLP(&m_ptMouse);
str.Format ("邏輯坐標 X=%i * 0.1mm, Y=%i * 0.1mm", m_ptMouse.x, m_ptMouse.y);
pStatus->SetPaneText(2, str);
}
6.如何更新狀態條上的現實內容
By default, a CStatusBar pane is not enabled when the pane is created. To activate a pane, you must call the ON_UPDATE_COMMAND_UI() macro for each pane on the status bar and update the panes. Because panes do not send WM_COMMAND messages, you cannot use ClassWizard to activate panes; you must type the code manually. For example, suppose one pane has ID_INDICATOR_PAGE as its identifier and that it contains the current page number in a document. To make the ID_INDICATOR_PAGE pane display text, add the following to a header file (probably the MAINFRM.H file):
afx_msg void OnUpdatePage(CCmdUI *pCmdUI);
Add the following to the application message map:
ON_UPDATE_COMMAND_UI(ID_INDICATOR_PAGE, OnUpdatePage)
Add the following to a source code file (probably MAINFRM.CPP):
void CMainFrame::OnUpdatePage(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
}
To display text in the panes, either call SetPaneText() or call CCmdUI::SetText() in the OnUpdate() function. For example, you might want to set up an integer variable m_nPage that contains the current page number. Then, the OnUpdatePage() function might read as follows:
void CMainFrame::OnUpdatePage(CCmdUI *pCmdUI)
{
pCmdUI->Enable();
char szPage[16];
wsprintf((LPSTR)szPage, "Page %d", m_nPage);
pCmdUI->SetText((LPSTR)szPage);
}
This technique causes the page number to appear in the pane during idle processing in the same manner that the application updates other indicators.
1.窗口最大最小化按紐的控制
①怎樣在程序開始的時候讓它最大化?
②vC 做出來的exe文件在窗體的右上方是沒有最大化和最小化按鈕的,怎樣實現這一功能?
③如何在顯示窗口時,使最大化按鈕變灰?
①在App類里的C…App::InitInstance()中把m_pMainWnd->ShowWindow(SW_SHOW)改成m_pMainWnd->ShowWindow(SW_MAXIMIZE);
②在CreateWidnow時用WS_SYSMENU|WS_MINIMIZEBOX|WS_MAXIMIZEBOX 風格.
③ 第一種方法:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
// disable the maxmini box
cs.style &= ~WS_MAXIMIZEBOX;
return TRUE;
}
第二種方法:
CMenu *pMenu=AfxGetApp()->m_pMainWnd->GetSystemMenu(FALSE);
int x=pMenu->GetMenuItemCount( );
UINT pID=pMenu->GetMenuItemID(x-1);
pMenu->EnableMenuItem(pID, MF_DISABLED);
第三種方法:
ModifyStyle(WS_MAXIMIZEBOX, 0);
這個函數也可以是最大化按鈕失效!
并且可以在程序中動態的改變窗口的風格
2.創建動態菜單
void CMainFrame::OnSelectState(NMTOOLBAR* pnmtb, LRESULT *plr)
{
CMenu menu;
if(!menu.CreateMenu())
return;
menu.AppendMenu(MF_STRING,0,"開始");
menu.AppendMenu(MF_STRING,0,"結束");
CRect rc;
m_wndToolBar.SendMessage(TB_GETRECT, pnmtb->iItem, (LPARAM)&rc);
m_wndToolBar.ClientToScreen(&rc);
menu.TrackMenu( TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_VERTICAL,
rc.left, rc.bottom, this, &rc);
// menu.DestroyMenu();
menu.detach();
}
3.如何禁止對話框關閉按鈕和浮動工具條上的系統菜單
1、禁止對話框中的關閉按鈕有二種方法。
第一種方法,用ModiftMenu()涵數來實現:
CMenu* pMenu = this->GetSystemMenu(FALSE);
pMenu->ModifyMenu(SC_CLOSE,MF_BYCOMMAND | MF_GRAYED );
第二種方法,用EnableMenuItem()涵數來實現:
CMenu* pMenu = this->GetSystemMenu(FALSE);
pMenu->EnableMenuItem( SC_CLOSE, MF_BYCOMMAND|MF_GRAYED);
2、禁止浮動工具條上的系統菜單。
新建一個CToolBar的派生類CxxToolBar,在新類中的左鍵雙擊(CxxToolBar::OnLButtonDblClk(...))
和左鍵單擊(CxxToolBar:: OnLButtonDown(...))涵數中分別加入下面代碼既可:
if (IsFloating()) //工具條正在浮動狀態中
{
CWnd* pMiniFrame;
CWnd* pDockBar;
pDockBar = GetParent();
pMiniFrame = pDockBar->GetParent();
//去除其上系統菜單
pMiniFrame->ModifyStyle(WS_SYSMENU, NULL);
//重繪工具條
pMiniFrame->ShowWindow(SW_HIDE);
pMiniFrame->ShowWindow(SW_SHOW);
}
3、禁止窗口最大化按鈕
在PreCreateWindow()涵數中去掉WS_MAXIMIZEBOX風格顯示既可。
BOOL CxxFrameWnd::PreCreateWindow(CREATESTRUCT& cs)
{
cs.style &= ~WS_MAXIMIZEBOX;
return CFrameWnd::PreCreateWindow(cs);
}
4.如何將標題欄上的右鍵菜單屏蔽掉?
[解決方法]
右鍵菜單是系統菜單,只要將其WS_SYSMENU的屬性去掉即可.
[程序實現]
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
........
long style = GetWindowLong(m_hWnd, GWL_STYLE);
style &= ~WS_SYSMENU;
SetWindowLong(m_hWnd, GWL_STYLE, style);
return 0;
}
5.修改標題欄高度
NONCLIENTMETRICS nm
調用SystemParametersInfo(SPI_GETNONCLIENTMETRICS,sizeof(nm),&nm,0)
重設SystemParametersInfo(SPI_SETNONCLIENTMETRICS,sizeof(nm),&nm,0)
6.窗口最大化、最小化及關閉的消息是什么?如何截獲?
最大化、最小化將發送WM_SYSCOMMAND消息。要處理該消息,可以這么做:
1、在Form的頭文件中添加:
void __fastcall RestrictMinimizeMaximize(TMessage &Msg);
BEGIN_MESSAGE_MAP
MESSAGE_HANDLER(WM_SYSCOMMAND, TMessage, RestrictMinimizeMaximize)
END_MESSAGE_MAP(TForm)
2、在Form的單元文件中添加:
void __fastcall TForm1::RestrictMinimizeMaximize(TMessage& Msg)
{
if (Msg.WParam == SC_MINIMIZE)
{
//catches minimize...
}
else if (Msg.WParam == SC_MAXIMIZE)
{
//catches maximize...
}
TForm::Dispatch(&Msg);
// or "else TForm::Dispatch(&Msg)" to trap
}
關閉窗口的消息為WM_CLOSE,C Builder提供了OnClose事件。
7.如何改變窗口標題?
[問題提出]
在應用程序的不同運行時期,要反映當前狀態往往會修改應用程序標題.
[解決方法]
在MFC類庫中提供了CWnd::SetWindowText函數,通過該函數可以改變任何窗體(包括控件)的標題.
改變主窗體的標題:
CWnd *m_pMainWnd;
m_pMainWnd=AfxGetMainWnd();
m_pMainWnd->SetWindowText(_T("改變標題"));
當改變多視MDI的子窗口的標題時,用:
GetParentFrame()->SetWindowText(_T("MDI Child改變標題"));
當改變按鈕的標題時(假設按鈕的ID=IDC_BUTTON1):
GetDlgItem(IDC_BUTTON1)->SetWindowText(_T("Button 改變標題"));
運行看看.
8.如何用VC 動態修改應用程序菜單
[問題提出]
本文將介紹一些使用CMenu的方法,如查找指定菜單,在指定選項前添加菜單項.....
[解決方法]
使用CWnd::GetMenu( )訪問主菜單,GetMenu( )返回指向CMenu對象的指針,它有一些成員函數,答應我們修改一個菜單。
1) 如何實現找到一個菜單項:
步驟如下:
{
//動態修改菜單:
// Get the Main Menu
CMenu* pMainMenu = AfxGetMainWnd()->GetMenu();
CMenu* pSubMenu = NULL;
int i;
for (i=0; i<(int)pMainMenu->GetMenuItemCount(); i )
{
pSubMenu = pMainMenu->GetSubMenu(i);
if (pSubMenu && pSubMenu->GetMenuItemID(0) == ID_FILE_NEW)
break;
}
CString s;
s.Format("%d",i);//菜單項的位數.
AfxMessageBox(s);
ASSERT(pSubMenu);
}
2) 動態編輯菜單:
步驟如下(可以用上例的pSubMenu,要加的菜單你自己定義.):
1) 添加一個稱為Wzd2,命令ID為IDC_NAME_NEW1的菜單命令到該菜單中,可以用:
pSubMenu->AppendMenu(0,IDC_NAME_NEW1,"New&1");
2) 在New1前插入New2,可以用:
pSubMenu->InsertMenu(IDC_NAME_NEW1,MF_BYCOMMAND,IDC_NAME_NEW2, "New&2");
3) 把New1改變成New3,可以用:
pSubMenu->ModifyMenu(IDC_NAME_NEW1,MF_BYCOMMAND,IDC_NAME_NEW3, "New&3");
4) 刪除該菜單中第二項,可以用:
pSubMenu->RemoveMenu(1,MF_BYPOSITION);
9.屏蔽掉子框架的右上角的關閉按鈕
int CChildFrame::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CMDIChildWnd::OnCreate(lpCreateStruct) == -1)
return -1;
。。。
CMenu* pSysMenu = GetSystemMenu(FALSE);
pSysMenu->EnableMenuItem(SC_CLOSE,MF_BYCOMMAND |MF_DISABLED|MF_GRAYED);
return 0;
}
10.隱藏標題欄和菜單欄
隱藏標題欄 ModifyStyle(WS_CAPTION,0)
隱藏菜單欄 SetMenu(NULL)
11.動態增加或刪除菜單
1、 增加菜單
添加
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜單
(mainmenu->GetSubMenu (0))->AppendMenu (MF_SEPARATOR);//添加分隔符
(mainmenu->GetSubMenu (0))->AppendMenu(MF_STRING,ID_APP_ABOUT,
_T("Always on &Top")); //添加新的菜單項
DrawMenuBar(); //重畫菜單
2、 刪除菜單
刪除
CMenu *mainmenu;
mainmenu=AfxGetMainWnd()->GetMenu(); //得到主菜單
CString str ;
for(int i=(mainmenu->GetSubMenu (0))->GetMenuItemCount()-1;i>=0;i--) //取得菜單的項數。
{
(mainmenu->GetSubMenu (0))->GetMenuString(i,str,MF_BYPOSITION);
//將指定菜單項的標簽拷貝到指定的緩沖區。MF_BYPOSITION的解釋見上。
if(str=="Always on &Top") //假如是剛才我們增加的菜單項,則刪除。
{
(mainmenu->GetSubMenu (0))->DeleteMenu(i,MF_BYPOSITION);
break;
}
}
12.另一種改變窗口標題的方法
使用語句 CWnd* m_pCWnd = AfxGetMainWnd( ),然后,再以如下形式調用SetWindowText()函數:
SetWindowText( *m_pCWnd,(LPCTSTR)m_WindowText);// m_WindowText可以是一個CString類的變量。
13.上下文菜單事件觸發事件
OnContextMenu事件
14.顯示和隱藏程序菜單
CWnd *pWnd=AfxGetMainWnd();
if(b_m) //隱藏菜單
{
pWnd->SetMenu(NULL);
pWnd->DrawMenuBar();
b_m=false;
}
else
{
CMenu menu;
menu.LoadMenu(IDR_MAINFRAME); ////顯示菜單 也可改變菜單項
pWnd->SetMenu(&menu);
pWnd->DrawMenuBar();
b_m=true;
menu.Detach();
}
1.列表框中標題欄(Column)的添加
創建一個List Control,其ID為IDC_LIST,在其Styles屬性項下的View項里選擇Report、Align項里選擇Top、Sort項里選擇None.
然后在該List所在對話框的類(頭文件)里創建ClistCtrl的一個對象m_list然后在.cpp文件的OnInitDialog()之類的函數里實現如下代碼:
CString strname[3];
strname[0]="Screen Name";
strname[1]="Form ID";
strname[2]="Category Path";
for(int i=0;i<3;i )
{
m_List.InsertColumn(i,strname[i],LVCFMT_LEFT,130);
}
在這之前也要將List Control的ID與ClistCtrl的對象m_list在DoDataExchange(CDataExchange* pDX)函數里綁定,如下:
DDX_Control(pDX, IDC_LIST, m_List);
2.如何防止在列表框中添加很多數據出現不停的刷新?
[問題提出]
在listbox添加很多數據的時候,由于控件不停的刷新,導致出現閃爍,如何解決?
[解決方法]
再添加數據以前,禁止控件刷新,數據添加完畢以后,再刷新一次。
[程序實現](其中:m_ListBox是CListBox的控件類型的變量)
m_ListBox.LockWindowUpdate();//禁止本listbox刷新。
for(int i=0;i<9999;i )
{
m_ListBox.AddString("test");
}//添加數據。
this->RedrawWindow(NULL,NULL,RDW_INVALIDATE | RDW_UPDATENOW | RDW_ERASE);
3.列表框中選擇變化時如何獲得通知?
我在Report View中使用了一個CListCtrl(自繪制類型),我想知道什么時候選擇項發生了改變.
在選擇項變化時,可以使用按鈕有效或失效,按如下操作:
加入LVN_ITEMCHANGED消息處理.
void CYourClassNameHere::OnItemchangedEventList(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
*pResult = 0;
if (pNMListView->uChanged == LVIF_STATE)
{
if (pNMListView->uNewState)
{
GetDlgItem(IDC_DELETE)->EnableWindow(TRUE);
}
else
{
GetDlgItem(IDC_DELETE)->EnableWindow(FALSE);
}
}
}
4.列表框控件中整欄選擇?
我在處理List控件時碰到了麻煩,我想創建一個ListView,來依據Tree控件的選擇同時在ListView和ReportView中顯示列表的信息.以下是相關的代碼:
// Set full line select
ListView_SetExtendedListViewStyle(m_plstCustomers->GetSafeHwnd(),
LVS_EX_FULLROWSELECT);
按如下方法處理:
// -------------------- begin of snippet --------------------------------
bool CCommCtrlUtil32::ListCtrl_ModifyExtendedStyle(CListCtrl& p_rListCtrl,
const DWORD p_dwStyleEx,
const bool p_bAdd)
{
HWND t_hWnd = p_rListCtrl.GetSafeHwnd();
DWORD t_dwStyleEx = ListView_GetExtendedListViewStyle(t_hWnd);
if(p_bAdd)
{
if(0 == (p_dwStyleEx & t_dwStyleEx))
{
// add style
t_dwStyleEx |= p_dwStyleEx;
}
}
else
{
if(0 != (p_dwStyleEx & t_dwStyleEx))
{
// remove style
t_dwStyleEx &= ~p_dwStyleEx;
}
}
ListView_SetExtendedListViewStyle(t_hWnd, t_dwStyleEx);
return true;
}
5.如何雙擊列表框項啟動一個與文件關聯的程序?
有人問我如何雙擊列表框項啟動一個程序?其實這個問題很簡單,Windows中有一個API函數可以打開任何類型的文件:
ShellExecute(NULL,"open",lpFileName,NULL,NULL,SW_SHOWNORMAL);
參數 lpFileName 是文件的全路徑名。用這個變量你可以傳遞象“C:\\MyExcelFile.xls”或者“http://www.vckbase.com”啟動Excel程序或者瀏覽器程序。假如你只是想獲取與文件關聯的程序名,而不是要運行程序,那么調用::FindExecutable就可以了。
6.如何得到列表框中所選擇項的String?
[問題提出]
如何得到CListBox所選擇項的String
[解決方法]
用到:CListBox::GetText()
[程序實現]
CString scInfo;
pList->GetText( GetCurSel(),scInfo);
7.鎖定ListView的欄目頭寬度
編譯:NorthTibet
世界之大,真是無其不有。Windows 應用程序的GUI標準明確規定了 ListView 欄目頭(Column Header)的寬度必須是可調整的,這本來是專門為用戶考慮而設計的控制特性,可是偏偏就有用戶拒絕這樣的特性。作為技術人員,用戶的需求是很難拒絕的。盡管這明顯是一種“非典型性需求”。本文將通過一個實例來示范如何實現 ListView Column Header 寬度的鎖定。
ListView 及其 Column Header 實際上都是 Windows 通用控件(Comctl32.dll) 的一部分。所以查一查 MSDN 中與“Header Control”相關的控件資料不難發現,欄目頭的鎖定與否與幾個 Windows 的通知消息密切相關,這幾個消息分別是 HDN_TRACK、HDN_BEGINTRACK 和 HDN_ENDTRACKA。其中 HDN_BEGINTRACK 是本文要非凡關照的一個。當用戶在欄目頭上拖拽鼠標時,假如位置正好在改變寬度的分割條上,則欄目頭控件會向其父窗口發送一個 HDN_BEGINTRACK 通知消息。為了實現欄目頭寬度的鎖定,就必須搞掂這個通知消息。不能將它傳遞到父窗口,但是,這個消息與 Windows 中形形色色的其它通知消息一樣,有兩個版本:一個版本是 HDN_BEGINTRACKW,專門用于寬字符和 Unicode 字符集;另一個版本是 HDN_BEGINTRACKA,專門用于 ANSI 字符集。這兩個版本的使用方法可以從公共控件的頭文件 commctrl.h 中獲?。?/span>
// From commctrl.h
#ifdef UNICODE
#define HDN_BEGINTRACK HDN_BEGINTRACKW
#else
#define HDN_BEGINTRACK HDN_BEGINTRACKA
#endif
所以在實現對消息的 HDN_BEGINTRACK 處理時,實際上是根據 UNICODE 的取值實現對 HDN_BEGINTRACKA 或 HDN_BEGINTRACKW 的處理。那么 Header Control 到底是發送的哪一個消息呢?在這里必須明白:Header Control 是 Windows 通用控件的一部分,它的實現都在 comctl32.dll 動態鏈接庫中。由于這個 DLL 已經被編譯成可執行代碼,因此在工程中修改 UNICODE 的設置將無濟于事。如何知道欄目頭控件發送哪一個版本的通知消息呢?是 A 版本還是 W 版本?
為了找到答案,我們必須求助一個經常被遺忘的消息 WM_NOTIFYFORMAT。一般控件第一次被創建時,都要向父窗口一個消息詢問父窗口需要哪個版本的通知消息。然后父窗口返回 NFR_ANSI 或 NFR_UNICODE。假如父窗口不處理 WM_NOTIFYFORMAT,那么這個消息將根據父窗口或對話框本身的首選項被傳遞到 Windows 的 DefWindowProc 消息處理例程進行默認處理。默認為 UNICODE。因此,要知道通知消息的版本,必須處理 ListCtrl 的 WM_NOTIFYFORMAT。為了確認父窗口的返回值,你可以做一個試驗便明白了。
假如你不想處理 WM_NOTIFYFORMAT 消息,那么完全可以通過雙雙實現 HDN_BEGINTRACKA 和 HDN_BEGINTRACKW 通知消息的處理來簡化問題的解決方案,同時這種方法也更可靠和通用。此時代碼將同時支持 ANSI 和 Unicode。本文附帶的例子程序示范了這種方法的實現。如圖一所示:

圖一 鎖定欄目頭寬度
實現代碼很簡單,Header 控件發送 HDN_XXX 到父窗口(ListCtrl),在 MFC 中可以利用消息反射來處理 Header 控件的通知消息。因為“可鎖定欄目頭”特性本身更趨向于 Header 控件的屬性,而不是 ListCtrl 的屬性。假如你不用 MFC ,那么就得處理 ListCtrl 中的通知消息。例子程序使用了消息反射機制,在 Header 控件的消息映射使用 ON_NOTIFY_REFLECT,也就是該寫虛擬成員函數 OnChildNotify:
BOOL CLockableHeader::OnChildNotify(UINT msg, WPARAM wp, LPARAM lp, LRESULT* pRes)
{
NMHDR& nmh = *(NMHDR*)lp;
if (nmh.code==HDN_BEGINTRACKW || nmg.code==HDN_BEGINTRACKA)
return *pRes=TRUE;
......
}
因為 OnChildNotify 是虛函數,所以沒有必要具備消息映射入口。只要實現此函數即可。在任何應用中,Header 發送的消息非此即彼,不會兩者都發送。不管怎樣,所發送的通知消息在到達父窗口之前都會被吃掉。也就是說,消息處理總是返回 TRUE,是否鎖定欄目頭的寬度通過一個標志來控制:應用程序通過 Lock 來修改標志的值。
假如鎖定了頭寬度,那么同時也必須禁用改變寬度的光標,這樣用戶界面才會有一致性,要實現這一點也很簡單:
BOOL CLockableHeader::OnSetCursor( CWnd* pWnd, UINT nHit, UINT msg)
{
return m_bLocked ? TRUE : CHeaderCtrl::OnSetCursor(pWnd, nHit, msg);
}
假如欄目頭被鎖定,則 OnSetCursor 返回 TRUE,此時光標不會被重新設置,否則由 Header 控件的進行默認處理。鎖定寬度后,當鼠標移到欄目頭上時,Windows 顯示標準的箭頭光標,而不是帶左右箭頭光標。
從 CHeaderCtrl 派生類出來的類的使用方法與處理對話框控制一樣,通過在父窗口的 OnCreate 的處理例程中進行子類化。實現細節請參考例子源代碼:
// CMyView is derived from CListView
int CMyView::OnCreate(LPCREATESTRUCT lpcs)
{
VERIFY(CListView::OnCreate(lpcs)==0);
return m_header.SubclassDlgItem(0,this) ? 0 : -1;
}
由于 Header 控制的資源 ID = 0,所以上面的代碼是行得通的。為了有一個友好的用戶界面,例子程序創建了一個命令菜單和界面更新處理例程。如圖一所示。