【转载】Notepad++源码分析

在网上发现了一个哥们写了关于Notepad++源码的文章,不过就写了一就没有了,我就接着他的工作再说说吧!微笑

  大三了,也写了一点儿程序了,但是如果只是按照自己的思路写下去恐怕难以提高,于是准备开始阅读一些开源的代码,看看别人的代码,跟别人学习学习。

    一上来就接触过于大型的项目怕是无力掌握,于是从小一点儿的开始。很早的时候我就准备读一读Notepad++这个开源项目的代码了,但是总是有别的事 情,一拖再拖,现在安静下来了,就读一读吧。一开始当然从V1.0开始读啦,之后再慢慢的更新,扩大。并且,边读边记一些笔记,有一些可能看起来非常幼 稚,不过确实是我所想所感的,于是记录下来,方便自己今后查阅,也方便与别的有需要的童鞋。

今天主要的任务是分析一下Notepad++启动是的动作,准备好源码(可以从SourceForge下载),设置好断点,准备开始吧!

    下载完源码之后,可以看到,Notepad++的1.0版本一共有21个cpp文件,看他们的名字基本就可以知道他们的作用了。不要忘了今天的目的,就 是了解Notepad++启动时相关的动作,从开始执行分析到他的消息处理循环,今天的任务就完成了~所以,咱们果断打开winmain.cpp文件。

    话说第一眼看到这个文件我正要感叹作者真是好心人,写这么多注释,这些可爽了~而是定睛一看,居然是版权说明!代码里面干净的要死。。。只好硬着头皮一点儿一点儿的看了。。。

 1 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpszCmdLine, int nCmdShow)  
 2 {  
 3       
 4     HWND hNotepad_plus = ::FindWindow(Notepad_plus::getClassName(), NULL);   
 5     if (hNotepad_plus)   
 6     {  
 7         if (::IsIconic(hNotepad_plus))   
 8             ::OpenIcon(hNotepad_plus);   
 9         ::SetForegroundWindow(hNotepad_plus);   
10         if (lpszCmdLine[0])    
11         {  
12             COPYDATASTRUCT copyData;  
13             copyData.dwData = 0;//(ULONG_PTR);  
14             copyData.cbData = DWORD(strlen(lpszCmdLine) + 1);   
15             copyData.lpData = lpszCmdLine;   
16               
17             ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&copyData);   
18         }  
19         return 0; // if there has been a opended window, do not open another one!  
20     }  
21   
22     Notepad_plus notepad_plus_plus;  
23     MSG msg;  
24     msg.wParam = 0;  
25     try {  
26         char *pPathNames = NULL;  
27         if (lpszCmdLine[0])  
28         {  
29             pPathNames = lpszCmdLine;  
30         }  
31         notepad_plus_plus.init(hInstance, NULL, pPathNames);  
32         HACCEL hAccTable = ::LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_M30_ACCELERATORS));  
33         MSG msg;  
34         msg.wParam = 0;  
35         while (::GetMessage(&msg, NULL, 0, 0))  
36         {  
37             // if the message doesn't belong to the notepad_plus_plus's dialog  
38             if (!notepad_plus_plus.isDlgMsg(&msg))  
39             {   
40                 if (::TranslateAccelerator(notepad_plus_plus.getHSelf(), hAccTable, &msg) == 0)   
41                 {  
42                     ::TranslateMessage(&msg);  
43                     ::DispatchMessage(&msg);  
44                 }  
45             }  
46         }  
47     } catch(int i) {  
48         if (i == 106901)  
49             ::MessageBox(NULL, "Scintilla.init is failled!", "106901", MB_OK);  
50         else {  
51             char str[50] = "God Damn Exception : ";  
52             char code[10];  
53             itoa(i, code, 10);  
54             ::MessageBox(NULL, strcat(str, code), "int exception", MB_OK);  
55         }  
56     }  
57       
58     catch(std::exception ex) {  
59         ::MessageBox(NULL, ex.what(), "Exception", MB_OK);  
60     }  
61     catch(...) {  
62         systemMessage("System Err");  
63     }  
64     return (UINT)msg.wParam;  
65 } 

