天天看點

QQ揭秘:如何實作托盤閃動消息提醒?【低調贈送:QQ高仿版GG 4.1 最新源碼】

當QQ收到好友的消息時,托盤的圖示會變成好友的頭像,并閃動起來,點選托盤,就會彈出與好友的聊天框,随即,托盤恢複成QQ的圖示,不再閃動。當然,如果還有其它的好友的消息沒有提取,托盤的圖示會變成另一個好友的圖示,并繼續閃動。那麼,QQ的這一效果是如何實作的了?本文将為你詳細解密。

  當QQ收到好友的消息時,托盤的圖示會變成好友的頭像,并閃動起來,點選托盤,就會彈出與好友的聊天框,随即,托盤恢複成QQ的圖示,不再閃動。當然,如果還有其它的好友的消息沒有提取,托盤的圖示會變成另一個好友的圖示,并繼續閃動。那麼,QQ的這一效果是如何實作的了?我在QQ高仿GG2014中實作了同樣的效果,這裡我就詳細地介紹一下。另外,文末最後會奉上GG最新版本4.1的源碼,這次甚至包含了JustLib項目的源碼哦!

      想要直接下載下傳體驗的朋友請點選:“下載下傳中心”

一.TwinkleNotifyIcon的實作原理

  這個會閃動的托盤圖示,我将其定義為一個元件TwinkleNotifyIcon,我們先看TwinkleNotifyIcon的類圖:

  

QQ揭秘:如何實作托盤閃動消息提醒?【低調贈送:QQ高仿版GG 4.1 最新源碼】

   從TwinkleNotifyIcon類圖,我們已經可以看出大緻的實作方案:

(1)TwinkleNotifyIcon 内部使用了NotifyIcon,以顯示在右下角的托盤。

(2)使用一個Timer定時器來控制托盤的閃動。

(3)使用一個隊列friendQueue來存放待提取的好友消息。

(4)使用另一個隊列groupQueue來存放待提取的群消息。

(5)當網絡引擎接收到一個好友/群消息時,我們就調用PushFriendMessage/PushGroupMessage方法,将其壓入friendQueue/groupQueue,并開始閃動圖示。

(6)當托盤被點選時,就從Queue中提取最早的一個消息,并将其交給對應的聊天視窗去處理。

二.TwinkleNotifyIcon 實作要點

  我們順着以下的順序來研究TwinkleNotifyIcon的實作代碼,就很容易了:

(1)壓入好友/群消息。

(2)點選托盤,提取消息。

(3)重新判斷Queue中是否還有待提取的消息,以設定托盤的狀态。

1. 壓入消息

  我們以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)使用定時器閃動圖示。

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)從上面代碼執行的順序來看,是優先提取好友消息,當所有的好友消息提取完後,才提取群消息。

(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方法做的事情。

3.重新判斷Queue中是否還有待提取的消息,以設定托盤的狀态

  每當有消息被提取後,我們都需要重新掃描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 V4.1 源碼 

   下載下傳最新版本,請轉到這裡。 

   

QQ揭秘:如何實作托盤閃動消息提醒?【低調贈送:QQ高仿版GG 4.1 最新源碼】

歡迎和我探讨關于GG的一切,我的QQ:2027224508,多多交流!  

大家有什麼問題和建議,可以留言,也可以發送email到我郵箱:[email protected]。 

如果你覺得還不錯,請粉我,順便再頂一下啊,呵呵  

繼續閱讀