一种清除windows通知区域“僵尸”图标的方案——问题分析

原创 2013年12月09日 01:01:44

通知区域名称有趣的历史

        假如说到windows通知区域,可能很多人还是不清楚它是什么。如果改称Tray区域,可能有人就懂了。如果再白话点,叫它“托盘”或者“系统托盘”,可能会有更多的人猜到它是windows什么部位。现在我们揭开它真实的面纱,以windows7系统为例,下图就是它的通知区域。(转载请指明出于breaksoftware的csdn博客)


        其实,我们叫通知区域为“托盘”或者“系统托盘”是错误的。这个错误并非来源于中文翻译,而是来源于windows发展史上人们对其错误的认识。后来,这个命名也影响了中国一批程序员。我这儿要摘录一个微软老员工的回忆录《The Old New Thing》(中文名《windows编程启示录》)一书中关于这个错误认识起源的一段,还是蛮有意思的。

        “后来,我们将通知图标添加到任务栏中。”

        “我认为人们开始将通知区域叫作系统托盘是因为在Windows95中包含了一个systray.exe的程序,这个程序在通知区域中显示了一些图标,如音量控制,PCMCIA(在当时是叫这个名字)的状态、电池的电量表等。如果你终止了systray.exe,那么这些通知图标也将会消失。因此人们就认为,‘啊,systray程序一定是管理这些图标的组件,我敢打赌这个组件的名字就叫作“系统托盘”’。于是这个误解就形成了,而我们这十几年来一直都在努力澄清这个误解。”

        “更糟糕的是,其他的团队(Shell之外的团队)也错误地使用了这个词,并且开始在他们自己的文档和示例程序里面都使用了系统托盘这个词,其中有一些地方甚至错误地声称系统托盘就是通知区域的正式名称。”

        “有人可能会问,‘你为什么要关心这个名字的正误?既然现在所有的人都叫这个名字,你也可以随波逐流嘛。’”

        “如果每个人都叫错了你的名字,你会乐意吗?”

        其实我觉得,如果微软真的想彻底摒弃“系统托盘”这个名称,最好是从现在做起,将通知区域的一些信息都修改成和Tray这个单词无关。可是,我们使用Spy++查看Windows7任务栏的组成时就会发现,Tray这个单词无处不在啊!



“僵尸图标”

        说了这么多历史故事,我们再回到我们这篇博文要讲述的问题上。其实这个问题,依旧是个历史问题。还好,我发现vista之后的系统上,微软已经意识并修复了这个设计缺陷。我们看下下面的场景

        

        很多使用Windows的人可能都遇到过这个问题:通知区域出现了N个相同的“僵尸”图标。如果我们有意或者无意让光标划过这些图标时,这些图标会悄然消失。我们对这种现象,往往是疑惑一下就抛之脑后。然而,目前我在项目中就接到一个需求:把这些“僵尸”图标自动消失。出于我们产品的设计,我们存在出现这么多“僵尸”图标的场景,于是为了优化用户体验,我需要找到一种方法去解决这种体验问题。


通知区域图标的正常生死过程

        首先要分析一下这个问题出现的原因。一般来说,一个程序在创建时,可能会在通知区域创建一个图标。

一般初始化图标

        创建图标之前,我们需要初始化一个图标

NOTIFYICONDATA m_NotifyIcon;
……
m_NotifyIcon.cbSize = sizeof(m_NotifyIcon);
m_NotifyIcon.uFlags = NIF_ICON | NIF_TIP;
m_NotifyIcon.uVersion = NOTIFYICON_VERSION; // xp
m_NotifyIcon.hWnd = m_hWnd;
m_NotifyIcon.hIcon = m_hIcon;
std::wstring wstrInfo = L"中A英1文"; // 故意取一个晦涩的名字
wmemcpy_s(m_NotifyIcon.szTip, ARRAYSIZE(m_NotifyIcon.szTip), wstrInfo.c_str(), wstrInfo.length()+1 );

       这个地方需要注意的是下面几个参数:

  1. uFlags。我们只是设置了NIF_ICON和NIF_TIP,因为我们需要让我们的通知区域图标变得与众不同,故通过指定这两个标志分别告知系统:我们要设定图标和Tip文字。这个属性我们会在处理Windows7系统上“僵尸”图标的时候再次提起。
  2. hWnd。因为我们图标要相应用户的点击,并将相应消息传递给我们主窗口,所以我们此时要绑定主窗口句柄。这个属性我们会在未来介绍一个特定场景时再次提到。
  3. szTip。我们故意给我们这个图标取了一个晦涩的Tip,这样我们在之后查找“僵尸图标”时将有据可凭。