这个文件倒也直接了当,只有一个WinMain函数,消息循环这个文件里面也有了,看来搞定这个文件咱们今天就可以收工了~如果你对 WindowsAPI很熟悉的话,看这几行代码应该不成问题,可是我以前基本没用过(只用过很少几个),所以看代码也顺便学习一下API的使用了~由于这 里面大量使用了API,我不可能一一列出,所以打开MSDN或者Google准备好吧~

    好,一行一行的看:

    HWND hNotepad_plus = ::FindWindow(Notepad_plus::getClassName(), NULL);

    这一行调用了FindWindow,这个API的目的是为了找到满足类名为第一个参数,而标题名为第二个参数的handle(句柄)。MSDN上面说第二个参数给NULL则匹配所有满足类名为第一个参数的句柄。

    乍看起来,这个有些奇怪。作为主函数一开始不初始化,先获取句柄,这是安得什么心?各位看官向下看就能找到答案了~

 1 if (hNotepad_plus)   
 2     {  
 3         if (::IsIconic(hNotepad_plus))   
 4             ::OpenIcon(hNotepad_plus);   
 5         ::SetForegroundWindow(hNotepad_plus);   
 6         if (lpszCmdLine[0])    
 7         {  
 8             COPYDATASTRUCT copyData;  
 9             copyData.dwData = 0;//(ULONG_PTR);  
10             copyData.cbData = DWORD(strlen(lpszCmdLine) + 1);   
11             copyData.lpData = lpszCmdLine;   
12               
13             ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&copyData);   
14         }  
15         return 0;   
16     }  

这段代码是在结果不是NULL,也就是查找成功的时候执行的代码。认真看的话能看出一些名堂来了~对了,就是为了保证Notepad++只存在一个实 例,如果已经打开了一个Notepad++的实例,则hNotepad_plus这个句柄必然不是NULL,这个时候如果用户再次尝试打开,或者尝试拖拽 某个文件到Notepad++程序中,只会导致当前存在实例被最大化,或者开启一个新的tab来显示新打开的文件。(怎么实现的咱们之后再分析吧)各位可 以试一试,找到编译生成的Notepad++程序(在这里是debug版的),分别打开两次,把拖拽文件到程序图标打开试试,就会发现跟咱们想的一样,确 实只有一个Notepad++的实例存在~嗯嗯,还不赖~

    好了,由于咱们现在分析的是第一次执行的时候的状况,所以可以不考虑这一段代码,如果你设置了断点,并且单步跟踪执行的话,也会发现这一段代码没有执行~

    好,接下来进入第一次创建窗体时的部分~

    这段代码是被try起来的,我们先不考虑异常处理部分,这段代码相对还是比较好懂的~

 1 char *pPathNames = NULL;  
 2         if (lpszCmdLine[0])  
 3         {  
 4             pPathNames = lpszCmdLine;  
 5         }  
 6         notepad_plus_plus.init(hInstance, NULL, pPathNames);  
 7         HACCEL hAccTable = ::LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_M30_ACCELERATORS));  
 8         MSG msg;  
 9         msg.wParam = 0;  
10         while (::GetMessage(&msg, NULL, 0, 0))  
11         {  
12             // if the message doesn't belong to the notepad_plus_plus's dialog  
13             if (!notepad_plus_plus.isDlgMsg(&msg))  
14             {   
15                 if (::TranslateAccelerator(notepad_plus_plus.getHSelf(), hAccTable, &msg) == 0)   
16                 {  
17                     ::TranslateMessage(&msg);  
18                     ::DispatchMessage(&msg);  
19                 }  
20             }  

首先是,由于程序支持将文件拖拽到图标打开文件(马上就能看到怎么实现的),所以先去命令行参数的第一个参数,也就是要打开的文件名,然后调用notepad_plus_plus.init方法,这个方法值得一看,在init上面右键,转到定义!

 1 Window::init(hInst, parent);  
 2       
 3     WNDCLASS MACOCS30EditorClass;  
 4     MACOCS30EditorClass.style = 0;//CS_HREDRAW | CS_VREDRAW;  
 5     MACOCS30EditorClass.lpfnWndProc = Notepad_plus_Proc;  
 6     MACOCS30EditorClass.cbClsExtra = 0;  
 7     MACOCS30EditorClass.cbWndExtra = 0;  
 8     MACOCS30EditorClass.hInstance = _hInst;  
 9     MACOCS30EditorClass.hIcon = ::LoadIcon(_hInst, MAKEINTRESOURCE(IDI_M30ICON));  
10     MACOCS30EditorClass.hCursor = NULL;  
11     MACOCS30EditorClass.hbrBackground = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));  
12     MACOCS30EditorClass.lpszMenuName = MAKEINTRESOURCE(IDR_M30_MENU);  
13     MACOCS30EditorClass.lpszClassName = _className;  
14     if (!::RegisterClass(&MACOCS30EditorClass))  
15     {  
16         systemMessage("System Err");  
17         throw int(98);  
18     }  
19     _hSelf = ::CreateWindowEx(  
20                     WS_EX_ACCEPTFILES,/  
21                     _className,/  
22                     "MACOCS 30 IDE Demonstration",/  
23                     WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,/  
24                     CW_USEDEFAULT, CW_USEDEFAULT,/  
25                     CW_USEDEFAULT, CW_USEDEFAULT,/  
26                     _hParent,/  
27                     NULL,/  
28                     _hInst,/  
29                     (LPVOID)this); // pass the ptr of this instantiated object  
30                                    // for retrive it in Notepad_plus_Proc from   
31                                    // the CREATESTRUCT.lpCreateParams afterward.  
32     
33     if (!_hSelf)  
34     {  
35         systemMessage("System Err");  
36         throw int(777);  
37     }  
38       
39     if (cmdLine)  
40     {  
41         FileNamStringSpliter fnss(cmdLine);  
42         char *pFn = NULL;  
43         for (int i = 0 ; i < fnss.size() ; i++)  
44         {  
45             pFn = (char *)fnss.getFileName(i);  
46             doOpen((const char *)pFn);  
47         }  
48     }  
49     setTitle(_className);  
50     display();  
51     checkDocState(); 

 这个方法非常普通,首先调用父类Window的init方法,这个Window是自己实现的,之后咱们再分析里面有什么,为什么这样做~

