给Windows 95工具条增加显示桌面功能 在Windows 98的工具条中的右边新增了几个功能按钮,其中的一个名为“显示桌 面”,任何时候点击它都能快速地显示出桌面,而隐藏当前正在运行的所有程序 的窗口。由于我们都习惯将经常使用的程序的图标放置在桌面上,在同时使用多 个程序的情形下,该功能按钮可以帮助我们快速地从桌面上启动需要的程序,而 不需再从“开始”按钮去搜索需要的程序图标,极大地提供了工作效率。 但是在Windows 95中并没有提供该功能按钮,笔者用Visual C++编制了一个名为 ShowDesktop的程序,可以在Windows 95的工具条中插入一个名为“显示桌面”的 按钮,并实现Windows 98工具条中“显示桌面”功能按钮的所有功能,能对 Windows 95用户提供一定的方便。 本文在分析Windows 98“显示桌面”功能的基础上,介绍了ShowDesktop的实现技术。 Windows 98“显示桌面”功能分析 点击Windows 98的工具条中的的“显示桌面”按钮后,所有运行程序的窗口都将 被最小化,因而显露出桌面。再次点击“显示桌面”按钮,所有被最小化的窗口 都将恢复原样,但第一次点击“显示桌面”按钮时已被最小化的窗口除外。 由以上分析可以看出,“显示桌面”程序具有如下的功能特征: 能找出所有当前正在运行程序的窗口 如窗口未处于最小化,则将其最小化,并将它存入一个列表 再次点击“显示桌面”按钮时,能恢复记录在列表中的窗口 实现ShowDesktop的主要技术 ShowDesktop应实现上述的三个功能,同时还应将自身的运行图标插入工具条中。 由于ShowDesktop是作为一个功能程序设计的,因此不能出现在运行程序列表中, 也不能在工具条中的显示运行按钮,只能在工具条中显示为功能按钮。 然而,Windows 95并没有象Windows 98一样提供在工具条中设置功能按钮的接口 ,因此很难将ShowDesktop显示为工具条中功能按钮。但是,Windows 95任务条的 右边有一个区域被称为通知区域,在其中可以显示一些应用程序的图标,并且可 以获得这些图标的鼠标操作消息。时钟和音量控制是任务条通知区最常见的图标 ,用鼠标单击其中的图标一般能弹出应用程序的菜单,双击则能显示应用程序的 完整窗口界面。笔者采用这种技术来设计ShowDesktop,ShowDesktop运行时,仅 在任务条通知区域中显示图标,以代替任务条中的功能按钮。点击ShowDesktop图 标的功能和点击Windows 98 “显示桌面”按钮的功能完全相同。 下面将讨论实现ShowDesktop的主要技术要点。 在工具条中显示程序的功能图标 任务条通知区编程可以通过Windows 95外壳编程接口函数Shell_NotifyIcon来实 现,该函数在shellapi.h头文件中声明,其原型如下: WINSHELLAPI BOOL WINAPI Shell_NotifyIcon( DWORD dwMessage, PNOTIFYICONDATA pnid); dwMessage是对通知区图标进行操作的消息,主要有三中,如下表所示。 Shell_NotifyIcon使用的消息 消息 说明 NIM_ADD 在任务条通知区插入一个图标 NIM_ DELETE 在任务条通知区删除一个图标 NIM_ MODIFY 对任务条通知区的图标进行修改 pnid传入一个NOTIFYICONDATA结构的指针。NOTIFYICONDATA结构声明及各域的意义表示如下: typedef struct _NOTIFYICONDATA { // nid DWORD cbSize; // NOTIFYICONDATA结构的字节数 HWND hWnd; // 处理通知区图标消息的窗口句柄 UINT uID; // 通知区图标的ID UINT uFlags; // 表示下述三项是否有意义的标志 UINT uCallbackMessage; // 鼠标点击图标所发出消息的ID HICON hIcon; // 图标句柄 char szTip[64]; // 当鼠标移到图标上时显示的提示信息 } NOTIFYICONDATA, *PNOTIFYICONDATA; 当用Shell_NotifyIcon在任务条通知区中放置一个图标时,同时也定义了一条回 调消息,当用户用鼠标单击或双击图标时,NOTIFYICONDATA结构中指定的窗口句 柄将接受到该消息。该消息的lParam参数将说明鼠标操作的方式。当应用程序退 出时,应删除任务条中的图标。 为了截获并处理任务条通知区中图标的鼠标操作消息,需要定义一个Windows用户 消息,并将它赋直给NOTIFYICONDATA结构的uCallbackMessage。在ShowDesktop中 ,该消息的定义如下: #define UM_ICONNOTIFY WM_USER+100 ShowDesktop的AddIcon和DeleteIcon函数分别实现图标在的任务条通知区的插入 和删除,其代码如下: BOOL CShowDesktopDlg::AddIcon() { NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = m_hWnd; nid.uID = IDR_MAINFRAME; nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; nid.uCallbackMessage = UM_ICONNOTIFY; nid.hIcon = m_hIcon; CString str = "显示桌面"; lstrcpyn(nid.szTip, (LPCSTR)str, sizeof(nid.szTip) / sizeof(nid.szTip[0])); return Shell_NotifyIcon(NIM_ADD, &nid); } BOOL CShowDesktopDlg::DeleteIcon() { NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = m_hWnd; nid.uID = IDR_MAINFRAME; return Shell_NotifyIcon(NIM_DELETE, &nid); } 当用户用鼠标操作任务条通知区中的ShowDesktop图标时,ShowDesktop将收到一 条UM_ICONNOTIFY消息,ShowDesktop将在该消息的处理函数中完成。ShowDesktop 的操作方式如下:在图标上双击和单击鼠标左键,完成显示桌面程序的功能;在 图标上单击鼠标右键,显示一个操作菜单。该函数的结构如下: void CShowDesktopDlg::OnIconNotify(WPARAM wParam, LPARAM lParam) { switch ((UINT)lParam) { case WM_LBUTTONDOWN: // 点击左键 case WM_LBUTTONDBLCLK: // 双击左键 // 调用函数,完成显示桌面的功能 DoShowDesktop(); break; case WM_RBUTTONDOWN: // 点击右键 // 调用函数,显示操作菜单 DisplayMenu(); break; } } 函数DoShowDesktop是程序的主体功能函数,其实现将在下文介绍。DispalyMenu 函数调用TrackPopupMenu函数来显示弹出菜单,其实现如下: void CShowDesktopDlg::DisplayMenu() { CMenu menu; menu.LoadMenu(IDR_MENU1); CPoint point; GetCursorPos(&point); SetForegroundWindow(); menu.GetSubMenu(0)->TrackPopupMenu( TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this, NULL); PostMessage(WM_USER, 0, 0); } 消除程序在运行列表中的显示和在工具条中的运行按钮 ShowDesktop在任务条通知区中的图标是操作它的唯一界面,还需要清除它在运行 程序列表中的显示和在工具条中的运行按钮。这可通过修改窗口的扩展风格来实 现。Win 32窗口的扩展风格WS_EX_TOOLWINDOW定义的窗口将不会在运行程序列表 中显示信息,也没有工具条中的运行按钮。调用SetWindowLong函数可以修改窗口 的扩展风格。ShowDesktop中定义的函数HideTaskBarButton实现了该功能。 void CShowDesktopDlg::HideTaskBarButton() { ShowWindow(SW_HIDE); long lExStyle = ::GetWindowLong(m_hWnd, GWL_EXSTYLE); lExStyle &= ~WS_EX_APPWINDOW; // 去除AppWindow扩展风格 lExStyle |= WS_EX_TOOLWINDOW; // 增加ToolWindow扩展风格 ::SetWindowLong(m_hWnd, GWL_EXSTYLE, lExStyle); ShowWindow(SW_SHOWNA); } 寻找当前正在运行的所有程序的窗口 实现ShowDesktop功能的基础是找出当前正在运行的所有程序的窗口。实现该功能 有多种方法,可以调用FindWindow和FindWindowEx来实现,也可使用GetWindow来 实现。笔者在ShowDesktop中采用了后者,其调用方式如下程序片段所示: CWnd *pWnd = GetWindow(GW_HWNDFIRST); while (pWnd != NULL) { // 处理代码 // …………….. pWnd = pWnd->GetWindow(GW_HWNDNEXT); } 过滤需隐藏的窗口 用上述方法将找出的所有存在的窗口,包括桌面窗口、ShowDesktop窗口、各类子 窗口、已经最小化的窗口和程序管理器窗口等,这些都不应该被最小化,因此应 该屏蔽。下面的语句用于屏蔽这些窗口: CString str; pWndCur->GetWindowText(str); if (pWnd->IsWindowVisible() && ! pWnd->IsIconic() && ! pWnd->GetParent() && pWnd != this && pWnd != GetDesktopWindow() && ! str.IsEmpty() && str != "Program Manager") { // 应该处理的窗口 } else { // 应该屏蔽的窗口 } 保存窗口列表 由于再次点击ShowDesktop图标,将恢复所有被其最小化的窗口,因此应该在第一 次点击图标时,将需被其最小化的窗口保存在一个列表(缓冲区)中。第二次点 击图标时,只需恢复该列表中的窗口。 ShowDesktop在其窗口类中设置了一个数据成员m_pWnd来保存窗口列表,数据成员m_nWndNum用来记录列表中窗口数量,它们的定义如下: CWnd * m_pWnd[MAXNUM_RUNWINDOW]; // MAXNUM_RUNWINDOW=256 int m_nWndNum; 所有经过滤合格的窗口都将记录在m_pWnd列表中。DoShowDesktop函数中,过滤及保存窗口列表的语句段如下: // find all windows in run, store visible ones in an array CWnd *pWnd[MAXNUM_RUNWINDOW], *pWndCur; int nWndNum = 0; pWnd[nWndNum]= GetWindow(GW_HWNDFIRST); while (pWnd[nWndNum] != NULL) { pWndCur = pWnd[nWndNum]; CString str; pWndCur->GetWindowText(str); if (pWndCur->IsWindowVisible() && ! pWndCur->IsIconic() && ! pWndCur->GetParent() && pWndCur != this && pWndCur != GetDesktopWindow() && ! str.IsEmpty() && str != "Program Manager") { nWndNum++; // 列表中增加一个窗口 // 为保证安全!! nWndNum = min(nWndNum, MAXNUM_RUNWINDOW-1); } pWnd[nWndNum] = pWndCur->GetWindow(GW_HWNDNEXT); } 隐藏和重显窗口 隐藏窗口实际上就是将窗口列表中的窗口最小化,重显窗口就是恢复窗口列表中 的所有最小化窗口,可通过ShowWindow函数来实现。DoShowDesktop函数中实现窗 口隐藏和重显的语句段如下: if (nWndNum) // 如果当前列表中记录有窗口,将它们最小化 { // 最小化列表中的所有窗口 for (int i=0; iShowWindow(SW_SHOWMINNOACTIVE); m_pWnd[i] = pWnd[i]; } m_nWndNum = nWndNum; } else // 否则,恢复原列表中所有窗口 { // 恢复原列表中的所有窗口 for (int i=m_nWndNum-1; i>=0; --i) { if (::IsWindow(m_pWnd[i]->m_hWnd) && m_pWnd[i]->IsIconic()) { m_pWnd[i]->ShowWindow(SW_RESTORE); m_pWnd[i]->UpdateWindow(); } } } 辅助功能 除实现Windows 98的“显示桌面”功能外,ShowDesktop还设置了一个控制菜单, 当在图标上点击右键时,将显示该菜单。菜单中有四个菜单项,即关于、关闭计 算机、重新启动计算机和退出。选取关于菜单命令,将显示一个程序说明对话框 ;选取退出菜单命令,ShowDesktop将退出运行。关闭计算机、重新启动计算机菜 单命令的功能与Windows的关闭系统对话框中的命令功能相同,将关闭系统和重新 启动计算机。这两个功能是通过调用ExitWindowsEx函数来实现的。在关闭和重新 启动计算机之前,ShowDesktop将询问用户是否真要进行。这两个功能的实现函数如下: void CShowDesktopDlg::OnExitwindows() { if (IDYES == MessageBox(_T("确实要关闭计算机 ?"), _T("显示桌面"), MB_YESNO|MB_ICONQUESTION)) ExitWindowsEx(EWX_SHUTDOWN, 0L); } void CShowDesktopDlg::OnReboot() { if (IDYES == MessageBox(_T("确实要重新启动计算机 ?"), _T("显示桌面"), MB_YESNO|MB_ICONQUESTION)) ExitWindowsEx(EWX_REBOOT, 0L); } ShowDesktop的实现 为了方便,ShowDesktop采用对话框作为主窗口,其类定义在文件ShowDesktopDlg.h中。该文件的主要代码如下: #define UM_ICONNOTIFY WM_USER+100 #define MAXNUM_RUNWINDOW 256 class CShowDesktopDlg : public CDialog { // Construction public: CShowDesktopDlg(CWnd* pParent = NULL); // standard constructor // Dialog Data //{{AFX_DATA(CShowDesktopDlg) enum { IDD = IDD_SHOWDESKTOP_DIALOG }; // NOTE: the ClassWizard will add data members here //}}AFX_DATA // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CShowDesktopDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL // Implementation protected: HICON m_hIcon; CWnd * m_pWnd[MAXNUM_RUNWINDOW]; int m_nWndNum; void HideTaskBarButton(); void Inserticon(); BOOL AddIcon(); BOOL DeleteIcon(); void DisplayMenu(); void DoShowDesktop(); // Generated message map functions //{{AFX_MSG(CShowDesktopDlg) virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); afx_msg void OnAbout(); afx_msg void OnExit(); afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); afx_msg void OnDestroy(); afx_msg void OnExitwindows(); afx_msg void OnReboot(); //}}AFX_MSG afx_msg void OnIconNotify(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() }; CShowDesktopDlg类的实现是ShowDesktop程序的主体。该类的实现代码文件为 ShowDesktopDlg.cpp,其主要部分如下: #include "stdafx.h" #include "ShowDesktop.h" #include "AboutDlg.h" #include "ShowDesktopDlg.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CShowDesktopDlg dialog CShowDesktopDlg::CShowDesktopDlg(CWnd* pParent /*=NULL*/) : CDialog(CShowDesktopDlg::IDD, pParent) { //{{AFX_DATA_INIT(CShowDesktopDlg) // NOTE: the ClassWizard will add member initialization here //}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_nWndNum = 0; } void CShowDesktopDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(CShowDesktopDlg) // NOTE: the ClassWizard will add DDX and DDV calls here //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(CShowDesktopDlg, CDialog) //{{AFX_MSG_MAP(CShowDesktopDlg) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_COMMAND(IDM_ABOUT, OnAbout) ON_COMMAND(IDM_EXIT, OnExit) ON_WM_CREATE() ON_WM_DESTROY() ON_COMMAND(IDM_EXITWINDOWS, OnExitwindows) ON_COMMAND(IDM_REBOOT, OnReboot) //}}AFX_MSG_MAP ON_MESSAGE(UM_ICONNOTIFY, OnIconNotify) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CShowDesktopDlg message handlers BOOL CShowDesktopDlg::OnInitDialog() { CDialog::OnInitDialog(); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // insert icon and hide window Inserticon(); return TRUE; // return TRUE unless you set the focus to a control } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CShowDesktopDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } } int CShowDesktopDlg::OnCreate(LPCREATESTRUCT lpCreateStruct) { if (CDialog::OnCreate(lpCreateStruct) == -1) return -1; // set search tag for main window ::SetProp(m_hWnd, AfxGetApp()->m_pszExeName, (HANDLE)1); return 0; } void CShowDesktopDlg::OnDestroy() { CDialog::OnDestroy(); // remove main window tag ::RemoveProp(m_hWnd, AfxGetApp()->m_pszExeName); // remove icon from taskbar DeleteIcon(); } // The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CShowDesktopDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; } void CShowDesktopDlg::HideTaskBarButton() { ShowWindow(SW_HIDE); long lExStyle = ::GetWindowLong(m_hWnd, GWL_EXSTYLE); lExStyle &= ~WS_EX_APPWINDOW; // get rid of AppWindow ex-style lExStyle |= WS_EX_TOOLWINDOW; // add ToolWindow ex-style ::SetWindowLong(m_hWnd, GWL_EXSTYLE, lExStyle); ShowWindow(SW_SHOWNA); } void CShowDesktopDlg::Inserticon() { // first minimize to hide window ShowWindow(SW_MINIMIZE); // hide its run icon button in taskbar HideTaskBarButton(); // insert icon in taskbar AddIcon(); } BOOL CShowDesktopDlg::AddIcon() { // Add the notification icon to the taskbar NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = m_hWnd; nid.uID = IDR_MAINFRAME; nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; nid.uCallbackMessage = UM_ICONNOTIFY; nid.hIcon = m_hIcon; CString str = "显示桌面"; lstrcpyn(nid.szTip, (LPCSTR)str, sizeof(nid.szTip) / sizeof(nid.szTip[0])); return Shell_NotifyIcon(NIM_ADD, &nid); } BOOL CShowDesktopDlg::DeleteIcon() { // Remove the notification icon from the taskbar NOTIFYICONDATA nid; nid.cbSize = sizeof(nid); nid.hWnd = m_hWnd; nid.uID = IDR_MAINFRAME; return Shell_NotifyIcon(NIM_DELETE, &nid); } void CShowDesktopDlg::OnIconNotify(WPARAM wParam, LPARAM lParam) { switch ((UINT)lParam) { case WM_LBUTTONDOWN: // click or dbclick left button on icon case WM_LBUTTONDBLCLK: // should show desktop DoShowDesktop(); break; case WM_RBUTTONDOWN: // click right button, show menu DisplayMenu(); break; } } void CShowDesktopDlg::DisplayMenu() { CMenu menu; menu.LoadMenu(IDR_MENU1); CPoint point; GetCursorPos(&point); SetForegroundWindow(); menu.GetSubMenu(0)->TrackPopupMenu( TPM_LEFTBUTTON|TPM_RIGHTBUTTON, point.x, point.y, this, NULL); PostMessage(WM_USER, 0, 0); } void CShowDesktopDlg::DoShowDesktop() { // find all windows in run, store visible ones in an array CWnd *pWnd[MAXNUM_RUNWINDOW], *pWndCur; int nWndNum = 0; pWnd[nWndNum]= GetWindow(GW_HWNDFIRST); while (pWnd[nWndNum] != NULL) { pWndCur = pWnd[nWndNum]; CString str; pWndCur->GetWindowText(str); if (pWndCur->IsWindowVisible() && ! pWndCur->IsIconic() && ! pWndCur->GetParent() && pWndCur != this && pWndCur != GetDesktopWindow() && ! str.IsEmpty() && str != "Program Manager") { // add a window in list nWndNum++; // security!! nWndNum = min(nWndNum, MAXNUM_RUNWINDOW-1); } pWnd[nWndNum] = pWndCur->GetWindow(GW_HWNDNEXT); } if (nWndNum) { // minimize of the windows in run for (int i=0; iShowWindow(SW_SHOWMINNOACTIVE); m_pWnd[i] = pWnd[i]; } m_nWndNum = nWndNum; } else { // restore minimized windows in run for (int i=m_nWndNum-1; i>=0; --i) { if (::IsWindow(m_pWnd[i]->m_hWnd) && m_pWnd[i]->IsIconic()) { m_pWnd[i]->ShowWindow(SW_RESTORE); m_pWnd[i]->UpdateWindow(); } } } } void CShowDesktopDlg::OnAbout() { CAboutDlg about(this); about.DoModal(); } void CShowDesktopDlg::OnExit() { CDialog::EndDialog(0); } void CShowDesktopDlg::OnExitwindows() { if (IDYES == MessageBox(_T("确实要关闭计算机 ?"), _T("显示桌面"), MB_YESNO|MB_ICONQUESTION)) ExitWindowsEx(EWX_SHUTDOWN, 0L); } void CShowDesktopDlg::OnReboot() { if (IDYES == MessageBox(_T("确实要重新启动计算机 ?"), _T("显示桌面"), MB_YESNO|MB_ICONQUESTION)) ExitWindowsEx(EWX_REBOOT, 0L); }