當QQ收到好友的消息時,托盤的圖標會變成好友的頭像,並閃動起來,點擊托盤,就會彈出與好友的聊天框,隨即,托盤恢復成QQ的圖標,再也不閃動。固然,若是還有其它的好友的消息沒有提取,托盤的圖標會變成另外一個好友的圖標,並繼續閃動。那麼,QQ的這一效果是如何實現的了?我在QQ高仿GG2014中實現了一樣的效果,這裏我就詳細地介紹一下。另外,文末最後會奉上GG最新版本4.1的源碼,此次甚至包含了JustLib項目的源碼哦!html
想要直接下載體驗的朋友請點擊:「下載中心」網絡
這個會閃動的托盤圖標,我將其定義爲一個組件TwinkleNotifyIcon,咱們先看TwinkleNotifyIcon的類圖:函數
從TwinkleNotifyIcon類圖,咱們已經能夠看出大體的實現方案:this
(1)TwinkleNotifyIcon 內部使用了NotifyIcon,以顯示在右下角的托盤。spa
(2)使用一個Timer定時器來控制托盤的閃動。code
(3)使用一個隊列friendQueue來存放待提取的好友消息。orm
(4)使用另外一個隊列groupQueue來存放待提取的羣消息。htm
(5)當網絡引擎接收到一個好友/羣消息時,咱們就調用PushFriendMessage/PushGroupMessage方法,將其壓入friendQueue/groupQueue,並開始閃動圖標。blog
(6)當托盤被點擊時,就從Queue中提取最先的一個消息,並將其交給對應的聊天窗口去處理。隊列
咱們順着如下的順序來研究TwinkleNotifyIcon的實現代碼,就很容易了:
(1)壓入好友/羣消息。
(2)點擊托盤,提取消息。
(3)從新判斷Queue中是否還有待提取的消息,以設置托盤的狀態。
咱們以PushFriendMessage方法爲例,PushGroupMessage的道理是同樣的。
public void PushFriendMessage(string userID, int informationType, byte[] info, object tag) { lock (this.locker) { try { this.twinkleNotifySupporter.PlayAudioAsyn(); //播放消息提示音 //首先查看是否已經存在對應的聊天窗口 IChatForm form = this.twinkleNotifySupporter.GetExistedChatForm(userID); if (form != null) { form.HandleReceivedMessage(informationType, info, tag); return; } //接下來準備將消息壓入queue UnhandleFriendMessageBox cache = null; lock (this.locker) { //先查看queue中目標好友對應的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); //觸發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); //獲取好友的頭像,將其做爲托盤圖標 this.twinkleIcon = this.twinkleNotifySupporter.GetHeadIcon(userID); this.ControlTimer(true); //啓動閃爍 } catch (Exception ee) { MessageBox.Show(ee.Message); } } }
(1)在壓入消息的時候,先要播放消息提示音,以通知使用者收到了新的消息。
(2)首先要判斷消息的來源好友是否正在和本身聊天(已經打開了與對方的聊天窗口),若是是,則直接將消息交給聊天窗口去顯示。不然,進入下一步。
(3)看當前的隊列中是否已經存在了目標好友的Cache,由於可能已經有了待提取的來自該好友的消息了。若是已經存在這個Cache,則將消息直接放入Cache,不然,爲之新建一個,再放入。
(4)之因此要觸發UnhandleMessageOccured事件,是爲了通知外面,有待提取的消息出現了。好比,MainForm就會預約這個消息,而後使得好友列表中對應的頭像閃動。
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事件處理函數,不單單使得好友列表中對應的頭像閃動,即便是最近聯繫人中,若是存在目標好友,也會使其頭像閃動。
(5)將托盤圖標設置爲目標好友的頭像,並將ToolTip設置爲好友的名稱及待提取的消息數量。
(6)使用定時器閃動圖標。
若是托盤正在閃動,代表有待提取的消息,此時點擊托盤,將提取隊列中最先壓入的好友的消息。
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)從上面代碼執行的順序來看,是優先提取好友消息,當全部的好友消息提取完後,才提取羣消息。
(2)提取消息時,會調用twinkleNotifySupporter的GetChatForm方法來建立與目標好友的聊天窗口,並將提取的消息交給這個窗口去處理。
(3)當一個好友的消息被提取後,會觸發UnhandleMessageGone事件,以通知外部消息已經被提取了。好比,MainForm就會預約這個消息,而後使得好友列表中對應的頭像再也不閃動。
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)同時,會從新掃描隊列中待提取消息的情況,重設托盤圖標的狀態,這就是DetectUnhandleMessage方法作的事情。
每當有消息被提取後,咱們都須要從新掃描Queue中是否還有其它的待提取消息,DetectUnhandleMessage方法會被常常調用。
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當中都沒有任何消息了,則再也不閃動托盤圖標。
(2)再依次掃描好友消息的Queue和羣消息的Queue,若是發現還有待提取的消息,則設置托盤圖標的圖像,設置ToolTip,並開始閃動圖標。
下載最新版本,請轉到這裏。
歡迎和我探討關於GG的一切,個人QQ:2027224508,多多交流!
你們有什麼問題和建議,能夠留言,也能夠發送email到我郵箱:ggim2013@163.com。
若是你以爲還不錯,請粉我,順便再頂一下啊,呵呵