然 后就是填充WNDCLASS的对象,MACOCS30EditorClass。WNDCLASS的各个属性各位可以查MSDN,需要各位注意的是 MACOCS30EditorClass.lpfnWndProc = Notepad_plus_Proc;这一行,这个注册的是消息处理的回调函数,至于消息驱动那一套东西我就不说了,大家可以自行 Google~Notepad_plus_Proc这个函数很关键,赶紧右键转到定义一下!

 1 LRESULT CALLBACK Notepad_plus::Notepad_plus_Proc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)  
 2 {  
 3   switch(Message)  
 4   {  
 5     case WM_NCCREATE : // First message we get the ptr of instantiated object  
 6                        // then stock it into GWL_USERDATA index in order to retrieve afterward  
 7     {  
 8         Notepad_plus *pM30ide = (Notepad_plus *)(((LPCREATESTRUCT)lParam)->lpCreateParams);  
 9         pM30ide->_hSelf = hwnd;  
10         ::SetWindowLong(hwnd, GWL_USERDATA, (LONG)pM30ide);  
11         return TRUE;  
12     }  
13     default :  
14     {  
15       return ((Notepad_plus *)::GetWindowLong(hwnd, GWL_USERDATA))->runProc(hwnd, Message, wParam, lParam);  
16     }  
17   }  
18 }  

这个方法里面特别处理了一个WM_NCCREATE消息,把其他的消息都送给了runProc这个方法来处理。那么WM_NCCREATE这个消息为什么要如此特别的处理一下呢?

    这里干了一件事情,就是用SetWindowLong方法,把hwnd对应的GWL_USERDATA设置为Notepad_plus对象的指针。而以 后再处理消息的时候,则是用GWL_USERDATA对应的这个Notepad_plus对象指针调用runProc函数。这里我学到了一招,把 GWL_USERDATA作为数据存储的容器。尤其是消息处理函数给咱们的参数里面并没有Notepad_plus这种对象的指针,只有在 WM_NCCREATE消息的时候会通过CREATESTRUCT对象里面的lpCreateParameter传递过来,可是咱们的runProc是定 义在Notepad_plus这个类中的啊,得Notepad_plus这个对象的指针才能调用啊,所以就在第一次创建的时候把这个指针存到这个容器之 中,以后就可以随意使用啦!

    好,从Notepad_plus_Proc回来,继续init之旅~接下来是用RegisterClass注册这个类的对象,然后 CreateWindowEx。注意咱们之前说的那个WM_NCCREATE消息也就是这个时候触发的,clear?CreateWindowEx的第一 个参数很有意思,WS_EX_ACCEPTFILES,去MSDN查一下就发现,正是因为这个属性,才使我们可以拖动文件到Notepad++里面去,好 了,以后如果咱自己写的应用也要有这种效果,咱们也这么干~

    创建之后,会判断用户是否确实拖拽了一个文件以打开。这段代码也放到以后分析~

    最后是setTitle设置标题,默认设置时类名,这个类名是由一个宏定义的常量决定的:

    #define NOTEPAD_PP_CLASS_NAME "Notepad++"

    在Notepad_plus.cpp中这样赋值:

    const char Notepad_plus::_className[32] = NOTEPAD_PP_CLASS_NAME;

    所以,这里的_className也就是Notepad++!然后显示,检查文档那个的状态等等,这些咱们都以后再分析!

    之后是加载助记符~助记符的详细列表可以在资源文件的Notepad_plus.rc文件中找到,为了方便大家,贴到这里:

 1 IDR_M30_ACCELERATORS ACCELERATORS   
 2 BEGIN  
 3     //"A",            IDM_EDIT_SELECTALL,     VIRTKEY, CONTROL  
 4     //"C",            IDM_EDIT_COPY,          VIRTKEY, CONTROL  
 5     "N",            IDM_FILE_NEW,           VIRTKEY, CONTROL  
 6     "O",            IDM_FILE_OPEN,          VIRTKEY, CONTROL  
 7     "S",            IDM_FILE_SAVE,          VIRTKEY, CONTROL  
 8     //"V",            IDM_EDIT_PASTE,         VIRTKEY, CONTROL  
 9     //VK_DELETE,      IDM_EDIT_DELETE,        VIRTKEY   
