問題:
我在VC6.0中建立了一個SDI工程,工程中將主框架窗口切分為兩個視圖窗口。如何防止用戶移動主窗口以及調整切分視圖的大小?
有時候總感覺對不起用戶,原因是編程人員總是出於自己的目的,限制用戶進行這樣或那樣正常的圖形界面操作。如果某個程序限制我移動窗口、調整窗口大小或限制使用剪切、粘貼等功能的話,我的第一感覺就是將這個程序丟進垃圾箱。
但我是誰,憑什麼對此妄加評論呢?也許在某些情況下限制窗口移動和調整窗口大小的操作是明智的呢。也許你在為總統編寫一個核武器控制程序呢。不管怎樣總是有這樣的需求。限制調整窗口大小的最簡單的方法是創建窗口時不要用WS_THICKFRAME。如果一個窗口沒有了框架,那調整大小的途徑就堵住了,但關閉WS_THICKFRAME後,窗口還有一個標題框,用它鼠標點住它還可以移動窗口。為此你還必須屏蔽掉WS_CAPTION,這樣一來,你的應用程序連標題框都沒了。你當然要讓總統知道他正在使用的是核武器控制程序,而不是太空戰游戲。如何避免這種進退兩難的局面呢?如何讓應用程序有標題框而又不能移動窗口呢?並且你還不想使用瘦框架,而使用肥框架又不能調整串口大小該如何實現?
實際上,蛋糕會有的,而且你還能吃到蛋糕。你既可以讓窗口成為粗框架並帶有標題框,同時又可以限制窗口的移動和調整窗口的大小。訣竅就是處理WM_NCHITTEST消息。當鼠標位於非客戶區時,Windows會發送這個暧昧的消息來檢查鼠標特定的位置。所謂非客戶區:就是......,這是菜鳥級的問題啦,我在這就不羅嗦了,非客戶區所包括的范圍有:菜單、標題況和窗口邊界。通常你根本不必關心非客戶區的存在以及它包含一些什麼區域,但有時候,比如現在你最好還是卷起袖子認真研究一下它吧。
當用戶在標題框中點擊鼠標時,Windows發送WM_NCHITTEST消息。默認的窗口過程檢查鼠標坐標並返回一個特定的鼠標點擊測試代碼(HIT-TEST CODE)。例如,如果鼠標是在標題框中,默認的窗口過程返回HTCAPTION。如果鼠標是在左邊界或右邊界,默認的窗口過程分別返回HTLEFT或HTRIGHT。根據這些返回的代碼值。Windows進行窗口移動或調整窗口大小的操作。
通過重載WM_NCHITTEST消息處理函數限制窗口的移動和調整窗口大小,返回值是由其它的值代替HTCAPTION、HTLEFT或HTRIGHT,那到底返回值是什麼呢?首先可能想到返回HTNOWHERE,但這樣的話會有一個問題:如果另一個窗口覆蓋在你的窗口上,這時單擊你的應用窗口的標題框的話,什麼事也不會發生。我的意思是Windows不會激活你的應用程序窗口。那返回HTTRANSPARENT如何呢?也不行。HTTRANSPARENT和HTNOWHERE都使得現存的窗口框架狀態不明確。
正確的返回值應該是HTBORDER,當用戶單擊粗框架窗口(可調整窗口大小)的邊界時,默認的窗口過程返回相同的值。如果返回HTBORDER,Windows激活應用程序窗口時就不會初始化任何移動窗口或調整窗口大小的操作。
本文附帶了一個示例工程NoSize,這個程序示范了如何實現以上討論的內容,所有的工作都在CMainFrame類中完成。其中關鍵的函數是CMainFrame ::OnNcHitTest。它將所有不接受的點擊測試代碼映射到HTBORDER。
UINT CMainFrame::OnNcHitTest(CPoint point)
{
// 獲得鼠標位置代碼。
UINT hit = CFrameWnd::OnNcHitTest(point);
// 不接受以下的代碼值,將它們映射到HTBORDER
static char DisallowCodes[] = {
HTLEFT,HTRIGHT,HTTOP,...,
HTSIZE,HTCAPTION };
return strchr(DisallowCodes, hit)) ? HTBORDER: hit;
}
在實際的范例程序中還包含TRACE診斷代碼,這些代碼有助於你研究程序執行的過程細節。利用上面的代碼行。用戶便不能移動窗口以及調整窗口大小了。
但事情真是這樣嗎?你知不知道Windows應用程序中有多少種方法可以移動窗口和調整窗口大小?如標題框中的最大/最小按鈕以及系統菜單中的“移動”、“大小”等命令(見圖三),不要跟我說你搞忘了。
圖三
所幸的是搞定它們很容易。屏蔽掉標題框中的最大/最小按鈕,方法是在PreCreateWindow函數中調整窗口風格:
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
// 在標題框中去掉最大化/最小化按鈕
cs.style &= ~(WS_MINIMIZEBOX|WS_MAXIMIZEBOX);
return CFrameWnd::PreCreateWindow(cs);
}
要從系統菜單中去掉那幾個不想要的命令,必須在CMainFrame ::OnCreate函數中加上以下代碼:
static UINT BadCommands[] = {
SC_SIZE, SC_MOVE,
SC_MINIMIZE, SC_MAXIMIZE, SC_RESTORE, 0
};
CMenu *pSysMenu = GetSystemMenu(FALSE);
for (int i=0; BadCommands[i]; i++) {
pSysMenu->RemoveMenu(BadCommands[i], MF_BYCOMMAND);
}
現在系統菜單變成了如下圖四的樣子,
圖四
你可能認為這下萬事大吉了,但是還有一個漏洞,想一想還有哪裡可以讓用戶移動窗口或調整窗口的大小?記住Windows中是有很多貓膩的!。
用MFC建立的應用程序一般都會有狀態條,這個狀態條最右邊的下角有一個三角形的尺寸手柄,
圖五
用這個小玩意兒可以調整窗口的大小。
如何搞掂它呢?用下列代碼即可:
// 在 CMainFrame::OnCreate 中
ModifyStyle(WS_THICKFRAME,0);
// 去掉粗框架
m_wndStatusBar.Create(...);
ModifyStyle(0,WS_THICKFRAME);
// 恢復粗框架
在上面的代碼中,先是在創建狀態條前關閉或屏蔽掉WS_THICKFRAME,創建完狀態條之後再將WS_THICKFRAME恢復回來。原理是當創建狀態條的時候,MFC根據窗口風格確定狀態條上是否應該有尺寸手柄。至於在狀態條創建以後如何關閉尺寸手柄,目前我還沒有找到實現的方法。為了達到目的只好欺騙一下MFC 。圖六是去掉尺寸手柄後的狀態條。
圖六
最後一個要解決的問題是限制使用切分窗口的分割條來調整視圖窗口的大小,解決方法很簡單,要做兩件事情:首先是防止用鼠標操作切分條;其次是用普通光標指針代替尺寸調整光標指針。
具體方法是重載兩個函數:OnLButtonDown 和OnMouseMove
// 重載後不允許調整大小
void CMySplitterWnd::OnLButtonDown(UINT, CPoint)
{
return; //就這麼簡單
}
// 重載後不允許設置光標指針
void CMySplitterWnd::OnMouseMove(UINT, CPoint)
{
return; // 同上
}
我真是太喜歡這種代碼了,直接return,其它什麼也不做。通常當在切分條上點擊鼠標時,MFC執行的是如下代碼:
// (在 WinSplit.cpp 中)
void CSplitterWnd::OnLButtonDown(UINT, CPoint pt)
{
if (m_bTracking)
return;
StartTracking(HitTest(pt));
}
為了防止Windows檢測鼠標的點擊,重載這個函數,並且在這個函數中除了return以外什麼也不做。同樣,如法炮制 CSplitterWnd::OnMouseMove ,通常當鼠標移到切分條上時,光標指針會變為象電路圖中電容器一樣的東西。如圖七,
圖七
為了防止出現這個光標指針,重載CSplitterWnd::OnMouseMove並返回return,讓電容器短路,搞掂(圖八)。
圖八
第三個調整切分條的函數是:CSplitterWnd::DoKeyboardSplit。這個函數是用來實現“窗口|切分”菜單命令的,用戶通過它使用鍵盤來調整切分窗口。這裡不用重載CSplitterWnd::DoKeyboardSplit,把“窗口|切分”菜單命令從菜單中去掉不就行啦。
哈哈!從OnNcHitTest、最大化/最小化按鈕、系統菜單到欺騙MFC狀態條……,最後是切分 條。現在窗口應該保險了,試試看吧,肯定既不能移動窗口,也不能調整窗口大小。如果客戶打電話來抱怨或刪除你的程序的話請不要感到意外。最後祝編程愉快!
本文配套源碼