首页 > 代码库 > Notepad++源码分析(2)(转载)

Notepad++源码分析(2)(转载)

这次介绍NotePad++中多标签页下的鼠标拖动标签页位置的功能.

在TabBar.cpp文件中的类处理函数定义如下:

[cpp] view plaincopy
  1. LRESULT TabBar::runProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     switch (Message)  
  4.     {  
  5.         case WM_LBUTTONDOWN :  
  6.         {  
  7.             ::CallWindowProc(_tabBarDefaultProc, hwnd, Message, wParam, lParam);  
  8.   
  9.             if (_doDragNDrop)  
  10.             {  
  11.                 _nSrcTab = _nTabDragged = ::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0);  
  12.           
  13.                 POINT point;  
  14.                 point.x = LOWORD(lParam);  
  15.                 point.y = HIWORD(lParam);  
  16.                 if(::DragDetect(hwnd, point))   
  17.                 {  
  18.                     // Yes, we‘re beginning to drag, so capture the mouse...  
  19.                     _isDragging = true;  
  20.                     ::SetCapture(hwnd);  
  21.                     return TRUE;  
  22.                 }  
  23.                 break;  
  24.             }  
  25.             else  
  26.                 return TRUE;  
  27.         }  
  28.   
  29.         case WM_MOUSEMOVE :  
  30.         {  
  31.             if (_isDragging)  
  32.             {  
  33.                 POINT p;  
  34.                 p.x = LOWORD(lParam);  
  35.                 p.y = HIWORD(lParam);  
  36.                 exchangeItemData(p);  
  37.   
  38.                 // Get cursor position of "Screen"  
  39.                 // For using the function "WindowFromPoint" afterward!!!  
  40.                 ::GetCursorPos(&_draggingPoint);  
  41.                 draggingCursor(_draggingPoint);  
  42.                 return TRUE;  
  43.             }  
  44.             break;  
  45.         }  
  46.   
  47.         case WM_LBUTTONUP :  
  48.         {  
  49.             if (_isDragging)  
  50.             {  
  51.                 if(::GetCapture() == _hSelf)  
  52.                     ::ReleaseCapture();  
  53.   
  54.                 // Send a notification message to the parent with wParam = 0, lParam = 0  
  55.                 // nmhdr.idFrom = this  
  56.                 // destIndex = this->_nSrcTab  
  57.                 // scrIndex  = this->_nTabDragged  
  58.                 NMHDR nmhdr;  
  59.                 nmhdr.hwndFrom = _hSelf;  
  60.                 nmhdr.code = _isDraggingInside?TCN_TABDROPPED:TCN_TABDROPPEDOUTSIDE;  
  61.                 nmhdr.idFrom = reinterpret_cast<unsigned int>(this);  
  62.   
  63.                 ::SendMessage(_hParent, WM_NOTIFY, 0, reinterpret_cast<LPARAM>(&nmhdr));  
  64.                 return TRUE;                  
  65.             }  
  66.             break;  
  67.         }  
  68.   
  69.         case WM_CAPTURECHANGED :  
  70.         {  
  71.             if (_isDragging)  
  72.             {  
  73.                 _isDragging = false;  
  74.                 return TRUE;  
  75.             }  
  76.             break;  
  77.         }  
  78.   
  79.         case WM_DRAWITEM :  
  80.         {  
  81.             drawItem((DRAWITEMSTRUCT *)lParam);  
  82.             return TRUE;  
  83.         }  
  84.   
  85.         case WM_KEYDOWN :  
  86.         {  
  87.             if (wParam == VK_LCONTROL)  
  88.                 ::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_PLUS_TAB)));  
  89.             return TRUE;  
  90.         }  
  91.     }  
  92.     return ::CallWindowProc(_tabBarDefaultProc, hwnd, Message, wParam, lParam);  
  93. }  

 

 其中WM_LBUTTONDOWN消息的处理方法中,

a. 调用::CallwindowProc函数来用_tabBarDefaultProc这个函数处理WM_LBUTTONDOWN消息(这里的_tabBarDefaultProc的作用就是作为默认的消息处理函数,这个是怎么来的,稍后解释).

b. 如果鼠标处于拖拽动作状态,则调用::SendMessage(_hSelf, TCM_GETCURSEL, 0, 0);获取当前拖拽标签的编号,这里的_hSelf为TabBar实例窗口的句柄.在Notepad++很多窗口对象源码中都有类似的设计实现.言归正 传,向TabBar窗口发送TCM_GETCURSEL消息会返回当前被选择标签(tab)的标记.

 

