==========================================================================
補充:
(1)為了在預覽圖中顯示動态的 GIF 圖檔,我舍棄了 CImage, 而引用了 CxImage 類庫的代碼。這樣可以看到動态的 GIF 動畫。
(2)改進了繪制方法,改為使用記憶體位圖繪制,并把 WM_ERASEBKGND 合并到 WM_PAINT 中,以降低閃爍。
(3)修改了滑鼠位于表情網格(Cells)的最右邊緣時,錯誤的認為選中表情是下一行的第一個 Cell 的BUG。該 BUG 會導緻滑鼠在
最右側移入移出時在右上角有強烈閃爍感。
--hoodlum1980 2010年1月29日
最近有好多篇文章是關于QQ的選擇表情的那個視窗實作的。最初以為這是一個比較簡單的功能,不過做起來還是發現做了整個一晚上才做的差不多做好。做完我想為什麼有很多人願意嘗試它呢?可能是基于它的那個“預覽圖”對滑鼠的“躲藏”功能是它的一個亮點,是以可能是因為這個原因引起很多人的興趣來實作它。前人已經采用了很多種方式實作,例如 C# 的 winform,等等。在這裡是使用 VC 實作的。在這個範例中也展示了如何定制一個特殊外觀的視窗,以及自定義繪制(OWNER DRAW)按鈕(1/2,上一頁,下一頁按鈕),如何周遊一個檔案夾下的所有檔案等基本方法。
不過這個例子僅僅是模仿QQ的UI,實際上這也是這個例子的唯一意義。由于VC裡面對于存在多幀的 gif 格式并沒有特别好的支援,包括 CImage 盡管支援大多數圖像格式,但是卻僅能加載 gif 的一幀,而要展示動畫形式的gif,則通常要求助于第三方代碼,例如 CxImage 等。是以這裡我就僅僅模拟這個界面,實際上在預覽框裡面我僅僅繪制一下而已。而并沒有去真正的做 gif 動畫的預覽。
當滑鼠在視窗上移動時,需要繪制一個藍色小邊框反應目前選中的是哪一個cell,而且預覽圖會盡可能惰性的“遠離”滑鼠。所謂惰性,是指僅在它發覺滑鼠有向它逼近時,它的位置才會突變。例如當預覽框位于左上角(跨度占據0,1,2列),而滑鼠從右側首次進入第4列時, 它是不動的,但滑鼠在第4列繼續移動時,它才會突然移動到右側。是以為了檢測滑鼠的這種行為,實際上在範例中我記錄了滑鼠最近兩次的停留位置,即 lastSelection,currentSelection(二者的邏輯關系實際上是一個僅包含兩個元素的隊列)。
效果如下圖所示:

這個範例在UI方面基本是采用對QQ2009的表情選擇視窗的截圖來制作的, 使用的表情檔案夾是來自于qq2008的 “Face2” 。但該範例還是和真正的QQ有一些出入的地方,例如:
(1)QQ的表情選擇視窗彈出來時,qq表情按鈕是呈現按下狀态的。範例為了簡單期間,隻是彈出模态對話框,是以這裡不如QQ的 UI 自定義的那麼徹底。
(2)QQ的表情選擇視窗和其父視窗是非模态性的關系,而範例是模态關系,是以在沒有加載任何圖檔的情況下,為了使視窗能夠關閉,我做了一點特殊處理,也就是在範例中可以通過點選“空白處”的表情退出,而在QQ中點選空白位置是沒有回應的。
(3)QQ的表情選擇視窗有Tooltip,不過我不覺得這個功能很必要,估計很少會有人去關注這個功能,而且它還擋住了視窗的一部分。實作起來是很簡單的,是以範例中沒有tooltip了。
(4)QQ的表情選擇視窗能自動調節自己的出現位置,盡可能靠近觸發按鈕,并且保證自身的可見性(完全位于螢幕範圍以内)。操作中很多彈出式的視窗具有這種功能,例如 ToolTip , ComboBox, DateTimePicker 類控件的下拉視窗等。這個功能看起來簡單,但是實作起來還是需要一定技巧和複雜度。範例中僅僅讓視窗的位置固定出現在按鈕上方。
(5)QQ的表情選擇視窗對使用者自己添加的表情在繪制時,使用了拉伸繪制。在範例中為了簡單,沒有考慮表情圖檔的大小。
本範例使用的代碼都屬于比較傳統的技術,是以并沒有什麼特别需要單獨列出和講解的地方。由于最近該類實作的文章較多,是以将本文發于首頁候選區。
在這裡僅提供本範例的源代碼下載下傳連接配接:(可執行程式位于Debug目錄中)
--hoodlum1980
參考資料:
【1】 本文代碼中,關于 GIF 圖檔的解碼和展示引用了 CxImage 中的代碼。關于 CxImage 的介紹位址:
【補充:】2013年12月 ,幫網友寫的一個 DEMO (含源代碼)。(采用 VC6.0 和 MFC 為基礎開發)。
說明:
(1)選擇的表情的 FullPath 前面加了 “file:///" 。可以直接用在 html 代碼裡了。
(2)增加了 ToolTip 提示,但是現在隻是展示了檔案名和幀數量。如果要添加其他提示資訊,則需要增加其他資料檔案。(例如,使用一個 ini 檔案提供每個檔案的中文提示文本)。
(3)用法更簡單了,隻需要添加 CEmotionWnd.h/cpp; 添加 CxImage 檔案夾。(項目屬性中設定為 不使用 precompiled header)。
細節:
(a)添加 #include "EmotionWnd.h"
(b)添加成員變量:CEmotionWnd *m_pEmotionWnd;
(c)初始化:
this->m_pEmotionWnd = new CEmotionWnd();
//第一個參數是接收 WM_NOTIFY 通知消息的視窗( CWnd* );
//第二個參數是為表情選擇視窗設定一個唯一的ID (int),能和其他控件的 ID 區分開即可。
this->m_pEmotionWnd->CreateWnd(this, IDC_EMOTIONWND);
//從某個檔案夾下面添加所有 GIF 格式的檔案;
this->m_pEmotionWnd->LoadEmotions(_T("E:\\Face"));
(d)點選表情按鈕時,彈出表情選擇視窗(如果在對話框位于螢幕最右側,可自動移回螢幕範圍内):
//獲得“表情”按鈕的螢幕坐标(IDC_SEL_EMOTION 為表情按鈕的 ID)
CWnd *pBtn = this->GetDlgItem(IDC_SEL_EMOTION);
RECT rc;
pBtn->GetWindowRect(&rc);
this->m_pEmotionWnd->ShowOrHide(rc.left, rc.top);
(e)處理 WM_NOTIFY 得到使用者選擇的表情的完整路徑(前面已經添加 "file:///");
(f)在不再需要時,銷毀表情視窗,(例如主視窗的 OnDestroy):
this->m_pEmotionWnd->DestroyWindow();
delete this->m_pEmotionWnd;
this->m_pEmotionWnd = NULL;
-- hoodlum1980, ON 2013年12月12日。