明辉站/技术开发/内容

Windows下面向任务栏编程的若干问题

技术开发2023-08-11 阅读
[摘要]赵洁 胡德保一. 引言任务栏是Windows中一个众所周知的概念,它指的是Windows桌面上显示已运行程序的一块条状区域。一般来说,任务栏是由三个部分组成的:最左边是“开始”按钮,中间是已运行程序的显示区域(Windows98还有一个快捷工具栏),最右边是任务栏提示区域。所谓的任务栏编程主要指对...
赵洁 胡德保

一. 引言
任务栏是Windows中一个众所周知的概念,它指的是Windows桌面上显示已运行程序的一块条状区域。一般来说,任务栏是由三个部分组成的:最左边是“开始”按钮,中间是已运行程序的显示区域(Windows98还有一个快捷工具栏),最右边是任务栏提示区域。所谓的任务栏编程主要指对任务栏提示区进行编程。Windows系统允许用户在任务栏提示区里放置自己的应用程序图标并定制自己需要的操作。
在任务栏提示区里放置应用程序图标一方面方便了用户与应用程序的交流,另一方面也可使应用程序以更形象的形式给用户以提示。一般地,将应用程序图标放置在任务栏提示区中意味着该应用程序是一个后台程序。例如在Windows中进行后台打印时,一个打印机图标就会显示在任务栏提示区中提示用户打印机进程正在后台运行。目前有许多软件都使用了任务栏编程技术,如Netants、Go!zilla、金山词霸等,这一方面是由于这些软件具有某些“后台”性质,另一方面也是因为将应用程序图标放在任务栏提示区中使得人机交互更为简便。
目前有许多编程语言都可以针对任务栏进行编程,如VC++、Delphi、VB等,也已有一些文章对其进行了介绍。但笔者发现很少有文章从原理到程序实现系统地对任务栏编程进行论述,大多数的资料只是给出几个API函数声明和一段代码。但任务栏编程不仅涉及到API,还涉及回调函数等一些更为复杂的问题,因此有必要深入系统地对该问题进行探讨。
二. 任务栏编程的实现技术
Windows任务栏编程的基本思路是:(1)通过API函数调用在应用程序启动时将应用程序图标放入任务栏提示区,在程序运行时修改图标特性,并在应用程序关闭时将图标从任务栏中删去;(2)通过使用回调函数控制应用程序。本节主要探讨通过API来控制图标的一些问题。下一节将主要探讨回调函数。
1. Shell_NotifyIcon函数
其声明为: Private Declare Function Shell_NotifyIconLib "shell32" Alias "Shell_NotifyIconA" (ByVal dwMessage As Long,pnid As NOTIFYICONDATA) As Long
该函数给系统发送添加、修改、删除任务栏提示区图标的消息,系统根据发送的消息进行相应的处理。可以说该函数是任务栏提示区编程的核心,掌握它就可以轻松地编写出符合要求的程序来。该函数中的参数意义如下:
(1) 参数dwMessage (ByVal dwMessage As Long)
该参数通知系统进行何种操作,取值如下:
NIM_ADD
添加图标到任务栏提示区
NIM_DELETE
删除图标
NIM_MODIFY
发送图标特性已改变的消息
(2) 参数pnid (pnid As NOTIFYICONDATA)
存储图标特性数据,NOTIFYICONDATA定义如下:
Private Type NOTIFYICONDATA
CbSize As Long '该数据结构的大小
hWnd As Long '处理图标通知消息的窗口句柄
uID As Long '应用程序自定义的图标ID
uFlags As Long '用来设置uCallbackMessage、hIcon、szTip等三个栏目是否有效,一般取组合NIF_ICON Or NIF_TIP Or NIF_MESSAGE,表示全部有效
uCallbackMessage As Long '消息编号,将来当使用者在图标上按下鼠标时就会以消息通知消息处理回调函数
hIcon As Long '图标句柄
szTip As String*64 '提示消息
End Type
2.加入图标
在启动窗体的Form_Load()事件中加入以下代码就可以在程序运行时在任务栏提示区中添加一个图标:
Dim nid As NOTIFYICONDATA
nid.cbSize = Len(nid)
nid.hwnd = Me.hwnd
nid.uID = 9998
nid.uFlags = NIF_ICON + NIF_TIP + NIF_MESSAGE
nid.hIcon = Me.Icon
nid.uCallbackMessage = WM_USER+100
nid.szTip ="欢迎使用任务栏"
Shell_NotifyIcon NIM_ADD, nid ' 
3.删除图标
在Form_Unload()事件中添加以下代码在程序关闭时删除图标,要注意的是这里所使用到的uID和hWnd必须和当初加入图标时所使用的uID和hWnd完全一致。
Dim nid As NOTIFYICONDATA
nid.cbSize = Len(nid)
nid.hwnd = Me.hwnd
nid.uID = 9998
Shell_NotifyIcon NIM_DELETE, nid
4.修改图标
下面一段代码实现图标的修改。这里的uId和hWnd也必须和当初加入图标时所使用的 uId和hWnd完全一致。同时还必须正确设置uFlags和hIcon。uFlags必须设置为NIF_ICON,表示修改对图标有效;hIcon应设为新图标。
Dim nid As NOTIFYICONDATA
nid.cbSize = Len(nid)
nid.hWnd = Me.hWnd
nid.uID = 9999
nid.uFlags = NIF_ICON
nid.hIcon = Image1.Picture
Shell_NotifyIcon NIM_MODIFY, nid
修改提示消息和消息编号的方法与此类似。不同点是当修改提示消息时须将uFlags设为NIF_TIP,修改消息编号时须将uFlags设为NIF_MESSAGE。
三. 回调函数
在前面已经讲到,当用户在图标上按下鼠标时,系统将接收并传递消息给窗口消息处理回调函数进行具体的处理。用户可以在回调函数中对不同的消息进行不同的程序处理来定制自己的应用程序的功能(例如鼠标左击时弹出一个菜单,鼠标右击时弹出另一个菜单,鼠标双击时显示主窗口等等)。在任务栏提示区中加入图标时设置的消息编号uMessage就是用来告诉回调函数此消息来自图标对应的程序,回调函数即可据此进行处理。回调函数是个比较复杂的问题,下面从VB程序的运行机制入手来具体阐述任务栏编程中回调函数的实现方法。
1. VB应用程序的运行机制
VB应用程序的运行是基于Windows的消息传递机制的。Windows的工作方式是一种多任务多线程的工作方式,VB程序运行时是受Windows控制的。具体讲,一个VB程序运行时Windows的进程管理模块就会给它创建一个进程。进程与Windows之间的通讯是通过消息传递来实现的:进程发送一个消息给Windows并等待,Windows处理完此消息后将结果会传给进程。由于Windows支持多线程,因此一个进程可以在内部创建多个线程,这些线程共享此进程的地址空间、全局变量、文件以及各种信号。除了共享地址空间外,各个线程是独立的,每个线程有自己的程序计数器、堆栈、寄存器及状态(等待、就绪、运行)。VB程序在运行时,通过对其所含的每个控件都创建一个线程来并行地处理控件的事件。一个控件在进程内对应一个线程。这些线程也同样通过消息与Windows进行通讯。Windows为运行于其上的应用程序提供了确省的消息处理程序。例如在一个VB程序运行时,右击一个文本框就会弹出一个包含“剪切”、“复制”、“粘贴”等功能的快捷菜单,这就是由Windows的确省消息处理程序来完成的。Windows系统允许用户截获应用程序窗口的消息并用自定义的过程来加以处理。这个自定义的过程称为“回调”函数,用户可在回调函数中对感兴趣的消息进行适当的处理,并将其它的消息交给系统原来的消息处理过程处理。这种“替代”似乎很困难,但实际上却是完全可以实现的。
2. 用回调函数来处理任务栏提示区的消息
回调函数(Callback Function)是应用程序提供给Windows系统DLL或其它DLL调用的函数,一般用于截获消息、获取系统信息或处理异步事件。应用程序把回调函数的地址指针告诉DLL,而DLL在适当的时候会调用该函数。回调函数必须遵守事先规定好的参数格式和传递方式,否则DLL一调用它就会引起程序或系统的崩溃。
通常情况下,回调函数采用标准Windows API的调用方式,即_stdcall,当然,DLL编制者可以自已定义调用方式,但客户程序也必须遵守相同的规定。在_stdcall方式下,函数的参数按从右到左的顺序压入堆栈,除了明确指明是指针或引用外,参数都按值传递,函数返回之前自己负责把参数从堆栈中弹出。
将应用程序图标放入任务栏提示区后,如果用户要想打开该应用程序窗口,必须到任务栏提示区中去点击图标。但这并不意味着用户点击后,窗口就会自动显示出来。每当用户点击图标,系统会以消息方式通知窗口的消息处理回调函数,所以只要在应用程序窗口的回调函数中进行适当的编程就可以实现。
窗口消息回调函数的格式如下所示:
Function WndProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
因为回调函数是自定义的函数,因此函数名和参数名都可以自定义。各参数的含义如下表所示:
hWnd
窗口句柄
Msg
等于当初调用Shell_NotifyIcon时所设置的uCallbackMessage的值
wParam
等于当初调用Shell_NotifyIcon时所设置的uID的值
lParam
等于鼠标消息,例如WM_LBUTTONDOWN(按下鼠标左键)等
在VB使用回调函数存在不少限制:1、回调函数必须放到标准的模块(Module)中,而不能放在类模块或窗体代码中;2、AddressOf运算符只能用于自定义的过程、函数或属性,不能将其用于Declare语句声明的外部函数,也不能用于类型库中的函数;3、写在AddressOf后面的过程、函数和属性必须与有关的声明和过程在同一个工程中;4、由于回调函数要与系统直接交互,所以调试十分困难,如果回调函数中有错误,可能会引起非法操作,致使VB运行环境崩溃。
在VB中用回调函数处理任务栏提示区消息的具体步骤如下:
(1)在启动窗体的Form_Load()事件中加入以下代码。
prevWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) '获取系统确省的窗口消息处理函数句柄
SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WndProc '指定WndProc为新的消息处理函数
'加入图标
Dim nid As NOTIFYICONDATA
nid.cbSize = Len(nid)
nid.hwnd = Me.hwnd
nid.uID = 9998
nid.uFlags = NIF_ICON + NIF_TIP + NIF_MESSAGE
nid.hIcon = Me.Icon
nid.uCallbackMessage = WM_USER + 100 '定义消息编号
Shell_NotifyIcon NIM_ADD, nid ' 
(2)在模块中加入回调函数WndProc的代码
Function WndProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
If Msg = WM_USER + 100 Then ''该消息等于调用Shell_NotifyIcon时定义的uCallbackMessage
If lParam = WM_LBUTTONDOWN Or lParam = WM_RBUTTONDOWN Then
'当按下鼠标左键或右键时弹出菜单
frmCaution.PopupMenu frmCaution.mnuaa
End If
End If
'其它的消息交给系统处理,prevWndProc为原来的窗口消息处理函数的句柄
WndProc = CallWindowProc(prevWndProc, hwnd, Msg, wParam, lParam)
End Function
执行以上的代码时,API函数GetWindowLong()、SetWindowLong()和CallWindowProc()的声明需要先加入到模块中,这些声明可通过VB所带的“API文本浏览器”获得。
四. 实例分析:一个定时音乐提醒程序
对于一个从事计算机工作的人来说,长时间坐在计算机前面工作是常有的事。持续的屏幕注视往往使得眼睛十分疲劳。最好是能每隔一段时间休息一下。但计算机工作尤其是编程往往会使人全神贯注,忘记了时间。我们可以设计一个定时音乐提醒程序,让它每隔一段时间就自动提醒自己休息一会儿。
下面就结合前述的任务栏编程思想给出具体的程序实现。该程序具有每隔一段时间就播放一段音乐提醒和到某个特定时刻播放音乐提醒的功能,并可以由用户自定义提醒时间、提醒内容以及音效。
1、新建一个工程,并加入五个窗体,分别命名为frmCaution、frmTipMess、frmSound、frmChangTime和frmAbout 。其中frmcaution为显示提醒消息的启动窗体,frmtipmess为用户自定义提醒内容的窗体,frmsound为用户自定义提醒声音的窗体,frmchangtime为用户自定义提醒时间方式的窗体,frmabout为“关于”信息显示窗体。
2、在frmcaution上加入一个菜单“mnuaa”,在“mnuaa”下加入菜单“设置提醒时间……”、“设置提醒消息……”、“音效……”、“关于……”、“退出”。菜单“mnuaa”就作为当单击图标时的弹出菜单。在frmcaution中加入一个panel,命名为“pnlmessage”,用于显示消息。在frmcaution中加入一个多媒体控件,命名为“mm”,用于播放音乐。在frmcaution中加入一个时间控件“Time1”。
3、在工程中加入一个标准模块mdlCommon,并在其中放置如下代码:
'API函数声明
Declare Function Shell_NotifyIcon Lib "shell32.dll" Alias "Shell_NotifyIconA" (ByVal dwMessage As Long, lpData As NOTIFYICONDATA) As Long
Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long) As Long
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function SetWindowPos Lib "user32" (ByVal hwnd As Long, ByVal hWndInsertAfter As Long, ByVal x As Long, ByVal y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long) As Long

