zoukankan      html  css  js  c++  java
  • QQ揭秘:如何实现托盘闪动消息提醒?【低调赠送:QQ高仿版GG 4.1 最新源码】

      当QQ收到好友的消息时,托盘的图标会变成好友的头像,并闪动起来,点击托盘,就会弹出与好友的聊天框,随即,托盘恢复成QQ的图标,不再闪动。当然,如果还有其它的好友的消息没有提取,托盘的图标会变成另一个好友的图标,并继续闪动。那么,QQ的这一效果是如何实现的了?我在QQ高仿GG2014中实现了同样的效果,这里我就详细地介绍一下。另外,文末最后会奉上GG最新版本4.1的源码,这次甚至包含了JustLib项目的源码哦!

          想要直接下载体验的朋友请点击:“下载中心”

    一.TwinkleNotifyIcon的实现原理

      这个会闪动的托盘图标,我将其定义为一个组件TwinkleNotifyIcon,我们先看TwinkleNotifyIcon的类图:

      

       从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 源码 

       下载最新版本,请转到这里。 

       

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

    大家有什么问题和建议,可以留言,也可以发送email到我邮箱:ggim2013@163.com。 

    如果你觉得还不错,请粉我,顺便再顶一下啊,呵呵  

  • 相关阅读:
    【C】——sigprocmask 阻塞进程信号
    【C】——setjmp练习
    【程序练习】——交换两数组元素,使之和差最小
    【C】——setvbuf(scanf内存溢出问题)
    【C】——APUE小程序之递归遍历目录
    Java的原始类型(Primitive Type)
    类加载 静态加载
    行政拘留不属于行政强制措施
    行政立法主体
    行政法中三大具体行政行为
  • 原文地址:https://www.cnblogs.com/justnow/p/4195142.html
Copyright © 2011-2022 走看看