逆向探索–表情菜单解密
创建一个基于对话框的MFC应用程序QQFaceMenu(对话框为QQFaceMenuDlg),在文件QQFaceMenuDlg.h中的DECLARE_MESSAGE_MAP() 之前添加声明:
afx_msg void OnMeasureItem(int nIDCtl,LPMEASUREITEMSTRUCT lpmis);
afx_msg void OnDrawItem(int nIDCtl,LPDRAWITEMSTRUCT lpdis);
afx_msg void OnFaceMenuClicked(UINT nID);
在QQFaceMenuDlg.cpp文件中完善上述处理程序:
void CQQFaceMenuDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis) {
if (lpmis->CtlType == ODT_MENU) {
lpmis->itemHeight = 20 + 2;
lpmis->itemWidth = 10;
}
}
void CQQFaceMenuDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis) {
int index;
CDC dc;
POINT pt;
CBrush *pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU));
dc.Attach(lpdis->hDC);
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
if (lpdis->CtlType == ODT_MENU) {
index = lpdis->itemID - 0x785;
pt.x = lpdis->rcItem.left + 1;
pt.y = lpdis->rcItem.top + 1;
ImgQQFace.Draw(&dc, index, pt, ILD_NORMAL);
}
dc.Detach();
}
void CQQFaceMenuDlg::OnFaceMenuClicked(UINT nID) {
CClientDC dc(this);
POINT pt;
pt.x = 0;
pt.y = 0;
ImgQQFace.Draw(&dc, nID - 0x785, pt, ILD_NORMAL);
}
在BEGIN_MESSAGE_MAP(CQQFaceMenuDlg, CDialog)和END_MESSAGE_MAP()之间添加消息映射表:
ON_WM_MEASUREITEM()
ON_WM_DRAWITEM()
ON_COMMAND_RANGE(0x785,0x7B6,OnFaceMenuClicked)
在CQQFaceMenuDlg::OnInitDialog()中初始化ImgQQFace:
// TODO: Add extra initialization here
CGdiObject GdiObj;
ImgQQFace.Create(20,20,25,50,1);
GdiObj.Attach((HIMAGELIST)LoadBitmap(GetModuleHandle(NULL),MAKEINTRESOURCE(IDB_QQFace)));
ImageList_AddMasked(ImgQQFace.m_hImageList,(HBITMAP)GdiObj.m_hObject,0X808000);//CYAN
GdiObj.DeleteObject();
OnBtnShowFaceMenu是单击按钮时的处理过程,它负责将表情菜单弹出来:
void CQQFaceMenuDlg::OnBtnShowFaceMenu() {
// TODO: Add your control notification handler code here
CMenu popFaceMenu;
POINT point;
popFaceMenu.Attach(CreatePopupMenu());
int flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
for (int col = 0; col < 10; col++) {
int index;
for (int row = 0; row < 5; row++) {
index = row * 10 + col;
AppendMenu(popFaceMenu.m_hMenu, flags, index + 0x785, NULL);
flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
}
flags |= MF_MENUBREAK;
}
GetCursorPos(&point);
popFaceMenu.TrackPopupMenu(0x62, point.x, point.y, AfxGetApp()->GetMainWnd());
}
QQ表情菜单实现一
腾讯QQ或者QQGame客户端几乎都提供了一个表情菜单:
感觉很有意思,动手实现。整个资源图片可以在相关程序的安装文件中找到:
不过实现思想还不太会,于是就逆向了一下“大家来找茬”的客户端程序,看看人家是怎么
实现的。完了之后就写实现代码了:
用VC新建一个基于Dialog的项目,在OnInitDialog()事件中加载图像资源:
CGdiObject GdiObj;
ImgQQFace.Create(20,20,25,50,1);
GdiObj.Attach((HIMAGELIST)LoadBitmap(GetModuleHandle(NULL),MAKEINTRESOURCE(IDB_QQFace)));
ImageList_AddMasked(ImgQQFace.m_hImageList,(HBITMAP)GdiObj.m_hObject,0X808000);//CYAN
GdiObj.DeleteObject();
其中ImgQQFace是CImageList 类型的,点击一个按钮弹出这个表情菜单,则事件代码应是:
void CQQFaceMenuDlg::OnBtnShowFaceMenu() {
// TODO: Add your control notification handler code here
CMenu popFaceMenu;
POINT point;
popFaceMenu.Attach(CreatePopupMenu());
int flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
for (int col = 0; col < 10; col++) {
int index;
for (int row = 0; row < 5; row++) {
index = row * 10 + col;
AppendMenu(popFaceMenu.m_hMenu, flags, index + 0x785, NULL);
flags = MF_BYCOMMAND | MF_ENABLED | MF_STRING | MF_OWNERDRAW;
}
flags |= MF_MENUBREAK;
}
GetCursorPos(&point);
popFaceMenu.TrackPopupMenu(0x62, point.x, point.y, AfxGetApp()->GetMainWnd());
}
添加的菜单属于自制菜单,应该相应WM_MEASUREITEM和WM_DRAWITEM消息来画菜单:
void CQQFaceMenuDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpmis) {
if (lpmis->CtlType == ODT_MENU) {
lpmis->itemHeight = 20 + 2;
lpmis->itemWidth = 10;
}
}
void CQQFaceMenuDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpdis) {
int index;
CDC dc;
POINT pt;
CBrush *pBrush = new CBrush(::GetSysColor((lpdis->itemState & ODS_SELECTED) ? COLOR_HIGHLIGHT : COLOR_MENU));
dc.Attach(lpdis->hDC);
dc.FrameRect(&(lpdis->rcItem), pBrush);
delete pBrush;
if (lpdis->CtlType == ODT_MENU) {
index = lpdis->itemID - 0x785;
pt.x = lpdis->rcItem.left + 1;
pt.y = lpdis->rcItem.top + 1;
ImgQQFace.Draw(&dc, index, pt, ILD_NORMAL);
}
dc.Detach();
}
完成。
QQ表情菜单实现二
腾讯QQ或者QQGame客户端几乎都提供了一个“表情菜单”,在较早的版本里实现原理确实是靠菜单来完成的,详见上节。
不过到了2009版本以后,“表情菜单”却是靠窗口绘图来实现的,下面是我模拟的效果:
鼠标移动时gif预览是动态显示的(静态截图体现不出来),程序按[Esc]键可以退出。