Thursday, May 27, 2004

 

Some notes on Taskbar

1.
(按:今天有朋友问起这个问题,恰又发现以前的一包资料,遂有此篇转载。剩下的资料正好填以前blog的坑)

作者:sunyuzhe114
出处:http://www.csdn.net

[quote]
为了说明这个问题,我测试了下面的软件,我的机器是p3-450,192mb,win2000:
天网防火墙2.4.8,
腾讯QQ简体标准版2000C Build 0305b
简体测试版QQ2000C Build 0510
金山公司的金山词霸2002,
foxmail 4.1

当上述程序运行后,它们会在屏幕的右下脚的托盘中增加一个小图标,当我们打开进程管理器时,把系统的explore.exe结束,这时windows下的任务栏和开始菜单就消失了,这是我们再能过进程管理器运行ie(我的是在D:\WINNT\explorer.exe),这时windows下的任务栏和开始菜单就又出现了,但是我们注意到原来的一些程序在屏幕的右下脚的托盘中增加一个小图标不见了,但是该程序的进程仍在进程管理器中可见,这是问题就出现了,我们的程序就无法在用户的控制下运行了。经测试上述软件除foxmail 4.1存活下回去,其它无一幸免。同时我还测试了一下国外的一软件,如诺顿,winmap等,做同样的测试,它们的程序图标在ie恢复后仍可出现在任务栏中。而且存在bug的程序远不止这些程序,只要是在屏幕的右下脚的托盘中增加一个小图标的程序80%都存在这个问题。我写这篇文章的目的就是要告诉大家,这个bug很容易修复的,以一个基于对话框的vc++6.0程序为例,
首行应在对话框类中加入如下变量。
NOTIFYICONDATA m_tnid;
再定义一个全局变量
#define MYWM_NOTIFYICON (WM_USER+100)

我们知道,写一个托盘程序就要在OnInitDialog()中加入如下代码就可以了
m_tnid.cbSize=sizeof(NOTIFYICONDATA);
m_tnid.hWnd=AfxGetMainWnd()->m_hWnd;
m_tnid.uFlags=NIF_MESSAGE|NIF_ICON|NIF_TIP;
m_tnid.uCallbackMessage=MYWM_NOTIFYICON;
strcpy(m_tnid.szTip,"Only 1.99 $");
m_tnid.uID=IDR_MAINFRAME;
HICON hIcon;
hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_tnid.hIcon=hIcon;
::Shell_NotifyIcon(NIM_ADD,&m_tnid);
if(hIcon)::DestroyIcon(hIcon);
这时运行程序,就会在系统托盘中加入一个我们的图标, 但是我们退出程序时那个右下
脚的图标并不会消失,只有把鼠标移到图标那里图标才会消失。我们可以重载程序的
关闭窗口函数,在OnCancel() 函数中加入
NOTIFYICONDATA tnid;
tnid.cbSize=sizeof(NOTIFYICONDATA);
tnid.hWnd=AfxGetMainWnd()->m_hWnd;
tnid.uID=IDR_MAINFRAME;//保证删除的是我们的图标
Shell_NotifyIcon(NIM_DELETE,&tnid);
这时我们退出程序时,屏幕的右下脚的托盘中增加一个小图标也会随之消失。也许许多人包括我在内,都会觉得我样的程序就很完美了,但是当有时ie执行了非法操作被关闭时,我们重新恢复ie时刚才写过的程序的光标在任务栏中不见了。也许你会说我们的程序在任务栏中还有最小化的窗口,的确我们写是程序是这样的,但是我们测试的程序在最小化以后在任务栏中是没有窗口的如果是这样,当它们的小图标在屏幕的右下脚的托盘中消失后,一般情况下你是无法让它们再出来了。当然你是高手你可以自己写个程序,找到那个消失光标的程序的句柄,用一个showwindow让它出来,但是通常你不得不再用进程管理器重新结束那此程序的进程。这就是上述国产软件的通病。也许这样并不会造成什么损失,不过有一次我用sterm 1.0 bet时挂在上面2天,结果因为ie掉死了所以2天的48小时的时间就没有加到我的经验值里。不过现在的sterm 就没有这个bug了。那么我们该怎么解决这个问题呢。其实很简单。
在csdn的讨论中,经过 jiangsheng(蒋晟卧病中)的帮助,我得出结论
我们只要在全局const UINT WM_TASKBARCREATED =
::RegisterWindowMessage(_T("TaskbarCreated"));//这个消息是系统开始菜单,任务栏创建时发出的
再加上消息映射表
ON_REGISTERED_MESSAGE(WM_TASKBARCREATED,
OnTaskBarCreated)
OnTaskBarCreated()函数如下:
afx_msg void OnTaskBarCreated();

void CTianWangDlg::OnTaskBarCreated()
{
m_tnid.cbSize=sizeof(NOTIFYICONDATA);
m_tnid.hWnd=AfxGetMainWnd()->m_hWnd;
m_tnid.uFlags=NIF_MESSAGE|NIF_ICON|NIF_TIP;
m_tnid.uCallbackMessage=MYWM_NOTIFYICON;
strcpy(m_tnid.szTip,"Only 1.99 $");
m_tnid.uID=IDR_MAINFRAME;
HICON hIcon;
hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_tnid.hIcon=hIcon;
::Shell_NotifyIcon(NIM_ADD,&m_tnid);
}

