Link to home
Start Free TrialLog in
Avatar of Crash2100
Crash2100Flag for United States of America

asked on

Hide specific system tray icons

Is there any way I can use VB to hide a specific system tray icon?  I found this API code that will hide the whole collection of tray icons, but I can't seem to figure out how to pull a single icon out of this so I can hide it.

Public Function HideTaskBarIcons()
    Dim FindClass As Long, Handle As Long
    FindClass& = FindWindow("Shell_TrayWnd", "")
    Handle& = FindWindowEx(FindClass&, 0, "TrayNotifyWnd", vbNullString)
    ShowWindow Handle&, 0
End Function

I also found this code that I think I can modify to do what I want, but I haven't figured out how exactly to get the buttons to hide.  I had to modify the API calls like this to get the list to work:
   http://www.visualbasiccode.com/Asp/showsn.asp?theID=11610

    hWndTray = FindWindow("Shell_TrayWnd", vbNullString)
    hWndTray2 = FindWindowEx(hWndTray, 0, "TrayNotifyWnd", vbNullString)
    hWndTray3 = FindWindowEx(hWndTray2, 0, "SysPager", vbNullString)
    hWndToolBar = FindWindowEx(hWndTray3, 0, "ToolbarWindow32", vbNullString)

I tried adding a For..Next loop to go through all the buttons, it finds the button I want to hide, but I haven't figured out how exactly to hide it.  Either that, or the system tray needs to be refreshed or something after it's hidden.
        Dim Ob As IAccessible
        AccessibleObjectFromWindow ByVal hWndToolBar, OBJID_CLIENT, UID1, Ob

        For iBtnIndex = 0 To iTrayButtonsCount
            If Ob.accName(Av(iBtnIndex)) = "PeerGuardian" Then
                SendMessage hWndToolBar&, TB_HIDEBUTTON, iBtnIndex, 0
                SendMessage hWndToolBar&, TB_SAVERESTOREA, True, vbNull
            End If
        Next iBtnIndex
ASKER CERTIFIED SOLUTION
Avatar of Ark
Ark
Flag of Russian Federation image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Crash2100

ASKER

That is really neat, is that something you wrote yourself?
Yes, I forgot to add copyrights :)
All code is mine. This version (with icon extraction) works for Win ME/2000/XP/2003 OS only. You can find my previous version (works on all OS, but can not extract icons into ListView - it uses a trick with Tray tooltips) at http://www.freevbcode.com/ShowCode.Asp?ID=3291
I managed to take the code you gave me and I came up with this function that shows and hides icons.  I was wondering, is there any way I can do this more simply, without having to include all of those modules?  Possibly with a SendMessage API call or something?  If not, it's ok, I'm just curious.

Private bIsIconVisible As Boolean

Private Sub SetTrayIconVisible(strEXEName As String, bVisible As Boolean)
   Dim ti As TRAY_INFO
   
   Dim i As Long
   Dim pic As StdPicture
   Dim nIcon As Long
   Dim idx As Long
   Dim li As ListItem
       
   idx = 1
   ti = GetTrayInfo
   For i = 0 To ti.nCount - 1
      If LCase(ti.TII(i).sExecutable) Like "*" & strEXEName Then
        Dim nid As NOTIFYICONDATA
        nid.cbSize = Len(nid)
       
        With ti.TII(i)
            nid.hWnd = .hWnd
            nid.uID = .uID
            nid.uFlags = NIF_STATE
            nid.dwState = 0
            nid.dwStateMask = NIS_HIDDEN
        End With
       
        nid.dwState = IIf(bVisible = True, NIS_SHOWING, NIS_HIDDEN)
        Shell_NotifyIcon NIM_MODIFY, nid
        'MsgBox "Sysdoc is " & IIf(bVisible = True, "showing", "hidden")
      End If
   Next i
   
End Sub
Hi
Youn need
 .hWnd, .uID and .sExecutable variables, so you can remove all other staff. Also, you can deal with NT OS only.
I don't care if it will only work with NT, I'm writing this for XP machines anyway.