[cpp] view plaincopy
  1. Returns the index of the selected tab if successful, or -1 if no tab is selected.  
  2.   
  3. lResult = SendMessage(      // returns LRESULT in lResult       
  4. (HWND) hWndControl,      // handle to destination control       
  5. (UINT) TCM_GETCURSEL,      // message ID       
  6. (WPARAM) wParam,      // = 0; not used, must be zero      
  7. (LPARAM) lParam      // = 0; not used, must be zero   
  8. );    

 

c. 将鼠标点击的坐标作为::DragDetect函数的参数传给该函数.::DragDetect函数的作用就是为了判断鼠标点击左键后是否有拖拽的动作.如果对这个函数还有什么疑问可以参阅http://baike.baidu.com/view/1080200.htm.

d. ::DragDetect函数返回为真,则将_isDragging标记为true,表示鼠标处于拖拽动作状态.

e. ::SetCapture(hwnd)函数表示为hwnd表示的窗口绑定鼠标操作.即便绑定后的鼠标光标离开了该窗口,鼠标动作也由该窗口捕获,如有什么疑问可以参阅http://baike.baidu.com/view/1080215.html?fromTaglist

 

剩下的工作有WM_MOUSEMOVE,WM_LBUTTONUP消息的处理函数来处理.

在WM_MOUSEMOVE消息的处理函数中,会做一下工作

a. 判断鼠标是否处于拖拽动作状态

b. 如果鼠标处于拖拽状态则执行exchangeItemData(p)函数其中p为鼠标在屏幕上的坐标(POINT).exchangeItemData(p)实现如下:

[cpp] view plaincopy
  1. void TabBar::exchangeItemData(POINT point)  
  2. {  
  3.     TCHITTESTINFO hitinfo;  
  4.     hitinfo.pt.x = point.x;  
  5.     hitinfo.pt.y = point.y;  
  6.   
  7.     // Find the destination tab...  
  8.     unsigned int nTab = ::SendMessage(_hSelf, TCM_HITTEST, 0, (LPARAM)&hitinfo);  
  9.   
  10.     // The position is over a tab.  
  11.     if (hitinfo.flags != TCHT_NOWHERE)  
  12.     {  
  13.           
  14.         _isDraggingInside = true;  
  15.   
  16.         if (nTab != _nTabDragged)  
  17.         {  
  18.             //1. set to focus  
  19.             ::SendMessage(_hSelf, TCM_SETCURSEL, nTab, 0);  
  20.   
  21.             //2. shift their data, and insert the source  
  22.             TCITEM itemData_nDraggedTab, itemData_shift;  
  23.             itemData_nDraggedTab.mask = itemData_shift.mask = TCIF_IMAGE | TCIF_TEXT;  
  24.             char str1[256];  
  25.             char str2[256];  
  26.   
  27.             itemData_nDraggedTab.pszText = str1;  
  28.             itemData_nDraggedTab.cchTextMax = (sizeof(str1));  
  29.   
  30.             itemData_shift.pszText = str2;  
  31.             itemData_shift.cchTextMax = (sizeof(str2));  
  32.   
  33.             ::SendMessage(_hSelf, TCM_GETITEM, _nTabDragged, reinterpret_cast<LPARAM>(&itemData_nDraggedTab));  
  34.   
  35.             if (_nTabDragged > nTab)  
  36.             {  
  37.                 for (int i = _nTabDragged ; i > nTab ; i--)  
  38.                 {  
  39.                     ::SendMessage(_hSelf, TCM_GETITEM, i-1, reinterpret_cast<LPARAM>(&itemData_shift));  
  40.                     ::SendMessage(_hSelf, TCM_SETITEM, i, reinterpret_cast<LPARAM>(&itemData_shift));  
  41.                 }  
  42.             }  
  43.             else  
  44.             {  
  45.                 for (int i = _nTabDragged ; i < nTab ; i++)  
  46.                 {  
  47.                     ::SendMessage(_hSelf, TCM_GETITEM, i+1, reinterpret_cast<LPARAM>(&itemData_shift));  
  48.                     ::SendMessage(_hSelf, TCM_SETITEM, i, reinterpret_cast<LPARAM>(&itemData_shift));  
  49.                 }  
  50.             }  
  51.             //  
  52.             ::SendMessage(_hSelf, TCM_SETITEM, nTab, reinterpret_cast<LPARAM>(&itemData_nDraggedTab));  
  53.   
  54.             //3. update the current index  
  55.             _nTabDragged = nTab;  
  56.               
  57.         }  
  58.     }  
  59.     else  
  60.     {  
  61.         //::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_TAB)));  
  62.         _isDraggingInside = false;  
  63.     }  
  64.       
  65. }  

函 数中首先在TCHITTESTINFO结构体中将鼠标坐标参数传入pt.x,pt.y.具体TCHITTESTINFO结构体还是很有意思的,并且通过发 送TCM_HITTEST消息带上这个结构体作为参数.这个用法更有意思.在MSDN上的解释如下,得到指定屏幕位置处得Tab信息

