QQ揭秘:如何實現(xiàn)托盤閃動消息提醒?
當(dāng)前位置:點晴教程→知識管理交流
→『 技術(shù)文檔交流 』
當(dāng)QQ收到好友的消息時,托盤的圖標(biāo)會變成好友的頭像,并閃動起來,點擊托盤,就會彈出與好友的聊天框,隨即,托盤恢復(fù)成QQ的圖標(biāo),不再閃動。當(dāng)然,如果還有其它的好友的消息沒有提取,托盤的圖標(biāo)會變成另一個好友的圖標(biāo),并繼續(xù)閃動。那么,QQ的這一效果是如何實現(xiàn)的了?我在QQ高仿GG2014中實現(xiàn)了同樣的效果,這里我就詳細地介紹一下。另外,文末最后會奉上GG最新版本4.1的源碼,這次甚至包含了JustLib項目的源碼哦! 想要直接下載體驗的朋友請點擊:“下載中心” 一.TwinkleNotifyIcon的實現(xiàn)原理這個會閃動的托盤圖標(biāo),我將其定義為一個組件TwinkleNotifyIcon,我們先看TwinkleNotifyIcon的類圖:
從TwinkleNotifyIcon類圖,我們已經(jīng)可以看出大致的實現(xiàn)方案: (1)TwinkleNotifyIcon 內(nèi)部使用了NotifyIcon,以顯示在右下角的托盤。 (2)使用一個Timer定時器來控制托盤的閃動。 (3)使用一個隊列friendQueue來存放待提取的好友消息。 (4)使用另一個隊列g(shù)roupQueue來存放待提取的群消息。 (5)當(dāng)網(wǎng)絡(luò)引擎接收到一個好友/群消息時,我們就調(diào)用PushFriendMessage/PushGroupMessage方法,將其壓入friendQueue/groupQueue,并開始閃動圖標(biāo)。 (6)當(dāng)托盤被點擊時,就從Queue中提取最早的一個消息,并將其交給對應(yīng)的聊天窗口去處理。 二.TwinkleNotifyIcon 實現(xiàn)要點我們順著以下的順序來研究TwinkleNotifyIcon的實現(xiàn)代碼,就很容易了: (1)壓入好友/群消息。 (2)點擊托盤,提取消息。 (3)重新判斷Queue中是否還有待提取的消息,以設(shè)置托盤的狀態(tài)。 1. 壓入消息我們以PushFriendMessage方法為例,PushGroupMessage的道理是一樣的。 public void PushFriendMessage(string userID, int informationType, byte[] info, object tag) { lock (this.locker) { try { this.twinkleNotifySupporter.PlayAudioAsyn(); //播放消息提示音 //首先查看是否已經(jīng)存在對應(yīng)的聊天窗口 IChatForm form = this.twinkleNotifySupporter.GetExistedChatForm(userID); if (form != null) { form.HandleReceivedMessage(informationType, info, tag); return; } //接下來準(zhǔn)備將消息壓入queue UnhandleFriendMessageBox cache = null; lock (this.locker) { //先查看queue中目標(biāo)好友對應(yīng)的Cache是否存在 for (int i = 0; i < this.friendQueue.Count; i++) { if (this.friendQueue[i].User == userID) { cache = this.friendQueue[i]; break; } } if (cache == null) //如果不存在,則為好友新建一個Cache { cache = new UnhandleFriendMessageBox(userID); this.friendQueue.Add(cache); //觸發(fā)UnhandleMessageOccured事件 if (this.UnhandleMessageOccured != null) { this.UnhandleMessageOccured(UnhandleMessageType.Friend, userID); } } cache.MessageList.Add(new Parameter<int, byte[], object>(informationType, info, tag)); } string userName = this.twinkleNotifySupporter.GetFriendName(userID); this.notifyIcon1.Text = string.Format("{0}({1}) {2}條消息", userName, userID, cache.MessageList.Count); //獲取好友的頭像,將其作為托盤圖標(biāo) this.twinkleIcon = this.twinkleNotifySupporter.GetHeadIcon(userID); this.ControlTimer(true); //啟動閃爍 } catch (Exception ee) { MessageBox.Show(ee.Message); } } } (1)在壓入消息的時候,先要播放消息提示音,以通知使用者收到了新的消息。 (2)首先要判斷消息的來源好友是否正在和自己聊天(已經(jīng)打開了與對方的聊天窗口),如果是,則直接將消息交給聊天窗口去顯示。否則,進入下一步。 (3)看當(dāng)前的隊列中是否已經(jīng)存在了目標(biāo)好友的Cache,因為可能已經(jīng)有了待提取的來自該好友的消息了。如果已經(jīng)存在這個Cache,則將消息直接放入Cache,否則,為之新建一個,再放入。 (4)之所以要觸發(fā)UnhandleMessageOccured事件,是為了通知外面,有待提取的消息出現(xiàn)了。比如,MainForm就會預(yù)定這個消息,然后使得好友列表中對應(yīng)的頭像閃動。 void notifyIcon_UnhandleMessageOccured(UnhandleMessageType type, string friendOrGroupID) { if (type == UnhandleMessageType.Friend) { this.friendListBox1.SetTwinkleState(friendOrGroupID, true); this.recentListBox1.SetTwinkleState(friendOrGroupID, false, true); return; } if (type == UnhandleMessageType.Group) { this.groupListBox.SetTwinkleState(friendOrGroupID, true); this.recentListBox1.SetTwinkleState(friendOrGroupID, true, true); return; } } 上面的UnhandleMessageOccured事件處理函數(shù),不僅僅使得好友列表中對應(yīng)的頭像閃動,即使是最近聯(lián)系人中,如果存在目標(biāo)好友,也會使其頭像閃動。 (5)將托盤圖標(biāo)設(shè)置為目標(biāo)好友的頭像,并將ToolTip設(shè)置為好友的名稱及待提取的消息數(shù)量。 (6)使用定時器閃動圖標(biāo)。 2.點擊托盤,提取消息如果托盤正在閃動,表明有待提取的消息,此時點擊托盤,將提取隊列中最早壓入的好友的消息。 void notifyIcon1_MouseClick(object sender, MouseEventArgs e) { try { if (e.Button != MouseButtons.Left) { return; } lock (this.locker) { if (this.friendQueue.Count > 0) { UnhandleFriendMessageBox cache = this.friendQueue[0]; this.friendQueue.RemoveAt(0); IChatForm form = this.twinkleNotifySupporter.GetChatForm(cache.User); if (form != null) //如果為null,表示剛刪除好友 { form.HandleReceivedMessage(cache.MessageList); } this.DetectUnhandleMessage(); if (this.UnhandleMessageGone != null) { this.UnhandleMessageGone(UnhandleMessageType.Friend, cache.User); } return; } if (this.groupQueue.Count > 0) { UnhandleGroupMessageBox cache = this.groupQueue[0]; this.groupQueue.RemoveAt(0); IGroupChatForm form = this.twinkleNotifySupporter.GetGroupChatForm(cache.Group); form.HandleReceivedMessage(cache.MessageList); this.DetectUnhandleMessage(); if (this.UnhandleMessageGone != null) { this.UnhandleMessageGone(UnhandleMessageType.Group, cache.Group); } return; } } if (this.MouseClick != null) { this.MouseClick(sender, e); } } catch (Exception ee) { MessageBox.Show(ee.Message + " - " + ee.StackTrace); } } (1)從上面代碼執(zhí)行的順序來看,是優(yōu)先提取好友消息,當(dāng)所有的好友消息提取完后,才提取群消息。 (2)提取消息時,會調(diào)用twinkleNotifySupporter的GetChatForm方法來創(chuàng)建與目標(biāo)好友的聊天窗口,并將提取的消息交給這個窗口去處理。 (3)當(dāng)一個好友的消息被提取后,會觸發(fā)UnhandleMessageGone事件,以通知外部消息已經(jīng)被提取了。比如,MainForm就會預(yù)定這個消息,然后使得好友列表中對應(yīng)的頭像不再閃動。 void notifyIcon_UnhandleMessageGone(UnhandleMessageType type, string friendOrGroupID) { if (type == UnhandleMessageType.Friend) { this.friendListBox1.SetTwinkleState(friendOrGroupID, false); this.recentListBox1.SetTwinkleState(friendOrGroupID, false, false); return; } if (type == UnhandleMessageType.Group) { this.groupListBox.SetTwinkleState(friendOrGroupID, false); this.recentListBox1.SetTwinkleState(friendOrGroupID, true, false); return; } } (4)同時,會重新掃描隊列中待提取消息的狀況,重設(shè)托盤圖標(biāo)的狀態(tài),這就是DetectUnhandleMessage方法做的事情。 3.重新判斷Queue中是否還有待提取的消息,以設(shè)置托盤的狀態(tài)每當(dāng)有消息被提取后,我們都需要重新掃描Queue中是否還有其它的待提取消息,DetectUnhandleMessage方法會被經(jīng)常調(diào)用。 private void DetectUnhandleMessage() { if (this.friendQueue.Count == 0 && this.groupQueue.Count == 0) { this.ControlTimer(false); } else if (this.friendQueue.Count > 0) { UnhandleFriendMessageBox cache = this.friendQueue[0]; string userName = this.twinkleNotifySupporter.GetFriendName(cache.User); this.notifyIcon1.Text = string.Format("{0}({1}) {2}條消息", cache.User, userName, cache.MessageList.Count); this.twinkleIcon = this.twinkleNotifySupporter.GetHeadIcon(cache.User); } else { UnhandleGroupMessageBox cache = this.groupQueue[0]; string groupName = this.twinkleNotifySupporter.GetGroupName(cache.Group); this.notifyIcon1.Text = string.Format("{0}({1}) {2}條消息", groupName, cache.Group, cache.MessageList.Count); this.twinkleIcon = this.twinkleNotifySupporter.GroupIcon; } } (1)如果好友消息的Queue和群消息的Queue當(dāng)中都沒有任何消息了,則不再閃動托盤圖標(biāo)。 (2)再依次掃描好友消息的Queue和群消息的Queue,如果發(fā)現(xiàn)還有待提取的消息,則設(shè)置托盤圖標(biāo)的圖像,設(shè)置ToolTip,并開始閃動圖標(biāo)。 三.GG V4.1 源碼下載最新版本,請轉(zhuǎn)到這里。
該文章在 2017/2/7 18:23:00 編輯過 |
關(guān)鍵字查詢
相關(guān)文章
正在查詢... |