Re: Icon in system tray doesn't process mouse clicks for a dll form
- From: "MikeD" <nobody@xxxxxxxxxxx>
- Date: Wed, 23 Nov 2005 18:59:38 -0500
"Mike" <Mike@xxxxxxxxxxxxxxxxxxxxxxxxx> wrote in message
news:00B88A34-C31D-42DA-8B42-33A2E74918D2@xxxxxxxxxxxxxxxx
> I'm using Shell_NotifyIcon to place an icon in the system tray when the
> user
> minimizes the application. The application is really just a form in a dll
> that is launced as a plugin from another application.
I actually have more I want to say. Just some tips and suggestions for you
to consider.
In order to make your DLL reusable (after all, that is one of the main
reasons for creating a DLL), I would recommend having your DLL raise events.
If this DLL is only ever going to be used by this ONE app, there's probably
not much sense in having the DLL; you may as well just write the code in the
EXE. By raising events, your DLL is not hard-coded for particular actions,
menu/control/form names, etc. It will also keep code in your main app
cleaner since the events will occur in that app. Maybe you're doing that
already, but it doesn't appear so. One problem you'll run into in doing this
IF you've done the systray icon the right way (by subclassing) is that you
can only raise events from a class module. This means your winproc function
(the callback of your subclass) needs a reference to the class module. You
*might* just think to declare a global object variable and set that in the
class module's Initialize event. Don't! If the DLL is used by 2 or more
apps at the same time, they'll stomp all over each other because global
variables in a DLL project are shared. IOW, if both AppA and AppB use your
systray DLL, the global object variable is "common". This is going to have
dire consequences for one of the two apps (most likely the first app to use
the DLL, since the second app would set a reference to a different instance
of the class module which would then be "seen" in the first app).
To deal with this, store the reference (which is a pointer and is really a
Long) to the class module in the USERDATA area of the form of the DLL (each
instance of the form is private). Since you're subclassing this form, you
get its hwnd in the callback procedure and can then easily get a reference
to the correct instance of the class module in your callback procedure. It's
actually very simple to do. To store the pointer in the USERDATA area of the
form, use this code in the class module's Initialize event:
-----BEGIN CODE
'This will allow us to obtain a reference to this class
'in the callback procedure. This is necessary so
'that we can raise events by calling the Friend functions
'in this class module.
Dim ptrObject As Long
CopyMemory ptrObject, Me, 4&
SetWindowLong frmSubclass.hWnd, GWL_USERDATA, ptrObject
-----END CODE
where frmSubclass is the form in your DLL. This form must always remain
loaded but NEVER made visible. Also, declare the following as appropriate:
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA"
(ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function GetWindowLong Lib "user32" Alias "GetWindowLongA"
(ByVal hWnd As Long, ByVal nIndex As Long) As Long
Public Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (pDest As
Any, pSrc As Any, ByVal ByteLen As Long)
Public Const GWL_USERDATA As Long = (-21)
Then, in your callback procedure (the winproc for your subclass), do this:
-----BEGIN CODE
Private Function TrayMessage(ByVal hWnd As Long, ByVal uMsg As Long, ByVal
wParam As Long, ByVal lParam As Long) As Long
Dim oTBNA As TBNAManager
Dim ptrObject As Long
'Get the pointer to our object
ptrObject = GetWindowLong(hWnd, GWL_USERDATA)
'Obtain a reference to that object from the pointer
CopyMemory oTBNA, ptrObject, 4&
'Determine the message that was received
Select Case uMsg
Case WM_NOTIFYICON
oTBNA.RaiseTrayEvent lParam, wParam
Case Else
'Message is not one we want to process. Send it on to
'Windows for default processing. This is ABSOLUTELY
'necessary.
TrayMessage = CallWindowProc(oTBNA.lpPrevWndProc, hWnd, uMsg,
wParam, lParam)
End Select
<whatever else you need to do>
'Destroy the local object reference
CopyMemory oTBNA, 0&, 4&
Set oTBNA = Nothing
End Function
-----END CODE
where TBNAManager is the name of the class module in your DLL. If you're
curious, TBNA stands for TaskBar Notification Area (which according to MS is
the correct name for the system tray). It is VERY important to destroy the
reference before this function exits. If you don't, bad things will happen.
Therefore, I would not recommend any Exit Sub statements. As you can see,
the TBNAManager also has a property for the address of the previous winproc
procedure. This needs to be assigned when you create the subclass.
I just have a single Friend method in the class module which I call from the
callback procedure and pass it wParam and lParam. This Friend method looks
like this:
-----BEGIN CODE
Friend Sub RaiseTrayEvent(ByVal lParam As Long, ByVal wParam As Long)
Dim oIcon As TrayIcon
Set oIcon = GetIcon(wParam)
If oIcon Is Nothing Then
Exit Sub
End If
Select Case lParam
Case WM_MOUSEMOVE
RaiseEvent MouseMove(oIcon)
Case WM_LBUTTONDOWN
RaiseEvent LeftButtonDown(oIcon)
Case WM_LBUTTONUP
RaiseEvent LeftButtonUp(oIcon)
Case WM_LBUTTONDBLCLK
RaiseEvent LeftButtonDoubleClick(oIcon)
Case WM_RBUTTONDOWN
RaiseEvent RightButtonDown(oIcon)
Case WM_RBUTTONUP
RaiseEvent RightButtonUp(oIcon)
Case WM_RBUTTONDBLCLK
RaiseEvent RightButtonDoubleClick(oIcon)
Case WM_MBUTTONDOWN
RaiseEvent MiddleButtonDown(oIcon)
Case WM_MBUTTONUP
RaiseEvent MiddleButtonUp(oIcon)
Case WM_MBUTTONDBLCLK
RaiseEvent MiddleButtonDoubleClick(oIcon)
Case WM_CONTEXTMENU
RaiseEvent ContextMenu(oIcon)
Case NIN_BALLOONSHOW
RaiseEvent BalloonShow(oIcon)
Case NIN_BALLOONHIDE
RaiseEvent BalloonHide(oIcon)
Case NIN_BALLOONTIMEOUT
RaiseEvent BalloonTimeOut(oIcon)
Case NIN_BALLOONUSERCLICK
RaiseEvent BalloonUserClick(oIcon)
Case NIN_SELECT
RaiseEvent EnterSelect(oIcon)
Case NIN_KEYSELECT
RaiseEvent KeySelect(oIcon)
End Select
End Sub
Private Function GetIcon(ByVal wParam As Long) As TrayIcon
Dim oIcon As TrayIcon
For Each oIcon In m_oIcons
If wParam = oIcon.ID Then
Exit For
End If
Next
Set GetIcon = oIcon
End Function
-----END CODE
TrayIcon is another class in the DLL which basically is just a wrapper for
the NOTIFYICONDATA structure and m_oIcons is a reference to yet another
class called TrayIcons which is a collection class for TrayIcon objects.
Also note that a reference to the TrayIcon object gets passed to each event
procedure.
Hopefully, this information will be useful to you in writing a robust DLL
that you can reuse in multiple apps. If you have any questions about
anything, feel free to ask. If your question pertains to how *I* do this
(by no means do I claim that how I've done this is the best way and I have
no doubt there are other ways), I'll answer as best as I can.
Oh, and just in case this isn't clear from the code, wParam from the
callback procedure is the ID you used for the uID member of the
NOTIFYICONDATA structure (as I said, TrayIcon is basically just a wrapper
for that structure).
--
Mike
Microsoft MVP Visual Basic
.
- Follow-Ups:
- Re: Icon in system tray doesn't process mouse clicks for a dll form
- From: Kevin Provance
- Re: Icon in system tray doesn't process mouse clicks for a dll form
- References:
- Prev by Date: Re: Icon in system tray doesn't process mouse clicks for a dll form
- Next by Date: Re: procedure doesn't works if the program compiled to native code...
- Previous by thread: Re: Icon in system tray doesn't process mouse clicks for a dll form
- Next by thread: Re: Icon in system tray doesn't process mouse clicks for a dll form
- Index(es):
Loading