[cpp] view plaincopy
  1. <pre class="cpp" name="code">Determines which tab, if any, is at a specified screen position. You can send this message explicitly or by using the TabCtrl_HitTest macro  
  2. lResult = SendMessage(      // returns LRESULT in lResult       
  3. (HWND) hWndControl,      // handle to destination control       
  4. (UINT) TCM_HITTEST,      // message ID       
  5. (WPARAM) wParam,      // = 0; not used, must be zero      
  6. (LPARAM) lParam      // = (LPARAM) (LPTCHITTESTINFO) pinfo;   
  7. );   

 

要想知道具体能够得到标签(tab)的什么状态,必须了解这个TCHITTESTINFO结构体了

[cpp] view plaincopy
  1. typedef struct tagTCHITTESTINFO {  
  2.     POINT pt;  
  3.     UINT flags;  
  4. } TCHITTESTINFO, *LPTCHITTESTINFO;  
  5. pt Position to hit test, in client coordinates. flags Variable that receives the results of a hit test. The tab control sets this member to one of the following values:   
  6. TCHT_NOWHERE The position is not over a tab.   
  7. TCHT_ONITEM The position is over a tab but not over its icon or its text. For owner-drawn tab controls, this value is specified if the position is anywhere over a tab.   
  8. TCHT_ONITEMICON The position is over a tab‘s icon.   
  9. TCHT_ONITEMLABEL The position is over a tab‘s text.   
  10. TCHT_ONITEM is a bitwise-OR operation on TCHT_ONITEMICON and TCHT_ONITEMLABEL.  



 

 了解了结构体以及消息的作用之后,理解if( hitinfo.flags != TCHT_NOWHERE)这一句的意思就很easy了.

c. 如果鼠标拖拽行为将tab拖动到其他tab位置时,将_isDraggingInside置为true表示启动了鼠标拖拽标签(tab)插入.然后将通过 sendmessage获得鼠标坐标处标签的编号nTab与处理WM_LBUTTONDOWN消息时鼠标左键点击的标签标号_nTabDragged对 比.注意这两个标号的获取方式是不同的.具体怎么不一样,回顾一下前文就晓得了.然后将tabbar空间当前现则的标签标号标记为nTab.

d.  新建两个TCITEM结构体.首先将结构体的mask成员变量设置为TCIF_IMAGE|TCIF_TEXT.即在获得数据,填充这个结构体时,只填充pszText,iImage两个成员变量即可.具体的说法鉴于篇幅的关系可以参考MSDN.

e.然后就是根据_nTabDragged以及nTab之间的关系,采用TCM_GETITEM,TCM_SETITEM来类似于插入排序的方式,将鼠标拖拽的标签插入的指定的位置上.

f.为了方便插入操作的循环进行将成员变量_nTabDragged = nTab.

 

到这里只是实现了标签中TEXT和Image的替换,剩下的工作就是根据鼠标的位置,给光标附上不同的cursor图标,关键函数为draggingCursor(_draggingPoing).定义如下:

[cpp] view plaincopy
  1. void TabBar::draggingCursor(POINT screenPoint)  
  2. {  
  3.     HWND hWin = ::WindowFromPoint(screenPoint);  
  4.     if (_hSelf == hWin)  
  5.         ::SetCursor(::LoadCursor(NULL, IDC_ARROW));  
  6.     else  
  7.     {  
  8.         char className[256];  
  9.         ::GetClassName(hWin, className, 256);  
  10.         if ((!strcmp(className, "Scintilla")) || (!strcmp(className, WC_TABCONTROL)))  
  11.         {  
  12.             if (::GetKeyState(VK_LCONTROL) & 0x80000000)  
  13.                 ::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_PLUS_TAB)));  
  14.             else  
  15.                 ::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_TAB)));  
  16.         }  
  17.         else  
  18.             ::SetCursor(::LoadCursor(_hInst, MAKEINTRESOURCE(IDC_DRAG_INTERDIT_TAB)));  
  19.     }  
  20. }  


在这里我觉得比较有价值的知识点就是,::WindowFromPoint(screenPoint);他可以根据屏幕坐标返回改位置处窗口控件句柄.(类似于spy++的功能了).剩下的代码我不说你应该看的懂吧.哈哈~~

 

最后WM_LBUTTONUP消息的处理函数就更好理解了.

a. 取消拖拽状态时绑定鼠标动作的控件

b. 通知父窗口此处的鼠标拖拽的细节.这里的处理方式就是Notepad_plus.cpp中notify()函数中的处理方式了.这里也省略了.下次说吧.