Re: Icon in system tray doesn't process mouse clicks for a dll form
- From: "Kevin Provance" <casey@xxxxxxxxxxx>
- Date: Wed, 23 Nov 2005 21:27:50 -0500
Mike -
Awesome exmaple. I had to say that. Very impressive.
- Kev
"MikeD" <nobody@xxxxxxxxxxx> wrote in message
news:u8Zq1nI8FHA.4036@xxxxxxxxxxxxxxxxxxxxxxx
>
> "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
>
>
>
.
- References:
- Prev by Date: Re: procedure doesn't works if the program compiled to native code...
- Next by Date: Re: Icon in system tray doesn't process mouse clicks for a dll form
- 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 for
- Index(es):
Loading