文章转自:http://www.gymsaga.com/project/593.html
今天我们在来继续设计QQ界面,本节添加QQ窗口桌面自动隐藏功能和换肤功能,同时修复了之前的一些小bug
先说一下之前遗留下的小bug
1:搜索的Edit控件失去焦点后显示异常
解决方案:当Edit调用ShowWindow显示的时候,同时调用SetFocus,将当前的焦点权给Edit即可,这样当Edit失去焦点后,显示也就正常了,同时,为了达到QQ点击主面板任意位置也能恢复之前的效果,在窗口点击的WM_LBUTTONDOWN消息内,添加SetFocus();
2:QQ签名增加了编辑功能,原理和搜索的Edit处理一样,当点击QQ签名的按钮的时候,隐藏掉按钮,同时显示Edit,当Edit失去焦点后,将Edit隐藏,同时获取Edit的文本,将获取到的字符串显示在按钮上,参照代码的OnEnKillfocusEditPass方法
3:好友列表头像变灰,图像变灰代码如下
bool CImageEx::SetGray() { //加载判断 ASSERT(m_pImage!=NULL); if (m_pImage==NULL) return false; UINT nWidth = GetWidth(); UINT nHeight = GetHeight(); BitmapData bmpData; Rect rect(0, 0, nWidth, nHeight); m_pImage->LockBits(&rect, Gdiplus::ImageLockModeRead|Gdiplus::ImageLockModeWrite, PixelFormat32bppARGB, &bmpData); int nValue = 0; BYTE * pArray = (BYTE *)bmpData.Scan0; UINT nBytes = (UINT)((nWidth * nHeight) << 2); for (UINT i = 0; i < nBytes; i += 4) { nValue = (int)(pArray[i] * 0.1 + pArray[i+1] * 0.2 + pArray[i+2] * 0.7); pArray[i] = nValue; pArray[i+1] = nValue; pArray[i+2] = nValue; } m_pImage->UnlockBits(&bmpData); return true; }
注意一点,这里是将图像m_pImage设置成了灰度图像,如果你想单纯的只是在绘制的时候成灰度的模样,可以使用ColorMatrix颜色矩阵将图片渲染成灰色
4:添加了QQ好友消息头像闪动的功能
m_BuddyList.SetBuddyItemHeadFlashAnim(nTeamIndex,0,TRUE);
在说一下QQ主面板的桌面自动隐藏功能:
先说一下原理,当按住QQ主面板放在桌面大约相差20像素的位置,如果是左边或者右边,窗口会进行自动拉伸,如果是上面或者是下面,窗口不会拉伸大小,当鼠标离开窗口大约200毫秒,窗口会自动移动到窗口一边,同时在桌面的边缘留出大约2像素的边框,这个边框正是用来捕捉鼠标的,当窗口捕获到鼠标消息时,自动进行移动,显示窗口。
先看一下代码
#pragma once //窗口收缩类型 enum AFX_HIDE_TYPE { en_None = 0, //不收缩 en_Top, //向上收缩 en_Bottom, //向下收缩 en_Left, //向左收缩 en_Right //向右收缩 }; ////////////////////////////////////////////////////////////////////////// class CHideWindow { protected: bool m_bSized; //窗口大小是否改变 bool m_bTimed; //是否设置了检测鼠标的Timer INT m_nWindowHeight; //旧的窗口宽度 INT m_nTaskBarHeight; //任务栏高度 INT m_nEdgeHeight; //边缘高度 INT m_nEdgeWidth; //边缘宽度 AFX_HIDE_TYPE m_enHideType; //隐藏模式 bool m_bFinished; //隐藏或显示过程是否完成 bool m_bHiding; //该参数只有在!m_hsFinished才有效,真:正在隐藏,假:正在显示 HWND m_hOwnHwnd; public: CHideWindow(void); ~CHideWindow(void); //设置函数 public: //设置窗口属性 void SetHideWindow(HWND hWnd,int nEdgeHeight=3,int nEdgeWidth=3); //修正移动时窗口的大小 void FixMoving(UINT fwSide, LPRECT pRect); //修正改改变窗口大小时窗口的大小 void FixSizing(UINT fwSide, LPRECT pRect); //从收缩状态显示窗口 void ShowWindow(); //从显示状态收缩窗口 void HideWindow(); //计时器消息 void HideLoop( UINT nIDEvent ); //开始隐藏 void BeginHide(CPoint point); //重载函数 BOOL SetWindowPos(HWND hWndInsertAfter,LPCRECT pCRect, UINT nFlags = SWP_SHOWWINDOW); };
我们定义一个CHideWindow类,定义AFX_HIDE_TYPE枚举刚才鼠标拖动到桌面的四种位置,当我们按住窗口进行拖动时,我们响应WM_NCHITTEST消息,然后开启计时器,计时器的作用主要就是检测当前鼠标在窗口的位置,如果鼠标不在窗口上,就让他自动移动,隐藏起来,其次,我们需要窗口的WM_MOVING,通过这个消息,我们监听鼠标拖动窗口在桌面的位置,满足条件时,之前的计时器就会判断鼠标是否离开窗口,离开自然就启动第二个计时器(IDI_BEGINHIDE),实现慢慢移动的效果。到这里,自动拉伸隐藏就实现了,那么在说一下已经隐藏的窗口,我们怎么再让他显示出来的
void CHideWindow::HideLoop( UINT nIDEvent ) { if(nIDEvent == IDI_CHECKMOUSE ) { CPoint point; GetCursorPos(&point); CRect rcWindow; ::GetWindowRect(m_hOwnHwnd,rcWindow); rcWindow.InflateRect(INFALTE,INFALTE); if(!rcWindow.PtInRect(point)) { ::KillTimer(m_hOwnHwnd,IDI_CHECKMOUSE); m_bTimed = false; m_bFinished = false; m_bHiding = true; ::SetTimer(m_hOwnHwnd,IDI_BEGINHIDE,BH_ELAPSE,NULL); } ::InvalidateRect(m_hOwnHwnd,NULL,FALSE); } else if(nIDEvent == IDI_BEGINHIDE) { if(m_bFinished) ::KillTimer(m_hOwnHwnd,IDI_BEGINHIDE); else m_bHiding ? HideWindow() : ShowWindow(); ::InvalidateRect(m_hOwnHwnd,NULL,FALSE); } }
在看一下WM_NCHITTEST消息代码
void CHideWindow::BeginHide(CPoint point) { if( (m_enHideType != en_None) && !m_bTimed && (point.x < GetSystemMetrics(SM_CXSCREEN) + INFALTE)) { ::SetTimer(m_hOwnHwnd,IDI_CHECKMOUSE,CM_ELAPSE,NULL); m_bTimed = true; m_bFinished = false; m_bHiding = false; ::SetTimer(m_hOwnHwnd,IDI_BEGINHIDE,BH_ELAPSE,NULL); //开启显示过程 } }
如上,我们在WM_NCHITTEST消息中同时将两个计时器都打开,刚才我们也说了,IDI_BEGINHIDE主要是实现窗口移动的动画,我们只需要通过变量m_bHiding,根据计时器IDI_CHECKMOUSE的鼠标位置判断去设置m_bHiding的值,那么当逻辑走到计时器IDI_BEGINHIDE的时候,隐藏和显示的效果也就实现了
在说一下换肤,
先说一下界面的做法,下方显示的图片列表可不是什么控件,在这里也提醒了大家,有时候我们做东西的时候不一定非要纠结于用什么控件,当遇到控件不能实现的效果,我们完全可以去模拟出来,好,言归正传,我们先观察一下QQ的换肤,打开皮肤窗口,他会从网站下载最新皮肤数据,放在了我的账户的用户文件夹中,同时每个皮肤会附带一个xml配置文件,代码如下
<?xml version="1.0" encoding="utf-8" ?> <theme-config PackID="C148CE621F870A05B54AC459DDEAA5B0" PackPreview="preview.png" PackLogonPreview="logon_preview.png" PackRecentPreview="recent_preview.png" PackVersion="2.0" PackMinVer="2509" PackName="skin"> <backgroundlist> <background preview="bg_preview.png" mainfile="main.png" mainloc="topleft" aioloc="topleft" bkgstyle="stretch" color="ff1e7db9" /> </backgroundlist> </theme-config>
从代码中我们发现,里面有加载的图片名称,绘制图像的位置,绘制模式,可能最后的color你会发愣,这个是用来做什么的呢,当我们拉伸窗口大小时,如果窗口大于图片的尺寸,他会用这个color填充不能显示的区域,说白了也就是用color填充整个客户区,然后用绘制了该背景图片,代码中,我没有像QQ那样从配置文件说读取这些属性,当然你可以实现这个功能,其次,我并没有用color去填充背景,而是选择图片的一列像素进行的拉伸处理,其次,代码中最后三个皮肤,采用的是topleft绘制方式,上面的12中是righttop方式,绘制的方法详见代码吧。其次说一下重点,当我们打开这个皮肤设置窗口还有QQ会话聊天窗口,绘画的都是同一个模样,那么我们怎么将这些窗口关联起来的呢?
我们创建这些窗口的基类对话框CSkinManager,通过CSkinManager,我们派生出这些一个模样的窗口,然后创建一个vector,我们将所有窗口的窗口指针全部压到该vcetor中,创建vector的作用正是在换肤的时候,我们可以及时的通知所有窗口去更新背景,不过这里需要注意一点,我们之前将窗口的Clip Children设为了true如果只是单纯的调用窗口的Invalidate函数的话,背景是换掉了,但是由于剪辑掉了控件的位置,所以导致控件的旧背景仍然存在,解决办法相当接单,当我们点击换肤时,将Clip Children属性先设为false,刷新完界面后,我们在将Clip Children属性设为true即可,代码如下
void CSkinManager::UpdateSkin() { RenderEngine->RemoveImage(m_pImageBack); m_pImageBack = RenderEngine->GetImage(GlobalUnits->m_szDefaultSkin); for (int i=0;i<GlobalUnits->m_WindowArray.size();i++) { CSkinManager * pSkinManager = GlobalUnits->m_WindowArray.at(i); if (pSkinManager->GetSafeHwnd() == NULL ) continue; pSkinManager->ModifyStyle(WS_CLIPCHILDREN,0); pSkinManager->Invalidate(FALSE); pSkinManager->ModifyStyle(0,WS_CLIPCHILDREN); } }
关于QQ会话聊天窗口的创建
添加QQ好友列表双击事件响应消息
ON_NOTIFY(NM_DBLCLK, IDC_BUDDYLIST, &CQQDlg::OnBnClickOpenChat)
关于窗口的创建,我们发现,假如打开了A窗口,我们在双击A的话,他只会将该窗口置前而不是重新又创建出这么一个窗口来,所以我们采用vector来管理聊天窗口
void CQQDlg::OnBnClickOpenChat( NMHDR *pNMHDR, LRESULT *pResult ) { int nSelTeamIndex = m_BuddyList.GetSelTeamIndex(); int nSelIndex = m_BuddyList.GetSelIndex(); if ( m_BuddyList.IsBuddyItemHasMsg(nSelTeamIndex,nSelIndex) ) { m_BuddyList.SetBuddyItemHeadFlashAnim(nSelTeamIndex,nSelIndex,FALSE); } for (int i=0;i<GlobalUnits->m_ChatArray.size();i++) { CChatDlg * pChatDlg = GlobalUnits->m_ChatArray.at(i); if ( StrCmp(pChatDlg->m_pBuddyItem->m_strQQNum, m_BuddyList.GetBuddyItemQQNum(nSelTeamIndex,nSelIndex) ) == 0 ) { pChatDlg->ShowWindow(SW_SHOW); pChatDlg->SetForegroundWindow(); return; } } CChatDlg *pChatDlg = new CChatDlg; pChatDlg->Create(IDD_CHAT); pChatDlg->ShowWindow(SW_SHOW); pChatDlg->CenterWindow(GetDesktopWindow()); pChatDlg->m_pBuddyItem = m_BuddyList.GetBuddyItemByIndex(nSelTeamIndex,nSelIndex); GlobalUnits->m_ChatArray.push_back(pChatDlg); }
上面的代码,我们如果点击了闪动头像的聊天窗口,先取消闪动,其次,因为QQ号没有重复的,所以我们通过匹配QQ号的方式来查找是否存在当前窗口,当然方法还有很多,仅供参考。
明天我们继续完善QQ绘画聊天窗口
代码下载地址:http://pan.baidu.com/s/1quc4x