利用钩子实现菜单阴影效果2
来源:互联网 发布:家居网络布线 编辑:程序博客网 时间:2024/04/29 15:36
我们再来看看,怎么"登记"它们:
CMenuWndHook* CMenuWndHook::AddWndHook(HWND hwnd)
{
CMenuWndHook* pWnd = NULL;
if (m_WndMenuMap.Lookup(hwnd, pWnd))
{
// 有这个人了,不用再登记了。
return pWnd;
}
// 给它分配个房间(牢房! 嘿嘿)
pWnd = new CMenuWndHook(hwnd);
if (pWnd != NULL)
{
m_WndMenuMap.SetAt(hwnd, pWnd);
}
return pWnd;
}
// 另外还可有一个对应的查找函数:
CMenuWndHook* CMenuWndHook::GetWndHook(HWND hwnd)
{
CMenuWndHook* pWnd = NULL;
if (m_WndMenuMap.Lookup(hwnd, pWnd))
{
return pWnd;
}
return NULL;
}
上面的函数和变量大部分都是静态成员,因为hook系统只要有一套就可以了到这里为止,坚巨的任务已经完成了一半,做下面的事,就得心应手多了。下面是窗口的新过程,依然为一个静态的函数。
LRESULT CALLBACK CMenuWndHook::CoolMenuProc(HWND hWnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam)
{
WNDPROC oldWndProc = (WNDPROC)::GetProp(hWnd, CoolMenu_oldProc);
CMenuWndHook* pWnd = NULL;
switch (uMsg)
{
// 计算非客户区的大小--------------------------
case WM_NCCALCSIZE:
{
LRESULT lResult = CallWindowProc(oldWndProc,
hWnd,
uMsg,
wParam,
lParam);
if ((pWnd = GetWndHook(hWnd)) != NULL)
{
pWnd->OnNcCalcsize((NCCALCSIZE_PARAMS*)lParam);
}
return lResult;
}
break;
// 当窗口的位置将要发生改变, 在这里它一般发生在菜单被弹出之前,
// 给你最后一次机会设置它的位置.
case WM_WINDOWPOSCHANGING:
{
if ((pWnd = GetWndHook(hWnd)) != NULL)
{
pWnd->OnWindowPosChanging((LPWINDOWPOS)lParam);
}
} break;
// 为什么要响应这个消息呢? 我也不知道啊,我只知道,当菜单是以动画的方式弹出的时候
// 系统是通过发送这个消息来绘制菜单的,wParam是对应的设备上下文句柄,不过我也不知
// 道它到底是属于谁的.
case WM_PRINT:
{
LRESULT lResult = CallWindowProc(oldWndProc,
hWnd,
uMsg,
wParam,
lParam);
if ((pWnd = GetWndHook(hWnd)) != NULL)
{
pWnd->OnPrint(CDC::FromHandle((HDC)wParam));
}
return lResult;
}
break;
//这个就不同说了吧.
case WM_NCPAINT:
{
if ((pWnd = GetWndHook(hWnd)) != NULL)
{
pWnd->OnNcPaint();
return 0;
}
}
break;
// 菜单窗口被隐藏的时候,我也不知道这种情况会不会发生, :(, 主要是看到人家这样处理了.
case WM_SHOWWINDOW:
{
if ((pWnd = GetWndHook(hWnd)) != NULL)
{
pWnd->OnShowWindow(wParam != NULL);
}
}
break;
// 菜单窗口被销毁的时候
case WM_NCDESTROY:
{
if ((pWnd = GetWndHook(hWnd)) != NULL)
{
pWnd->OnNcDestroy();
}
}
break;
}
return CallWindowProc(oldWndProc, hWnd, uMsg, wParam, lParam);
}
下面就看如何慢慢实现这些消息的响应函数吧: void CMenuWndHook::OnWindowPosChanging(WINDOWPOS *pWindowPos)
{
if (!IsShadowEnabled())
{
//加一块区域来显示阴影-------
pWindowPos->cx += 4;
pWindowPos->cy += 4;
}
// 为了绘制阴影,我们须要先保存这个区域的图像,以便绘制半透明的阴影.
if (!IsWindowVisible(m_hWnd) && !IsShadowEnabled())
{
if (m_bmpBack.m_hObject != NULL)
{
m_bmpBack.DeleteObject();
}
m_bmpBack.Attach(GetScreenBitmap(CRect(pWindowPos->x,
pWindowPos->y,
pWindowPos->cx,
pWindowPos->cy)));
}
}
void CMenuWndHook::OnNcCalcsize(NCCALCSIZE_PARAMS* lpncsp)
{
if (!IsShadowEnabled())
{
//留出一点区域来显示阴影-------
lpncsp->rgrc[0].right -= 4;
lpncsp->rgrc[0].bottom -= 4;
}
}
上面我用到了两个全局函数, 其中IsShadowEnabled是检测系统是否开启了菜单阴影(主要针对于Windows XP, Windows 2003及他更高的版本) 如果系统已经给我们开启了阴影,我们还忙乎什么哦。BOOL WINAPI IsShadowEnabled()
{
BOOL bEnabled = FALSE;
if (SystemParametersInfo(SPI_GETDROPSHADOW, 0, bEnabled,0))
{
return bEnabled;
}
return FALSE;
}
其中 SPI_GETDROPSHADOW 在VC6里面没有被声明,你需要自已声明它:
#ifndef SPI_GETDROPSHADOW
#define SPI_GETDROPSHADOW 0x1024
#endif
另外还有 GetScreenBitmap 函数用于截取屏幕上指定区域内的图像: HBITMAP WINAPI GetScreenBitmap (LPCRECT pRect)
{
HDC hDC;
HDC hMemDC;
HBITMAP hNewBitmap = NULL;
if ((hDC = ::GetDC(NULL)) != NULL )
{
if ((hMemDC = ::CreateCompatibleDC(hDC)) != NULL)
{
if ((hNewBitmap = ::CreateCompatibleBitmap(hDC,
pRect->right - pRect->left,
pRect->bottom - pRect->top)) != NULL)
{
HBITMAP hOldBitmap = (HBITMAP)::SelectObject(hMemDC, hNewBitmap);
::BitBlt(hMemDC, 0, 0, pRect->right - pRect->left, pRect->bottom - pRect->top,
hDC, pRect->left, pRect->top, SRCCOPY);
::SelectObject(hMemDC, (HGDIOBJ)hOldBitmap);
}
::DeleteDC(hMemDC);
}
::ReleaseDC(NULL, hDC);
}
return hNewBitmap;
}
下面这两个函数要做的事就差不多了: void CMenuWndHook::OnNcPaint()
{
CWindowDC dc(CWnd::FromHandle(m_hWnd));
OnPrint(&dc);
}
void CMenuWndHook::OnPrint(CDC *pDC)
{
CRect rc;
GetWindowRect(m_hWnd, &rc);
rc.OffsetRect(-rc.TopLeft());
// 绘制阴影
if (!IsShadowEnabled())
{
CDC cMemDC;
cMemDC.CreateCompatibleDC (pDC);
HGDIOBJ hOldBitmap = ::SelectObject (cMemDC.m_hDC, m_bmpBack);
pDC->BitBlt (0, rc.bottom - 4, rc.Width() - 4, 4, &cMemDC, 0, rc.bottom - 4, SRCCOPY);
pDC->BitBlt (rc.right - 4, 0, 4, rc.Height(), &cMemDC, rc.right - 4, 0, SRCCOPY);
DrawShadow(pDC, rc);
rc.right -= 4;
rc.bottom -= 4;
}
// 绘制边框
pDC->Draw3dRect(rc, m_crFrame[0], m_crFrame[1]);
rc.DeflateRect (1, 1);
pDC->Draw3dRect(rc, m_crFrame[2], m_crFrame[3]);
}
在指定的矩形区域内绘制阴影的全局函数(当然这些函数不一定都要做成全局函数,我把它们写成了全局函数是因为在好几个类中都用到了它们, 写成全局函数便于调用) 也许你会觉得这不符合面向对象编程的思想,其实面向过程的编程思想,并不一定就比面向对象的思想落后,我把这些比较独立的函数写成全局函数,当作API函数用,还是觉得很方便的,如果硬要将它们塞到一个类里面,反而觉得很郁闷 。:-). void DrawShadow(CDC *pDC, CRect rect);
void DrawShadow(CDC *pDC, CRect rect)
{
COLORREF oldcolor = RGB(255, 255, 255);
BYTE newValR, newValG, newValB;
BYTE AlphaArray[] = {140, 170, 212, 240};
BYTE AlphaArray2[] = {170, 205, 220, 240, 240, 250, 255};
// 底部的阴影 -----------------------------------------
int i, j;
for (j = 0; j < 4; j++)
{
for (i = 6; i <= rect.right - 5; i++)
{
oldcolor = pDC->GetPixel(i, rect.bottom - (4 - j));
newValR = GetRValue(oldcolor) * AlphaArray[j] / 255;
newValG = GetGValue(oldcolor) * AlphaArray[j] / 255;
newValB = GetBValue(oldcolor) * AlphaArray[j] / 255;
pDC->SetPixel(i, rect.bottom - (4 - j), RGB(newValR, newValG, newValB));
}
}
// 右边的阴影 -----------------------------------------
for (i = 0; i < 4; i++)
{
for (j = 6; j <= rect.bottom - 5; j++)
{
oldcolor = pDC->GetPixel(rect.right - (4 - i), j);
newValR = GetRValue(oldcolor) * AlphaArray[i] / 255;
newValG = GetGValue(oldcolor) * AlphaArray[i] / 255;
newValB = GetBValue(oldcolor) * AlphaArray[i] / 255;
pDC->SetPixel(rect.right - (4 - i), j, RGB(newValR, newValG, newValB));
}
}
// 角上的阴影 --------------------------------------
for (i = 0; i < 4; i++)
{
for (j = 0; j < 4; j++)
{
if ((i + j) > 6) break;
oldcolor = pDC->GetPixel(rect.right - 4 + i, rect.bottom - 4 + j);
newValR = GetRValue(oldcolor) * AlphaArray2[i + j] / 255;
newValG = GetGValue(oldcolor) * AlphaArray2[i + j] / 255;
newValB = GetBValue(oldcolor) * AlphaArray2[i + j] / 255;
pDC->SetPixel(rect.right - 4 + i,
rect.bottom - 4 + j,
RGB(newValR,
newValG,
newValB));
oldcolor = pDC->GetPixel(rect.right - 4 + i, rect.top + 5 - j);
newValR = GetRValue(oldcolor) * AlphaArray2[i + j] / 255;
newValG = GetGValue(oldcolor) * AlphaArray2[i + j] / 255;
newValB = GetBValue(oldcolor) * AlphaArray2[i + j] / 255;
pDC->SetPixel(rect.right - 4 + i,
rect.top + 5 - j,
RGB(newValR,
newValG,
newValB));
oldcolor = pDC->GetPixel(rect.left - i + 5, rect.bottom - 4 + j);
newValR = GetRValue(oldcolor) * AlphaArray2[i + j] / 255;
newValG = GetGValue(oldcolor) * AlphaArray2[i + j] / 255;
newValB = GetBValue(oldcolor) * AlphaArray2[i + j] / 255;
pDC->SetPixel(rect.left - i + 5,
rect.bottom - 4 + j,
RGB(newValR,
newValG,
newValB));
}
}
}
这么复杂? 唉! 还不是想让它把阴影画得更好看一点, 速度?...在我机子上还过得去。毕竟菜单是不会被频繁地重画的. 这样实现阴影确实有点笨拙,且在意外的时候可能会出现一些不愉快的绘图上的bug. 但是要实现Windows XP 那样完美的菜单阴影还是很难的。我希望已经知道的高手,能指点指点! 谢了先。
下面是处理清理工作了: void CMenuWndHook::OnNcDestroy()
{
delete this; // 自杀!
}
void CMenuWndHook::OnShowWindow(BOOL bShow)
{
if (!bShow)
{
delete this; // 自杀2!
}
}
... ..., 好狠哦! 嘿嘿!
扫尾工作还由是~CMenuWndHook它老人家做, 在delete自己的时候会自动调用它的: CMenuWndHook::~CMenuWndHook()
{
WNDPROC oldWndProc = (WNDPROC)::GetProp(m_hWnd, CoolMenu_oldProc);
if (oldWndProc != NULL)
{
::SetWindowLong(m_hWnd, GWL_WNDPROC,(DWORD)(ULONG)oldWndProc);
::RemoveProp(m_hWnd, CoolMenu_oldProc);
}
m_WndMenuMap.RemoveKey(m_hWnd);
if (m_bmpBack.m_hObject != NULL)
{
m_bmpBack.DeleteObject();
}
}
这个类基本上写完了,如果我还有什么没讲清的地方,你就再去看看我的源代码吧。我们可以在APP类里面调用它: ............
#include "MenuWndHook.h"
...........
BOOL CNewmenuApp::InitInstance()
{
.......
CMenuWndHook::InstallHook();
}
int CNewmenuApp::ExitInstance()
{
CMenuWndHook::UnInstallHook();
return CWinApp::ExitInstance();
}
我时常听见人说 Delhpi 程序界面比VC程序的界面如何如何好? 如果是说默认的那些控件的外观,VC确实不如Delphi,(微软也真小气,自已产品的界面做得那么"华丽"(像Office XP/2003, Windows XP,VS.NET...), 而给我们用的这些控件的外观却这么"老土")...总之真正的精美的有个性的界面是大家自已做出来的,这正是我钟爱VC的理由之一。呵呵。
补充一点:点击菜单发送的消息顺序如下:
//////////////////////////////////////////////////////////////////////////////
// When mouse click menu bar item , message send sequence
// WINXP
// visual effect no:
// WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM
// visual effect fade effect
// WM_ERASEBKGND -- WM_DRAWITEM -- WM_PRINT
// scroll effect
// WM_ERASEBKGND -- WM_DRAWITEM -- WM_PRINT -- WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM
// WIN2k
// visual effect no:
// WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM
// visual effect fade effect
// WM_PRINT -- WM_ERASEBKGND -- WM_DRAWITEM
// scroll effect
// WM_PRINT -- WM_ERASEBKGND -- WM_DRAWITEM -- WM_NCPAINT -- WM_ERASEBKGND -- WM_DRAWITEM
// there are three messages before all the case:
// WM_WINDOWPOSCHANGING -- WM_NCCALCSIZE -- WM_WINDOWPOSCHANGING
///////////////////////////////////////////////////////////////////////////////
其顺序与菜单的过渡效果有关,以xp为例,可以在“显示器\属性\外观\效果”来设定它的过渡效果:
- 利用钩子实现菜单阴影效果2
- 利用钩子实现菜单阴影效果
- vc 利用钩子实现菜单阴影效果
- 利用BlurMaskFilter实现具有阴影效果的Imageview
- 利用Radiogroup Radiobutton 实现滑动效果菜单
- 利用字典实现多级菜单效果
- CSS实现阴影效果
- CSS实现阴影效果
- CSS实现阴影效果
- WPF 实现阴影效果
- css实现阴影效果
- css实现阴影框效果
- 图片阴影效果的实现
- 实现textview的阴影效果
- CSS实现图片阴影效果
- 图片阴影效果的实现
- 图片阴影效果的实现
- Android实现图片阴影效果
- 淘宝技术发展 5.Java时代:创造技术-TFS
- struts2环境搭建
- 条件格式:判断某一单元格内容,将一定范围内所有单元格设定背景色(亲自实践)
- solaris 中的highbit和lowbit函数
- android使用代码安装apk
- 利用钩子实现菜单阴影效果2
- Excel2007 图标集的使用(亲自实践)
- 只能输入数字的TextBox自定义控件
- 淘宝技术发展 6.Java时代,创造技术-TFS
- SharedPreferences 详解
- MFC控件和对话框一起变小变大
- Excel工作簿减肥--删除无用的对象 (亲自实践)
- Windows Azure 革新 – TFS集成(WAWS第2部分)
- 【学习点滴-数据结构-栈&队列】 栈的应用之二:括号匹配的检测