让WINDOWS的开始按钮也靠边站?搞错没?你一定会问。不过这是真的。
其实在网上搜索一下“开始按钮”几个字,与编程相关的可能多半都是隐藏开始按钮或任务栏什么的,再进一步深入的可能就不好找了。而仅有的那些隐藏开始按钮或任务栏的代码,一旦刷新屏幕,原来已经隐藏了的东西都又冒出来了。
费话就不多说了,很久没写过技术文章了,突然觉得这个东西还比较新颖,好象还没发现有过实现类型功能的软件,就决定把这个功能的实现过程写出来,算是我在开发项目中的一点心得吧。
其实原理很简单,无论是开始按钮还是任务栏,在WINDOWS系统中就一个窗口的概念而已,所以对它们的操作跟普通窗口的操作没什么不一样,这也就意味着,以下的这些文字对高手级别的人物来说就不要看了。而对初学编程或对这篇文字的标题比较感兴趣或好奇的朋友不妨一看,也许你会从中学到点什么也不一定,呵呵。
在读这些文字之前,你需要明白什么是窗口,什么是窗口句柄以及进程空间这些概念,如果这些概念都不太清楚或甚至没看过,那么就请自行查找相关的资料或书籍了。因为这些文字主要还是针对初学编程的朋友,所以我从最开始地一步一步的分析,当然也可能分析得不正确或产生错误,大家就不要见笑了。
要对一个窗口进程操作,那么我们首先要做的第一件事是获得这个窗口的句柄,而获得窗口句柄的方法通常是通过FindWindow和FindWindowEx这两个API函数取得的,这两个函数要求传递窗口的类名、标题和其它的信息,这其中最关键的地方就是窗口的注册类名。这个类名从何得来呢,最简单的方法就是通过SPY++这个工具来获取,怎么获取的详细我就不说了,挺简单的。
前面我们已经说过,无论开始按钮还是任务栏在WINDOWS中就一个窗口的概念,所以要操纵它自然也得先取得它的句柄了。也许你已经知道任务栏的窗口类名为“Shell_TrayWnd”,如果不知道,那就自己使用SPY++来查找了。得到了任务栏的类名过后,接下来就是通过FindWindow函数取得它的窗口句柄了,如下:
HWND hTaskWnd = FindWindow("Shell_TrayWnd", NULL);
后面一个参数为窗口标题,是可选的,直接传NULL。如果函数调用成功,就已经取得任务栏的窗口句柄了。不过通常情况下都能成功的,如果不成功,自己调用GetLastError查看出错原因。
说到这里你可能有疑问了,我们要做的是移动开始按钮的位置啊,干吗说到任务栏上去了。原因很简单,任务栏相当于我们的一个Form窗体,而开始按钮仅是任务栏这个Form的一个按钮,即它的子窗体。我们先获得任务栏的句柄后,再通过这个句柄调用FindWindowEx来获取开始按钮这个子窗体的句柄,如下:
HWND hStartBtnWnd = FindWindowEx(hTaskWnd, NULL, "Button", NULL);
第一个参数为父窗口句柄,即任务栏的句柄,后面的参数不多说了,不明白的自己查。
到现在已经得到开始按钮的窗口句柄了,要移动它的位置还不简单,直接调用MoveWindow函数就可以了,你也许会这样认为。你现在确实已经可以调用MoveWindow函数移动它了,不过我遗憾地告诉你,请你改变任务栏的大小或刷新屏幕。发现什么了?是不是已经移动了的开始按钮又跑回原来的位置了?呵呵,郁闷吧。
这是怎么回事呢?咋解决呢?如何让它不跑回去呢?
原来开始按钮恢复到原来位置的原因是因为每当我们改变任务栏窗口大小或刷新屏幕是,系统都会向开始按钮发送WM_MOVE和WM_WINDOWPOSCHANGING这两个消息,而消息的参数指定了开始按钮的位置为(0,0),于是它又恢复回去了。如果不明白,你可以通过SPY++这个工具来捕获。而实际上,我也是用它来捕获到的。
这时也许你已经想到了,我们可以在开始按钮的窗口函数中拦截这两个消息,使它的位置不为(0,0),而改变成我们定义的位置。要拦截这两个消息,那么就得用我们自定义的窗口函数将原来开始按钮的窗口函数替换掉。这是你也许已经想到了,使用SetWindowLong改变原窗口函数地址,使它指向我们定义的窗口函数。但是遗憾的是,并不想我们想的这样简单,WINDOWS不允许一个进程访问另一个进程的进程空间,别说更改目标窗口函数地址,连访问一个变量都不行。
这时我们唯一的方法就是将我们的代码注入到开始按钮的进程空间中,即Explorer,进程注入的方法我就不说了,我们使用最简单也最不费事的HOOK方式注入。代码如下:
HWND hStartButtonWnd = NULL;
HWND hTaskWnd = NULL;
hTaskWnd = FindWindow("Shell_TrayWnd", NULL);
if (hTaskWnd != NULL)
{
hStartButtonWnd = FindWindowEx(hTaskWnd, NULL, "Button", NULL);
if (hStartButtonWnd != NULL)
{
DWORD hThreadId = GetWindowThreadProcessId(hStartButtonWnd, NULL);
if (hThreadId != 0)
{
g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)GetMsgProc, g_hInst, hThreadId);
}
}
}
HOOKPROC中的主要代码如下:
hTaskWnd = FindWindow("Shell_TrayWnd", NULL);
if (hTaskWnd != NULL)
{
hStartButtonWnd = FindWindowEx(hTaskWnd, NULL, "Button", NULL);
g_hBtnOldProc = (WNDPROC)GetWindowLong(hStartButtonWnd, GWL_WNDPROC);
SetWindowLong(hStartButtonWnd, GWL_WNDPROC, (LONG)ButtonWndProc);
}
以上代码说明如下:
首先获得开始按钮的窗口句柄,然后再通过窗口句柄得到线程ID,并根据线程ID安装消息钩子函数;一旦消息到达,即执行消息钩子函数。在消息钩子函数中获取开始按钮窗口句柄,然后调用GetWindowLong取得原窗口函数地址以便保存,再调用SetWindowLong更改其窗口函数地址为我们定义的窗口函数ButtonWndProc。
至此我们已经成功将代码注入并更改了开始按钮的窗口函数,接下来要做的就是在窗口函数中拦截WM_MOVE和WM_WINDOWPOSCHANGING这两个消息了。代码如下:
LRESULT CALLBACK ButtonWndProc(HWND hwnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch (message)
{
case WM_MOVE:
{
MoveWindow(hwnd, 76, 0, 50, 25, TRUE);
break;
}
case WM_WINDOWPOSCHANGING:
{
MoveWindow(hwnd, 76, 0, 50, 25, TRUE);
break;
}
}
至此开始按钮可怎么也恢复不回去了,呵呵,动手试试看吧。不过最后记得归还原窗口函数地址哦,不然你的Explorer是必死无疑的了。该在哪里恢复窗口函数地址呢?在什么地方调用UnhookWindowsHookEx呢?自己思考吧,放错了地方Explorer依然会死的哦。
到此就已经成功让WINDOWS的开始按钮靠边站了。不过如果只这样让它挪挪地方,那空出来的地方空空的总让人觉得不爽啦,所以还得放点什么在上边吧,比如女朋友的相片啊或什么的,甚至还可以做个和WINDOWS的开始菜单一样的东西来。这些功能就由你自己去自由发挥了,呵呵。不过在交互时你也许会遇到一些麻烦,比如你在开始按钮原来的位置上放了一个自己的按钮,那么这个按钮的事件你的应用程序如何才能得知或其它什么更复杂的操作,不过都应该不是什么大问题,相信你能够自己解决的。
就写这么多了,很久没写技术类的文章了,有没有写清楚自己都不知道,错了的莫怪哈。有什么不懂的大家提出来相互讨论就是了。这段时间一直在加班,累死掉了。
作者:陶冶(无邪)
邮箱:taoyi5178@hotmail.com
------摘自:http://www.xcnw.com/Article/cxsj/cxsjqt/200506/20050603135314.html
http://www.xcnw.com/Article/cxsj/cxsjqt/200506/20050603135314.html