这时现运行我们的程序,重复开始的过程,当ie恢复时我们的图标又会在系统托盘中出现了。
[/quote]

我正式出的程序都没有这个问题。

2.
Taskbar Sorter Utility
By Paul S. Vickery
http://www.codeproject.com/tools/tbarsort.asp

Utility to change order of icons in taskbar

3.
Taskbar Notification dialog
By John O'Byrne
http://www.codeproject.com/dialog/TaskbarNotifier.asp

A MSN IM-style popup notification dialog

4.
CTrayIconPosition - where is my tray icon?
By Irek Zielinski
http://www.codeproject.com/shell/ctrayiconposition.asp

Ever wanted to know position of your tray icon? Windows supplies no API for that. This class is a compact solution that works.

5.
作者:kingcom_xu
出处:http://www.csdn.net/develop/article/17/17482.shtm

VB的ShowInTaskbar功能分析以及用VC的实现 kingcom_xu(原作)

关键字 ITaskbarList,任务栏按钮,WS_EX_APPWINDOW,WS_EX_TOOLWINDOW



  在VB中想要显示或隐藏一个窗口在任务栏上的按钮很容易,直接设定一个form的ShowInTabkbar属性即可。但在SDK中却不是一件易事,一个窗口在什么情况下会在任务栏上显示一个按钮,在什么情况下不显示呢?这是有规定的:

0,无论什么情况,要让一个窗口在任务栏上显示按钮的前提是该窗口是可见的.

1.如果一个窗口是顶级窗口(也就是父窗口为NULL,也就是父窗口为桌面窗口),那么Windows将为它在任务栏上创建一个按钮.(有例外,见3)

2.如果一个窗口不是顶级窗口,但有WS_EX_APPWINDOW风格,那么Windows将为它在任务栏上创建一个按钮,否则不会有相应的任务栏按钮.

3.如果一个窗口是顶级窗口,但加了WS_EX_TOOLWINDOW,并去掉WS_EX_APPWINDOW风格,那么Windows不会为它在任务栏上创建一个按钮.

4....

  知道了这些规则,你就能随心所欲的控制你的窗口是否要ShowInTaskbar了,下面给出一个问题和解决方案.

问题:在MFC中一个基于对话框的工程,想要主对话框不在任务栏上显示按钮,该如何做?

解决方案1:

根椐上面的第三条规则,给该对话框加上WS_EX_TOOLWINDOW风格并去掉WS_EX_APPWINDOW风格.

代码:

在对话框类的OnInitDialog函数里加入:
ModifyStyleEx(WS_EX_APPWINDOW,WS_EX_TOOLWINDOW,SWP_FRAMECHANGED);


备注:这种方法会有一个缺点,你的对话框的标题将会是一个小标题,所以大多数人不会采用这种方法.

解决方案2:

根椐上面的第二条规则,将该对话框变为非顶级窗口并去掉WS_EX_APPWINDOW,在app类的initinstance里的主对话框domodal之前生成一个隐藏的对话框,并将这个窗口的指针保存在CWinApp类的m_pMainWnd成员变量中,以后DoModal的对话框都将是它的子窗口.


代码:

在app的initinstance函数中加入以下加注释的代码
BOOL CTestDlgApp::InitInstance()
{
  ...
  CFrameWnd mainWnd; //生成一个框架窗口对象
  mainWnd.Create(NULL,"aa");//生成窗口,不带ws_visible,也就是说不会显示出来

  CTestDlgDlg dlg;
  m_pMainWnd = &mainWnd;//将这个隐藏窗口设为主窗口

  int nResponse = dlg.DoModal();
  ...
}

在对话框的OnInitDialog里加入 ModifyStyleEx(WS_EX_APPWINDOW,0);以便出掉WS_EX_APPWINDOW。

备注:这种方法较为麻烦,不过很实用,据我观察,C++Builder和Delphi生成的程序都应该有一个隐藏的窗口.


解决方案3:

在IE版本为4.0以上的系统中有一个叫ITaskbarList的接口,故名思义,这个接口可用来控制任务栏上的按钮是否显示,你只要给出一个窗口的句柄和是否显示,完全不需考虑上面那此乱七八糟的规则.

代码:

void ShowInTaskbar(HWND m_hWnd,BOOL bshow)
{
//在app的InitInstance中加入::CoInitialize(NULL);
HRESULT hr;
ITaskbarList *pTaskbarList;

hr=CoCreateInstance(CLSID_TaskbarList,NULL,CLSCTX_INPROC_SERVER,
IID_ITaskbarList,(void**)&pTaskbarList);

pTaskbarList->HrInit();//MSDN中说在使用这个接口的其它方法之前得调用这个方法以便做一些初始化动作,我发现不调用这个方法也可以的.
if(bshow){
pTaskbarList->AddTab(m_hWnd);
}
else{
pTaskbarList->DeleteTab(m_hWnd);
}
pTaskbarList->Release();
//在app的ExitInstance中加入::CoUninitialize();

}


在需要的时候调用这个函数就可以了,第一个参数为窗口的句柄,第二个参数指明是在任务栏上是否显示按钮.

备注:这种方法功能强大,使用也较为简便,不过低版本的windows可能不支持.

以上三种方案大家可根据情况自行选择.



<< Home

This page is powered by Blogger. Isn't yours?