Public prevWndProc As Long '原有的窗口消息处理函数
Public Message As String '消息内容
Public TipInterval As Integer '提示时间间隔
Public TipTime As String '提示时间
Public Time As Integer '累计时间

Function WndProc(ByVal hwnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long
If Msg = WM_USER + 100 Then
If lParam = WM_LBUTTONDOWN Or lParam = WM_RBUTTONDOWN Then
'当按下鼠标左键或右键时弹出菜单
frmCaution.PopupMenu frmCaution.mnuaa
End If
End If
'其它的消息交给系统处理,prevWndProc为原来的窗口消息处理函数的句柄
WndProc = CallWindowProc(prevWndProc, hwnd, Msg, wParam, lParam)
End Function
以上程序限于篇幅略去了数据结构和常量的定义。该定义可从“API文本浏览器”中获得。
4、编写frmcaution的Form_Load()过程。
Private Sub Form_Load()
Load frmSound
prevWndProc = GetWindowLong(Me.hwnd, GWL_WNDPROC) '获取系统确省的窗口消息处理函数句柄
SetWindowLong Me.hwnd, GWL_WNDPROC, AddressOf WndProc '指定WndProc为新的消息处理函数
'加入图标
Dim nid As NOTIFYICONDATA
nid.cbSize = Len(nid)
nid.hwnd = Me.hwnd
nid.uID = 9998
nid.uFlags = NIF_ICON + NIF_TIP + NIF_MESSAGE
nid.hIcon = Me.Icon
nid.uCallbackMessage = WM_USER + 100 '定义消息编号
Shell_NotifyIcon NIM_ADD, nid
'设置显示界面
pnlMessage.Left = (Me.ScaleWidth - pnlMessage.Width) / 2
pnlMessage.Top = (Me.ScaleHeight - pnlMessage.Height) / 2
TipInterval = 30 '提醒时间间隔默认为30分钟
TipTime = "" '提醒时间默认为空
Time = 0
Message = "亲爱的主人,建议您先休息5分钟!"
mm.Filename = frmSound!filSound.List(0)
End Sub
5、编写frmcaution的Form_Unload()过程。
Private Sub Form_Unload(Cancel As Integer)
Dim nid As NOTIFYICONDATA
'恢复原来的窗口消息处理函数
SetWindowLong Me.hwnd, GWL_WNDPROC, prevWndProc
'删除图标
nid.cbSize = Len(nid)
nid.hwnd = Me.hwnd
nid.uID = uID
Shell_NotifyIcon NIM_DELETE, nid
mm.Command = "close"
End Sub
6、编写Timer1_Timer()过程。
Private Sub Timer1_Timer()
If TipInterval <> 0 Then '若设置了提醒间隔
Time = Time + 1 '累计时间加1秒
If Time >= TipInterval * 60 Then '提醒时间间隔已到
pnlMessage.Caption = Message
Timer1.Enabled = False
mm.Filename = mm.Filename
mm.Command = "open"
mm.Command = "play"
Time = 0
frmCaution.Show
'窗口总显示在最前
SetWindowPos Me.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE Or SWP_NOMOVE
End If
End If
If TipTime <> "" Then '若设置了定时
Dim NowTime As Date
Dim DestTime As Date
NowTime = Format(Now(), "hh:nn:ss")
DestTime = Format(TipTime, "hh:nn:ss")
If NowTime = DestTime Then '定时时间已到
pnlMessage.Caption = Message
Timer1.Enabled = False
mm.Filename = mm.Filename
mm.Command = "open"
mm.Command = "play"
frmCaution.Show
SetWindowPos Me.hwnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE Or SWP_NOMOVE
End If
End If
End Sub

以上就给出了定时音乐提醒程序的大致过程。该程序运行时,确省情况下每隔30分钟就会跳出一个提醒窗口,同时播放一段音乐。当用户单击任务栏提示区中的图标就会弹出一个菜单,然后用户可以选择需要执行的功能。该程序的一些其它功能如“设置提醒时间形式”、“设置音效”等因与本文主题没多少联系在此不再细述。
定时音乐提醒程序在Microsoft Visual Basic 6.0中文企业版、Windows98环境下调试通过。读者若需要完整的源程序请E-mail Martinet@sohu.com与笔者联系。

……

相关阅读