当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。
若是你以为还不错,请粉我,顺便再顶一下啊,呵呵