通常电脑里边的软件,当你打开后会在电脑最右下角的任务栏上生成一个系统托盘,当你点击最小化或者点击关闭按钮后,若想恢复窗口,可以左键双击或者单机这个系统图标,同时鼠标右键点击,又会有其他的菜单弹出,比如退出。
我看着感觉还是挺酷炫的,所以打算也在我的程序里边添加一个这样的功能。下边我把我自己探索的过程记录一下:
首先,我们需要用到NOTIFYICONDATA类,它是我们实现系统托盘的核心。关于这个类,百度百科上是这么说的:NOTIFYICONDATA是一个函数公式,主要含义和作用是以此函数用来向任务栏托盘区域发送消息。好了,其他的就不用管了,接下来进行实际的操作。
1、声明一个NOTIFYICONDATA类对象,一般可以放在父类里边作为成员变量或者作为全局的变量。
private:
NOTIFYICONDATA NotifyIcon; //系统托盘类
2、自定义一个消息
#define WM_SYSTEMTRAY WM_USER+5
为什么是 WM_USER+5?关于消息WM_USER,为了防止用户定义的消息ID与系统的消息ID冲突,MS(Microsoft)定义了一个宏WM_USER,小于WM_USER的ID被系统使用,大于WM_USER的ID被用户使用。加5是我自己随便定义的,当然你可以自己指定具体的数值。
3、接下来这步,可以自己手动添加,也可以通过类向导来操作。我采用手动添加方式。声明一个响应函数,用来响应鼠标的操作。
protected:
afx_msg LRESULT OnSystemtray(WPARAM wParam, LPARAM lParam);
4、注册刚才自定义的消息。在BEGIN_MESSAGE_MAP(CMyPlayerDlg, CDialogEx)和END_MESSAGE_MAP()之间添加如下代码:
ON_MESSAGE(WM_SYSTEMTRAY, &CMyPlayerDlg::OnSystemtray)
5、然后这一步就是开始产生作用的操作。一般添加在Oninitdialog()中,但是也可以是其他的函数中,比如只有在你点击关闭按钮后才添加系统拖盘图标,那么就不是在此处添加,具体在哪里添加,后面我会讲到。
BOOL CMyPlayerDlg::OnInitDialog()
{
//设置系统托盘
NotifyIcon.cbSize=sizeof(NOTIFYICONDATA);
//NotifyIcon.hIcon=AfxGetApp()->LoadIcon(IDR_MAINFRAME);
NotifyIcon.hIcon=m_hIcon; //上面那句也可以
NotifyIcon.hWnd=m_hWnd;
lstrcpy(NotifyIcon.szTip,_T("爆疯牛逼一代"));
NotifyIcon.uCallbackMessage=WM_SYSTEMTRAY;
NotifyIcon.uFlags=NIF_ICON | NIF_MESSAGE | NIF_TIP;
Shell_NotifyIcon(NIM_ADD,&NotifyIcon); //添加系统托盘
//...
}
其中比较重要的函数是Shell_NotifyIcon(NIM_ADD,&NotifyIcon),关于这个函数的具体解释,可以看这里:http://www.cnblogs.com/duzouzhe/archive/2010/04/08/1707050.html
NIM_ADD参数表示添加一个图标到系统托盘区。
6、上面我们定义了响应函数afx_msg LRESULT OnSystemtray(WPARAM wParam, LPARAM lParam),那么具体如何响应鼠标左键和右键的操作呢?
直接上代码:
afx_msg LRESULT CMyPlayerDlg::OnSystemtray(WPARAM wParam, LPARAM lParam)
{
//wParam接收的是图标的ID,而lParam接收的是鼠标的行为
// if(wParam!=IDR_MAINFRAME)
// return 1;
switch(lParam)
{
case WM_RBUTTONDOWN://右键起来时弹出快捷菜单
{
CMenu menuexit;
//menu.LoadMenuW(IDR_MENU1);//加载菜单资源
menuexit.LoadMenuA(IDR_MENUexit);
CMenu *pPopup=menuexit.GetSubMenu(0);
CPoint mypoint;
GetCursorPos(&mypoint);
//ClientToScreen(&mypoint);//将客户区坐标转换为屏幕坐标
SetForegroundWindow();
PostMessage(WM_NULL,0,0);
//显示右键菜单,由视类窗口拥有。
pPopup->TrackPopupMenu(TPM_LEFTALIGN,mypoint.x,mypoint.y,this);
}
break;
case WM_LBUTTONDOWN://左键单击的处理
{
ModifyStyleEx(0,WS_EX_TOPMOST); //可以改变窗口的显示风格
ShowWindow(SW_SHOWNORMAL);
}
break;
}
return 0;
}
代码中我只实现了两个消息的响应,即鼠标左键单击和右键单击的消息。当鼠标左键单击后,恢复原窗口的大小和位置;当鼠标右键单击后,弹出菜单。我自己的菜单是如下设置的:
case WM_LBUTTONDOWN:里边的代码没有什么好讲的,关于case WM_RBUTTONDOWN:里边的代码,其实也没什么好讲的,也就是当鼠标右键单击时,装载一个右键菜单而已,其他的没有什么。具体可以看我之前的博客: 右键弹出菜单和快捷键的设置 。
然后菜单里的每一个子菜单的响应函数记得要编写,但先预留退出子菜单我下面讲。
7、其实上面的几部就已经可以实现想要的效果了。但是如果想要点击关闭按钮后程序并不关闭,而只是以系统托盘的形式出现在托盘区,直到你鼠标右键点击图标,执行退出操作,才真正退出,另一方面当你点击最小化后,程序最小化,但任务栏上任然具有程序的图标。如果想要实现这样的效果,其实也很简单,我们需要重写关闭“X”的响应函数。
具体重写的操作我就不写了,之前的博客写过这样的过程。重写代码如下:
void CMyPlayerDlg::OnCancel() //点击X 按钮,最小化到系统托盘
{
// TODO: 在此添加专用代码和/或调用基类
this->ShowWindow(HIDE_WINDOW);
//CDialogEx::OnCancel();
}
注意,注意,上面代码中的CDialogEx::OnCancel();一定要注释掉。因为我们只是想改写关闭操作,但并不真的执行关闭操作,也就是说我们只是想重写关闭按钮操作的响应函数。
但是,如果这样操作以后,你运行会发现,程序关不了了,因为关闭按钮只是执行隐藏窗口功能,你的右键菜单的退出操作也还没写,那怎么办,先杀了这个进程,接着编写如下的步骤。
(关于第5步提到的如果只是想在点击关闭按钮的时候才添加系统托盘,那也应该再OnCancel函数中书写第5步的代码,同时书写this->ShowWindow(HIDE_WINDOW);)
8、关于窗口的关闭过程,我前面的博客里边也记录过,此处就不多说了 。窗口关闭的过程中会执行DestroyWindow()函数,那么我们也将重写这个函数。
BOOL CMyPlayerDlg::DestroyWindow()
{
// TODO: 在此添加专用代码和/或调用基类
Shell_NotifyIcon(NIM_DELETE, &NotifyIcon);//消除托盘图标
return CDialogEx::DestroyWindow();
}
其中Shell_NotifyIcon(NIM_DELETE, &NotifyIcon);//消除托盘图标很关键,正好和之前的Shell_NotifyIcon(NIM_ADD,&NotifyIcon)对应起来了。一个是添加,一个是删除。如果不写Shell_NotifyIcon(NIM_DELETE, &NotifyIcon),当你程序退出后,在系统托盘区的图标还会存在,因为进程残留,需要你鼠标移上去才会消失。
那么我们还需要在子菜单退出的响应函数中调用DestroyWindow()函数。
void CMyPlayerDlg::Onexit()
{
// TODO: 在此添加命令处理程序代码
DestroyWindow();
}
到此,我们就实现了一个比较满意的系统托盘方案,当然我自己探索的时候,还是碰到了 一些问题的,比如如何执行关闭操作,如何调用退出函数等等。但是,都一一克服了,仔细想想,其实也蛮简单的。
ok ,关于这个就到这里。
拙见,小记!