What exactly do you mean remove everything but .hWnd, .uID and .sExecutable?  Remove the things from the TRAY_ICON_INFO type?

Basically, I'm trying to figure out if there's another way to do this with just a few lines of code, rather than having all the code in those four modules included in the project.  That's why I was wondering if you could do it with just a SendMessage API call or something.  I did try removing the modules one by one, but the way the code in them is all tied together, and I kept getting errors when I was trying to figure out how to isolate the code so it wouldn't require what was in the other module.
Hi
Actually, all my code use SendMessage API. The problem with interprocess memory communication is in the 4th member of this API  - lParam. When you don't use this member (TB_BUTTONCOUNT, for example) - everything is OK. But when you use it as a pounter to structure, which must be filled with this call (TB_GETBUTTONINFO) - there is a problem. The variable, holding this structure, declared in YOUR process and it's pointer belong to YOUR process memory space. Remote process (Shell in your case) have its own memory space, and same memory address may be empty or allocated with another variable/function code etc. So, SendMessage write TBBUTTON structure not in your process address, but in its own process. To use SendMessage in this case, you have to allocate enough memory in remote process, write initial structure there and pass this remote address to SendMessage. Then read structure back into your process and parse it parameters.

Pseudo code for your task for XP can be following:

1. Get Tray ToolBar hwnd:
    hTrayWnd = FindWindow("Shell_TrayWnd", vbNullString)
    hTrayNotifyWnd = FindWindowEx(hTrayWnd, 0, "TrayNotifyWnd", vbNullString)
    hTraySysPager = FindWindowEx(hTrayNotifyWnd, 0, "SysPager", vbNullString)
    hTB = FindWindowEx(hTraySysPager, 0, "ToolbarWindow32", vbNullString)

and check if there is ANY icon:
    nCount = SendMessage(hTB, TB_BUTTONCOUNT, 0, ByVal 0&) - exit from function if nCount = 0

2. Get Shell ProcessId:
    tid = GetWindowThreadProcessId(hTB, pid)

3. Open this process with required rights:
    hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE, False, pid)

4. Allocate memory for structures in remote process:
    Private Type TBBUTTONINFO ''Toolbar button info
       cbSize As Long
       dwMask As Long
       idCommand As Long
       iImage As Long
       fsState As Byte
       fsStyle As Byte
       cx As Integer
       lParam As Long  'for TrayNotyfy it's a pointer to NOTIFYICONDATA structure
       pszText As Long ' Not "As String" - Bug in previous declaration!
       cchText As Long
    End Type

    Dim tbi As TBBUTTONINFO
    tbiAddr = VirtualAllocEx(hProcess, 0, Len(tbi), MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)

5. Initialize structure and copy it to remote address. In your case all you need from structure is LPARAM (you don't need icon,text etc.):
    tbi.cbSize = Len(tbi)
    tbi.dwMask = TBIF_LPARAM Or TBIF_BYINDEX
    Call WriteProcessMemory(hProcess, ByVal tbiAddr, tbi, Len(tbi), lWritten)

6. In a For..Next loop for each button, call SendMessage, using REMOTE address and read info from remote address:
    Call SendMessage(hTB, TB_GETBUTTONINFO, i, ByVal tbiAddr)
    Call ReadProcessMemory(hProcess, ByVal tbiAddr, tbi, Len(tbi), lWritten)

7. As I found from memory investigation, tbi.lParam contain a pointer to NOTIFYICONDATA structure, so extract this structure from remote process:
    Call ReadProcessMemory(hProcess, ByVal tbi.lParam, ii, Len(ii), lWritten)

8. Now ii(ICONINFO) structure contain all info you need. You can collect these info into array (like in my sample) or check for sExecutable just here:
     sExecutable = TrimNULL(StrConv(ii.lpszInfo, vbFromUnicode))
     If LCase(ti.TII(i).sExecutable) Like "*" & strEXEName Then
' rest code

9. Free memory in remote process and close its handle:  
    VirtualFreeEx hProcess, ByVal tbiAddr, 0, MEM_RELEASE
    CloseHandle hProcess
Thanks for all the help!