WIN98特殊視窗的動態拖動 (轉)

worldblog發表於2007-12-06
WIN98特殊視窗的動態拖動 (轉)[@more@]

 

特殊視窗的動態拖動

  宋立波

  WIN98中常居頂層的無標題條視窗是一種特殊的視窗,典型例項有IME輸入法應用、UCWIN平臺、各種浮動工具箱、桌面工具欄等。

  一、命令檢測與游標動態提示

  這種視窗拖動一般分為兩種:特定客戶命令區域和非特定客戶命令區域。特定客戶命令區域是指利用"RECT"定義的特定子矩形區域;非特定客戶命令區域是指沒有明確定義的視窗客戶區域部分,即所有特定客戶命令區域之外的部分。實現該功能的首要問題是如何檢測和處理特定客戶命令區域和非特定客戶命令區域內的滑鼠命令,以及如何利用滑鼠游標來動態提示何時可以進行視窗的拖動操作。

  1、特定客戶命令區域檢測滑鼠命令

  要在視窗中設定實現拖動功能的圖示命令按鈕,就必須在資源中定義命令按鈕的特定客戶區域,該區域一般也就是命令按鈕圖示的矩形區域,這個區域的定義方法為"RECT DragRT",其中DragRT為定義的檢測滑鼠命令的矩形區域,它用DragRT.LEFT、DragRT.TOP、DragRT.RIGHT和DragRT.BOTTOM四個引數來描述矩形區域相對於視窗客戶區域左上角的相對座標值。也可用"SETRECT"填充。

  視窗函式在處理滑鼠訊息WM_LBUTTONDOWN時,當收到傳遞的滑鼠位置引數lParam後,透過MAKEPOINT()函式將其轉換為視窗座標值,然後利用判斷某座標點是否位於特定矩形區域內的函式PtInRect(),判斷出滑鼠指標是否點選在拖動命令按鈕之內。

  2、非特定客戶命令區域檢測滑鼠命令

  當視窗應用程式中採取了非特定客戶命令區域拖動方法時,必須在資原始檔中事先確定各個特定客戶命令區域的矩形座標,這時非特定客戶命令區域是不規則的區域,它需要根據實際的應用程式視窗及各個命令按鈕矩形區域來確定,也就是各個命令按鈕矩形區域相對於視窗矩形區域的“非”子集。視窗函式在處理滑鼠訊息WM_LBUTTONDOWN時,首先利用函式PtInRect()判斷當前滑鼠指標是否點選在各個命令按鈕矩形區域內,如果未點選在任何命令按鈕區域內,則可確定滑鼠點選在非特定客戶命令區域內,從而啟動視窗拖動功能。

  3、視窗拖動功能的游標動態提示

  實現滑鼠游標動態提示功能前需要定製滑鼠游標形狀,視窗拖動功能的動態提示游標形狀一般為四箭頭圖案,這可以利用公司的SDK、FPT3.0和VC++4.1等高階開發中的資源編輯器"IMAGE EDIT"等來實現。游標資原始檔一般為32X32的2色或16色.CUR圖形檔案。建立起自己的滑鼠游標資原始檔後,首先需要在應用程式的資原始檔中定義滑鼠游標,然後在應用程式中利用LoadCursor()調入,並在類中進行設定:wc.hCursor=hCurm。

  當滑鼠游標需要在視窗的特定客戶命令按鈕區域內或非特定客戶命令區域內進行動態提示時,不能使用上述定義方法,而需要在視窗函式處理WM_MOUSEMOVE訊息時進行特殊處理:首先判斷滑鼠游標指標的當前位置是否在拖動命令按鈕或非特定客戶命令區域內,如果滑鼠指標位置滿足拖動視窗功能區域的要求,則利用函式SETCURSOR()改變滑鼠游標圖案,提示使用者此時可以進行視窗拖動操作,並將滑鼠輸入控制權交給當前視窗,同時設定改變後的滑鼠游標標誌;當滑鼠指標移出拖動視窗命令啟動區域時,恢復原來滑鼠游標圖案同時釋放滑鼠輸入焦點控制權,並清除滑鼠游標動態提示標誌單元。

  二、動態拖動框的定製

  視窗拖動前的關鍵問題是滑鼠拖動視窗過程中的拖動框顯示與擦除功能實現。視窗拖動虛框就是在 整個螢幕區域內顯示被拖動視窗大小的線框,它的大小需要根據被拖動視窗的矩形區域大小和實際需要來具體確定,一般為被拖動視窗的矩形區域大小。

  WINDOWS系統中的繪圖方法是透過顯示裝置描述表實現的,繪圖操作需要佔用一定的GDI資源。WINDOWS 95中的GDI資源要比WINDOWS3.X中的GDI資源大得多,所以系統可以為視窗、選單、對話方塊、字型和各種繪圖函式分配足夠的GDI資源。WINDOWS中有兩種使用顯示裝置描述符表的方法:視窗顯示客戶區域和直接操作視窗顯示客戶區域。更新視窗顯示客戶區域是直接針對應用程式視窗矩形區域而言的,在視窗函式響應WM_PAINT訊息時利用圖形操作命令進行視窗更新處理:

  InvalidateRect(hWnd,&WinRECT,TRUE);//WinRECT為要更新區域

  UpdateWindow(hWnd);

  視窗初始建立時預設為更新視窗的全部區域,當要更新的矩形區域為NULL時表示更新視窗所有矩形區域。函式UpdateWindow()通知系統向要更新矩形區域的視窗傳送WM_PAINT訊息,視窗函式接收到WM_PAINT訊息後首先利用BeginPaint()函式取得裝置描述符表,然後利用圖形命令直接對顯示裝置進行更新操作,最後利用EndPaint()函式通知系統更新操作結束。

  更新視窗矩形區域直接使用視窗類中定義的螢幕畫刷,即使利用()函式選擇相應螢幕畫刷也無效,而且更新矩形區域範圍是透過InvalidateRect()函式累加的,由UpdateWindow()函式通知系統開始進行視窗更新操作。整個過程是由系統來排程的,因此使用這種方法無法實現視窗的拖動虛框繪製和實時操作。

  直接操作視窗客戶區域的方法是利用GetDC()函式直接取得顯示裝置控制程式碼,利用各種圖形操作命令直接對顯示裝置進行繪圖。它使用螢幕當前設定的畫筆和畫刷來實現各種圖形繪製操作,無須向系統傳遞任何訊息應用程式就可以實時地對螢幕視窗進行更新和繪圖操作。其操作過程是首先取得顯示裝置描述符控制程式碼hDC=GetDC(hWnd),當hWnd 引數為NULL時取得的是整個螢幕的裝置描述符表控制程式碼,然後利用SelectObject()函式設定當前螢幕的畫筆和畫刷,利用各種畫圖函式完成螢幕的繪圖操作,最後利用ReleaseDC()函式釋放獲取的裝置描述表。如果利用畫矩形函式Rectangle()實現虛框,那麼在設定當前螢幕畫筆的同時必須使用SelectObject(hDC,GetStockObject(NULL_BRUSH))遮蔽掉任何螢幕畫刷,否則WINDOWS程式會很快吞筮掉所有GDI資源,相當於在螢幕裝置資源中增加了無數矩形區域。

  對於視窗拖動框的擦除操作,只需在拖動框繪製函式中將螢幕的圖形畫筆操作方式設定為R2_XORPEN異或方式,即SetROP2(hDC2,R2_XORPEN)。在拖動框繪製結束時注意恢復,在視窗拖動框移動到下一個位置前,在原螢幕位置重新繪製函式一次將原來拖動框擦除(見程式1)。

  //利用畫矩形函式實現拖動實框(由於篇幅畫線、畫點函式省略)

  void DrawMoveRect(int xx1,int yy1,int xx2,int yy2,int xy)

  { HDC hDC;

   int oldrop2,m,k;

   hDC = GetDC(NULL); //取得全螢幕裝置描述控制程式碼

   oldrop2= GetROP2(hDC); //取得原來螢幕畫圖方式

   SetROP2(hDC,R2_XORPEN); //設定異或螢幕畫圖方式

   SelectObject(hDC,GetStockObject(NULL_BRUSH));//遮蔽畫刷

   SelectObject(hDC2,GetStockObject(WHITE_PEN));//選擇畫筆

   for (k=0;k    xx1-=1;xx2+=1;

   yy1-=1;yy2+=1;

   Rectangle(hDC2,xx1,yy1,xx2,yy2);

   }

   SetROP2(hDC2,oldrop2); //恢復原來作圖方式

   ReleaseDC(NULL,hDC2); //釋放裝置描述符表

  }

  (程式1)

  三、動態拖動視窗“三步曲”

  完成了對WINDOWS 高階視窗的客戶區域拖動命令判斷、拖動功能的滑鼠游標動態提示和定製視窗拖動框函式之後,就需要實現整個拖動方案中的拖動過程啟動、視窗拖動框移動和拖動結束處理的三步曲過程。於是必須在視窗函式中直接處理WM_LBUTTONDOWN、WM_MOUSEMOVE和WM_LBUTTONUP訊息。下面來具體處理上述三個步驟中的細節問題。

  第一步,在視窗函式中對滑鼠點選訊息WM_LBUTTONDOWN進行判斷,以處理使用者透過滑鼠游標動態提示功能獲取滿足視窗拖動條件時,按下滑鼠左鍵產生的啟動拖動過程訊息。其功能性程式碼如下:

  case WM_LBUTTONDOWN:

   pt = MAKEPOINT(lParam);

   if(PtInRect(&DragRT,pt)){

   DragBegin((LPRECT)&WinRT,lParam,hWnd,2);//啟動過程

  } else {//進行其它處理;}

  上述DragBegin()函式為筆者開發的視窗拖動啟動函式。由於一個高階視窗應用程式中往往存在很多視窗,所以將其作為一個單獨函式處理。其中WinRT為高階視窗矩形區域,這裡作為拖動框矩形區域引數來傳遞,lParam為滑鼠游標指標長整數,hWnd為當前被拖動視窗的控制程式碼,2 為拖動框寬度。這時,需要同時將滑鼠控制權交給當前被拖動視窗、設定拖動視窗標誌單元、儲存當前滑鼠在螢幕上的位置並顯示被拖動視窗的拖動框。拖動功能啟動函式的如下:

  void DragBegin(LPRECT WinRect, //拖動框的矩形區域

   LPARAM lParam, //滑鼠游標當前指標

   HWND hwnd, //當前視窗控制程式碼

   unsigned int kk) //拖動框顯示的寬度

  {SetCapture(hwnd); //拖動時視窗必須具有滑鼠輸入權

   MoveFlag=TRUE; //設定拖動標誌

   oldmx=LO(lParam);//記錄當前滑鼠螢幕座標X

   oldmy=HIWORD(lParam);//記錄當前滑鼠螢幕座標Y

   DrawMoveRect(WinRect->left,WinRect->top,//顯示拖動框

   WinRect->right,WinRect->bottom,kk);

  }

  第二步,需要處理滑鼠拖動視窗時的拖動框移動過程,這需要在視窗函式中進行WM_MOUSEMOVE訊息處理。拖動框的移動包括上次顯示拖動框的清除和本次拖動框的顯示兩步。由於拖動框繪製函式中對當前的繪製方式進行了重新設定,異或方式使得只要重新在原螢幕座標位置處呼叫一次該函式即可清除拖動框,因此,在滑鼠拖動視窗移動過程中顯示和清除拖動框只需要呼叫兩次拖動框繪製函式即可。另外,拖動框在螢幕上位置的計算方法也非常簡單,就是將當前取得的螢幕位置座標值減去儲存的前次螢幕位置座標值所得滑鼠移動偏移量,再用原來視窗螢幕左上角座標值加上這個偏移量,就可以確定被拖動視窗和拖動框新的螢幕位置座標值。

  case WM_MOUSEMOVE:

   if(MoveFlag==TRUE){

   DragMove((LPRECT)&WinRT,WinWT,WinHi,lParam,2);

   } else {//進行其它處理;}

  鑑於高階視窗應用程式一般為多個子視窗,所以將拖動框移動處理過程單獨編製成函式,並且對滑鼠拖動視窗過程中視窗不能完全位於螢幕可見區域內進行了特殊處理。開發者可根據需要自行調整其位置,以便被拖動的視窗能夠完全被顯示於螢幕可視區域內。其拖動過程函式原始碼部分如下:

   void DragMove(LPRECT rcwin, //拖動框矩形區域

   unsigned int wi, //被拖動視窗寬度

   unsigned int hi, //被拖動視窗高度

   LPARAM lParam, //滑鼠位置指標

   unsigned int kk) //拖動框邊框寬度

   { DrawMoveRect(rcwin->left,rcwin->top,

   rcwin->right,rcwin->bottom,kk);//清除上次畫拖動框

   rcwin->left+=LOWORD(lParam)-sImeG.oldmx;//計算視窗

   rcwin->top+=HIWORD(lParam)-sImeG.oldmy; //新位置

   sImeG.oldmx=LOWORD(lParam); //儲存當前座標值

   sImeG.oldmy=HIWORD(lParam);

   if (rcwin->left<0) rcwin->left=0;//對視窗超越螢幕

   if (rcwin->left>sImeG.xScrWi-wi) //可視區域處理

   rcwin->left=sImeG.xScrWi-wi;

   ii=sImeG.yScrHi-hi-(sImeG.WinVer<0x35f ? 0:BOTOFF);

   if (rcwin->top<0) rcwin->top=0; //對WIN95進行底部

   if (rcwin->top>ii) rcwin->top=ii;//特殊保留處理

   rcwin->right =rcwin->left+wi-1;

   rcwin->bottom=rcwin->top+hi-1;

   DrawMoveRect(rcwin->left,rcwin->top,

   rcwin->right,rcwin->bottom,kk);//畫新位置拖動框

   }

  第三步,在滑鼠拖動視窗結束時需要進行視窗的實際移動處理,這就需要在處理WM_LBUTTONUP訊息時利用MOVEWINDOW()命令進行實際移動處理。同樣鑑於多視窗原因仍然需要將這個處理過程單獨形成一個函式,而且在移動視窗前還需要利用繪製函式清除螢幕上所畫的拖動框。如果視窗未完全位於螢幕的可見位置,還必須進行適當調整使被拖動的視窗能夠完全位於螢幕可視區內,同時釋放滑鼠控制權並清除拖動視窗標誌單元。結束過程的描述性程式碼部分如下:

  case WM_LBUTTONUP:

   if (sImeG.MoveFlag==TRUE){//拖動標誌有效

   DragEnd((LPRECT)&WinRT,WinWT,WinHI,hWnd);}

  拖動結束處理函式的原始碼部分如下:

   void DragEnd(LPRECT rcwin,//拖動框矩形區域

   unsigned int wi, //被拖動視窗寬度

   unsigned int hi, //被拖動視窗高度

   unsigned int kk) //拖動框邊框寬度

   { DrawMoveRect(rcwin->left,rcwin->top,

   rcwin->right,rcwin->bottom,1); //清除拖動框

   if (rcwin->left<0) rcwin->left=0;//對視窗超越螢幕

   if (rcwin->left>sImeG.xScrWi-wi) //可視區域處理

   rcwin->left=sImeG.xScrWi-wi;

   ii=sImeG.yScrHi-hi-(sImeG.WinVer<0x35f ? 0:BOTOFF);

   if (rcwin->top<0) rcwin->top=0; //對WIN95進行底部

   if (rcwin->top>ii) rcwin->top=ii;//特殊保留處理

   rcwin->right =rcwin->left+wi-1;

   rcwin->bottom=rcwin->top+hi-1;

   MoveWindow(hwnd,rcwin->left,rcwin->top,

   wi,hi,TRUE); //將視窗實際移到新位置

   sImeG.MoveFlag=FALSE; //清除拖動標誌單元

   ReleaseCapture(); //釋放滑鼠控制權

   }

  至此,整個視窗的動態拖動過程就完成了。有興趣的讀者可以去"bobosong.yeah"主頁推薦的軟體來看看這一功能的實現。

  (作者地址:遼寧省鐵嶺市委辦公室 112000 收稿日期:1999.2.1)


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-988853/,如需轉載,請註明出處,否則將追究法律責任。

相關文章