10     //"X",            IDM_EDIT_CUT,           VIRTKEY, CONTROL  
11     //"Y",            IDM_EDIT_REDO,          VIRTKEY, CONTROL  
12     //"Z",            IDM_EDIT_UNDO,          VIRTKEY, CONTROL  
13 "F",            IDM_EDIT_FIND,          VIRTKEY, CONTROL  
14 "H",            IDM_EDIT_REPLACE,      VIRTKEY, CONTROL  
15 VK_F3,        IDM_EDIT_FINDNEXT,    VIRTKEY  
16 END  

助记符加载完毕,就进入了主消息循环,看来胜利在望啊~~

    首先GetMessage,第一件事是检测是不是对话框的消息,如果是的话就不继续处理了,对话框咱们也是以后再分析!继续向下看~

    首先来了一个TranslateAccelerator,也就是判断是不是助记符,如果是的话,返回的结果不是0,也就不继续 TranslateMessage了,这个也是MSDN上面说的:an application should not call TranslateMessage if the TranslateAccelerator function returns a nonzero value. 

    最后就是消息循环啦~

    好了,今天的任务也就到此为止了~~单击执行按钮,Notepad++的1.0版本也就展现在我们眼前了~

    分析了这么多,好像感觉少了点儿什么~对!这些控件什么的怎么出来的?好,下一次就把这个作为切入点,分析一下WM_CREATE消息的处理中到底干了些什么~说不定可以顺藤摸瓜,看看都有Notepad++都“实现”了哪些控件~

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

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

 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)的标记.

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)实现如下:

 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信息

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结构体了

 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).定义如下:

 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()函数中的处理方式了.这里也省略了.下次说吧.

::CallWindowProc(_tabBarDefaultProc, hwnd, Message, wParam, lParam);  

这里的_tebBarDefaultProc的由来是用来保存原始Tabbar控件的消息处理函数.即:

1 ::SetWindowLong(_hSelf, GWL_USERDATA, reinterpret_cast<LONG>(this));  
2 _tabBarDefaultProc = reinterpret_cast<WNDPROC>(::SetWindowLong(_hSelf, GWL_WNDPROC, reinterpret_cast<LONG>(TabBar_Proc)));    

说到这里更加令人感兴趣的东东来了,就是SetWindowLong,当然与之对应的就是GetWindowLong.这里用来设置和获取窗口类得参数,因 此我们可以知道一个窗口类被定义好之后还是可以修改这个窗口类的.能够修改哪些东西,我把MSDN上的东东粘贴下来,一起分析看看吧

The SetWindowLong function changes an attribute of the specified window. The function also sets the 32-bit (long) value at the specified offset into the extra window memory.  
  
Note  This function has been superseded by the SetWindowLongPtr function. To write code that is compatible with both 32-bit and 64-bit versions of Microsoft Windows, use the SetWindowLongPtr function.  
  
Syntax  
  
LONG SetWindowLong(          HWND hWnd,  
    int nIndex,  
    LONG dwNewLong  
);  
Parameters  
  
hWnd  
[in]   
Handle to the window and, indirectly, the class to which the window belongs.  
  