图标添加到通知区域

        图标初始化后,我们要将图标增加到通知区域
Shell_NotifyIcon(NIM_ADD, &m_NotifyIcon);

        这个图标是可以表明“这个进程还活着”;而且在无界面展现时,让用户方便唤起界面或者执行相应的功能。比如QQ的通知区域图标,它的存在表明QQ进程还是存在的。我们可以左键双击之,可以让主界面展现出来;还可以右击之,可以出现很多快捷功能键


图标从通知区域剔除        

        相应的,如果进程退出,应该通知系统通知区域:要将我设置的通知区域图标删除,因为我马上要退出了。

Shell_NotifyIcon(NIM_DELETE, &m_NotifyIcon);

        如果一切都如此按照规律的“正常生死”,也就没有之前提出的问题。可是,出于策略考虑以及一些异常情况,进程的意外死亡还是不可避免的。这样,如果出现连续的意外死亡场景,系统通知区域就会残留很多“僵尸”图标。为了大战这些“僵尸”,我们需要找到这些“僵尸”的家,然后对“僵尸”各个击破。于是,我们要看下各系统下通知区域的树状结构图。

XP、Win7下通知区域的结构

        先使用SPY++看下XP下任务栏即通知区域的结构

      #32769 (桌面)
        - Shell_TrayWnd
          - Button
          - TrayNotifyWnd
            - TrayClockWClass
            - SysPager
                - ToolbarWindow32(我们关心的,其直接显示在桌面上)
            - Button
              - CiceroUIWndFrame
              - MSTaskSwWClass
                - ToolbarWindow32
          - ReBarWindow32

        SysPager下类名为ToolbarWindow32的控件就是系统通知区域。非常庆幸,XP下只有这么一个通知区域,而且这个通知区域一直是可见的(Win7下有个不可见的通知区域)。

        再看下Win7的通知区域结构

#32769 (桌面)
  - Shell_TrayWnd
    - TrayNotifyWnd
      - TrayClockWClass
      - TrayShowDesktopButtonWClass
      - SysPager
        - ToolbarWindow32(我们关心的,其直接显示在桌面上)
      - ToolbarWindow32(其隐藏在桌面上,通过SendTimeout发送TB_BUTTONCOUNT不能获取其个数)
      - Button
    - ReBarWindow32
      - CiceroUIWndFrame
      - MSTaskSwWClass
        - MSTaskListWClass
        Win7的通知区域相对于XP有点复杂,其中我们一直可见的通知区域的树状结构和XP上是一致的。但是Win7上多出了一个隐藏的通知区域,它和SysPager同级


        针对XP和Win7上都可见的通知区域,我们可以通过如下代码找到相应区域去清理

VOID CKillRunProcessDlg::VisitNotificationArea()
{
    HWND hwndChildAfter = NULL;
    DWORD dwMaxLoopCount = MAXLOOPCOUNT;
    do {
        // 保守性编程,防止死循环
        dwMaxLoopCount--;
        HWND hTrayWnd = NULL;
        hTrayWnd = ::FindWindowEx( NULL, hwndChildAfter, L"Shell_TrayWnd", NULL);
        if ( NULL == hTrayWnd ) {
            break;
        }

        // 找到了窗口类为 Shell_TrayWnd的窗口,
        // 但是不保证找到的就是Notification所在区域的,
        // 所以记录下当前找到的,之后继续找
        hwndChildAfter = hTrayWnd;

        HWND hTrayNotifyWnd = ::FindWindowEx(hTrayWnd, NULL, L"TrayNotifyWnd", NULL );
        if ( NULL == hTrayNotifyWnd ) {
            // 继续找符合条件的Shell_TrayWnd类,然后再在其下找类为TrayNotifyWnd的子窗口
            continue;
        }

        // 这个窗口只能在Win7系统中可以找到
        HWND hToolBar32Ex = ::FindWindowEx( hTrayNotifyWnd, NULL, L"ToolbarWindow32", NULL );
        
        // 在win7 xp下都可以找到该树结构
        HWND hSysPager = ::FindWindowEx( hTrayNotifyWnd, NULL, L"SysPager", NULL );
        HWND hToolBar32Showed = NULL;
        if ( NULL != hSysPager ) {  
            hToolBar32Showed = ::FindWindowEx( hSysPager, NULL, L"ToolbarWindow32", NULL );
        }
        else {
            // 找不到该树结构
            // 则继续找符合条件的Shell_TrayWnd类,然后再在其下找类为TrayNotifyWnd的子窗口
            continue;;
        }

        if ( m_bVistaLater ) {
            if ( NULL == hToolBar32Showed || NULL == hToolBar32Ex ) {
                // 都要有,否则不是合法的
                continue;
            }
        }
        else {
            if ( NULL == hToolBar32Showed ) {
                continue;
            }
        }

        if ( FALSE == IsExplorerProcess(hToolBar32Showed) ) {
            if ( ERROR_NOT_FOUND != ::GetLastError() ) {
                // 不是Explorer进程,则继续寻找
                continue;
            }
        }

        if ( NULL != hToolBar32Showed ) {
            // 清理通知区域
            CleareIcons(hToolBar32Showed);
        }

    } while ( dwMaxLoopCount > 0 );
}

        鉴于XP的通知区域的结构简单性,我决定先从XP系统入手。其实XP上的解决方案是多种的,也是非常有意思的。详细的分析过程可以参看下篇博文《一种清除windows通知区域“僵尸”图标的方案——XP系统解决方案》