Windows 95/98/Me: The SetWindowLong function may fail if the window specified by the hWnd parameter does not belong to the same process as the calling thread.  
  
nIndex  
[in] Specifies the zero-based offset to the value to be set. Valid values are in the range zero through the number of bytes of extra window memory, minus the size of an integer. To set any other value, specify one of the following values.   
GWL_EXSTYLE  
Sets a new extended window style. For more information, see CreateWindowEx.   
GWL_STYLE  
Sets a new window style.  
GWL_WNDPROC  
Sets a new address for the window procedure.  
  
Windows NT/2000/XP: You cannot change this attribute if the window does not belong to the same process as the calling thread.  
  
GWL_HINSTANCE  
Sets a new application instance handle.  
GWL_ID  
Sets a new identifier of the window.  
GWL_USERDATA  
Sets the user data associated with the window. This data is intended for use by the application that created the window. Its value is initially zero.  
The following values are also available when the hWnd parameter identifies a dialog box.  
DWL_DLGPROC  
Sets the new address of the dialog box procedure.  
DWL_MSGRESULT  
Sets the return value of a message processed in the dialog box procedure.  
DWL_USER  
Sets new extra information that is private to the application, such as handles or pointers.  
dwNewLong  
[in] Specifies the replacement value.   
Return Value  
  
If the function succeeds, the return value is the previous value of the specified 32-bit integer.  
  
If the function fails, the return value is zero. To get extended error information, call GetLastError.   
  
If the previous value of the specified 32-bit integer is zero, and the function succeeds, the return value is zero, but the function does not clear the last error information. This makes it difficult to determine success or failure. To deal with this, you should clear the last error information by calling SetLastError(0) before calling SetWindowLong. Then, function failure will be indicated by a return value of zero and a GetLastError result that is nonzero.  
  
  
  
  
Remarks  
  
Certain window data is cached, so changes you make using SetWindowLong will not take effect until you call the SetWindowPos function. Specifically, if you change any of the frame styles, you must call SetWindowPos with the SWP_FRAMECHANGED flag for the cache to be updated properly.   
  
If you use SetWindowLong with the GWL_WNDPROC index to replace the window procedure, the window procedure must conform to the guidelines specified in the description of the WindowProc callback function.   
  
If you use SetWindowLong with the DWL_MSGRESULT index to set the return value for a message processed by a dialog procedure, you should return TRUE directly afterward. Otherwise, if you call any function that results in your dialog procedure receiving a window message, the nested window message could overwrite the return value you set using DWL_MSGRESULT.   
  
Calling SetWindowLong with the GWL_WNDPROC index creates a subclass of the window class used to create the window. An application can subclass a system class, but should not subclass a window class created by another process. The SetWindowLong function creates the window subclass by changing the window procedure associated with a particular window class, causing the system to call the new window procedure instead of the previous one. An application must pass any messages not processed by the new window procedure to the previous window procedure by calling CallWindowProc. This allows the application to create a chain of window procedures.   
  
Reserve extra window memory by specifying a nonzero value in the cbWndExtra member of the WNDCLASSEX structure used with the RegisterClassEx function.   
  
You must not call SetWindowLong with the GWL_HWNDPARENT index to change the parent of a child window. Instead, use the SetParent function.   
  
If the window has a class style of CS_CLASSDC or CS_OWNDC, do not set the extended window styles WS_EX_COMPOSITED or WS_EX_LAYERED.  
  
Windows 95/98/Me: SetWindowLongW is supported by the Microsoft Layer for Unicode (MSLU). SetWindowLongA is also supported to provide more consistent behavior across all Windows operating systems. To use these versions, you must add certain files to your application, as outlined in Microsoft Layer for Unicode on Windows 95/98/Me Systems.  

不过如果你觉得你的英文不行的话可以看看百度百科的解释http://baike.baidu.com/view/1080272.htm不过我觉得上面的解释有些句子读都读不通顺.大概的意思就是说我们可以通过SetWindowLong函数来改变一个类(子类)的消息处理函数,类类型,消息处理函数返回值等等.但是为了使得我们对类得修改生效,则需要调用SetWindowPos函数.

当然如同csdn所说的,SetWindowLong以及GetWindowLong已经过时了,不过这个函数的作用和思想还是很重要的,至少在notepad++源码中经常看到。

原文地址:https://www.cnblogs.com/flyelephant/p/Notepadplusplus_SourceCode_Analysis.html