版权声明:本文为博主原创文章,未经博主允许不得转载。

在任务栏上的时钟区域显示自己的内容

以 TrayClockWClass 为父窗口测试, 诸多不完美, 单调, 可能有闪烁,  仅作为基础测试使用 // ShellClock.cpp : Defines the entry poi...
  • zgl7903
  • zgl7903
  • 2016年07月15日 07:33
  • 416

下拉列表ListPopupWindow

1、查看源码,会发现PopupMenu和Spinner内部都是使用ListPopupWindow实现下拉列表效果,所以ListPopupWindow是基础。 2、PopMenu的列表页面无法定制...
  • android_zhengyongbo
  • android_zhengyongbo
  • 2017年05月15日 09:37
  • 601

win7+cuda8.0+cudnn5.1+caffe-master(microsoft)+faster-rcnn完整配置手册

首先给出一些下载地址: Cudnn下载: https://developer.nvidia.com/rdp/cudnn-download Vs2013下载: http://download.m...
  • m0_37973394
  • m0_37973394
  • 2018年01月28日 22:23
  • 45

通知区域托盘

//图标句柄 HICON hIcon; TCHAR szTip[] = _T("鼠标在图标上!"); TCHAR szInfo[] = _T("通知气球内容!"); TCHAR szInfoTitl...
  • hczhiyue
  • hczhiyue
  • 2011年09月27日 14:32
  • 486

清理Windows7通知区域的图标缓存

这里有一个简单的方案以清理Windows通知区域图标中过时或是没有用的图标缓存,使其不再在【控制面板/所有控制面板项/通知区域图标】的列表中显示:   下载这个BAT文件,然后执行它。如果你看到了一个...
  • kaedei
  • kaedei
  • 2010年04月10日 08:26
  • 4889

初识PopupWindow

PopupWindow?Dialog??? Android???????:Dialog?Popupwindow AlertDialog???????,Popupwindow?????? Dialog?...
  • chezi008
  • chezi008
  • 2016年08月02日 00:10
  • 157

RecyclerView+PopupWindow 自定义弹框

好久没写博客了,从7月份一直实习到现在,趁着过年有时间,整理一下今年学到的东西,弹框是我们一直需要的控件之一,所以弄一个好的弹框显示是蛮有必要的,这是我在我的项目里自己封装的一个弹框控件。 我...
  • TIANLANG3
  • TIANLANG3
  • 2016年02月05日 22:53
  • 3819

一种清除windows通知区域“僵尸”图标的方案——Windows7系统解决方案

Windows7下“僵尸”图标的解决方案         从《一种清除windows通知区域“僵尸”图标的方案——问题分析》(以后简称《问题分析》)一文中分析的通知区域结构可以看出,Windows7的...
  • breaksoftware
  • breaksoftware
  • 2013年12月09日 01:05
  • 3017

一种清除windows通知区域“僵尸”图标的方案——XP系统解决方案

XP下“僵尸”图标的解决方案         从《一种清除windows通知区域“僵尸”图标的方案——问题分析》(以后简称《问题分析》)一文中分析的通知区域结构可以看出,XP的通知区域结构是相对简单的...
  • breaksoftware
  • breaksoftware
  • 2013年12月09日 01:03
  • 3924

PopupWindow的设置以及效果

 只是自己用的时候顺便整理了一下 1.创建一个popupwindow   View view= LayoutInflater.from(context).inflate(R.layout.s...
  • qq_18509939
  • qq_18509939
  • 2015年05月28日 14:46
  • 1279
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:一种清除windows通知区域“僵尸”图标的方案——问题分析
举报原因:
原因补充:

(最多只允许输入30个字)