Solved

Where do the systray items come from?

Posted on 2004-09-26
21
711 Views
Last Modified: 2008-01-09
Hi experts...

I've been trying to figure out how to obtain a list of tasks including icon, name, and parent process that reside in the systray from VB6 and am at a loss  8^(

The easiest wat to explain what I want to do is if for example I wanted to make my own systray replacement, that would when run include the current list of items, and also poll periodically to see if any have been closed or new ones opened, as well as access to the ContextMenu to be able to access the parent process from the Icon

I took a look at SHNotifyIcon, and all you can do is Add, Modify and Delete an Icon of your own. There seems to be no easy way to access tray icons from other applications

Obviously I'd rather have a nice clean OS friendly method, but any hacky kludge will do ;^) as long as it gets the job done. I can always then dissect it and clean it up (possibly...)

Seems to be quite a difficult task, so I'm offering 250 points for a result

Thanks in advance
0
Comment
Question by:FireW0lf
  • 9
  • 6
  • 4
21 Comments
 
LVL 85

Accepted Solution

by:
Mike Tomlinson earned 125 total points
ID: 12156889
After searching EE and the net, I don't believe this is possible using VB alone...though it would be great if somebody proved me wrong.  =)

Here is a solution in C++ posted by EE expert NickRepin:
http://www.experts-exchange.com/Programming/Programming_Platforms/Win_Prog/Q_10103887.html

In the authors profile:
http://www.experts-exchange.com/M_315020.html

You can go to his website...
http://www.geocities.com/nickrepin/

...and can get the source code:
http://www.geocities.com/nickrepin/winprog/shellico.txt

Regards,

Idle_Mind
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12156904
Hi Idle_Mind

I've been looking into this for quite a while, and scoured thru EE too - I've seen this code:

http://www.experts-exchange.com/Programming/Programming_Languages/Visual_Basic/Q_20096375.html

Which apparently works on W9x, but I'm after code for NT based OS (W2Kpro and WXP)

I've not managed to decode it all to be able to redo it

Would sure appreciate any help...
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 12157329
I saw that link awhile ago as well, but it returns blank strings on my Win XP Pro system, just as AzraSound reported for NT.  I'm not sure what modifications are necessary (if it's possible at all) to make it work with current versions of Windows as the code is beyond my skill level. =o

Also, after studying the code again, I believe it only enumerates the ToolTips belonging to the icons in the tray.  It doesn't actually build a NOTIFYICONDATA structure that you can use to manipulate the icons with.

Both Ark and AzraSound are still active here at EE.  Perhaps one will see this question if you post a remark in that old PAQ...

~IM
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12161909
I'm looking at the code, and in particular the bit:

Private Function GetTrayNotifyWnd() As Long
   GetTrayNotifyWnd = FindWindowEx(FindWindow("Shell_TrayWnd", vbNullString), _
0, "TrayNotifyWnd", vbNullString)
End Function

Why only go as deep as TrayNotifyWnd ? Using Spy++ I see ToolbarWindow32 is the window that actually holds the icons... Wouldn't this be a better place to look in NT5+ ?
Or am I just talking thru my hat?  8^)

0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 12162015
The ToolBarWindow32 designation means that window is an instance of the ToolBar component.  It only displays icons and relays messages back to its owner when the mouse is over it or clicked.  Use Spy++ to view the toolbars on some other apps (like Internet Explorer for example) and you will find ToolBarWindow32 there as well.

With that in mind,  I don't think it would contain any information other than the icon image itself and possibly the tooltip.  You would more than this to add/remove/modify the tray icons themselves.

~IM
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12162077
Okay, I see where you're coming from

But when I run the program, I get a list of 9 items (all blank) when I only have 4 visible items in my systray. I had 6, but an explorer crash (bah!) made my CMedia Mixer icon and SMTP server icon disappear to leave 4

Maybe its an error in the NT Shared Mem module and the rest of the code is fine?????
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12162151
And actually, I dont want to add/remove/modify the existing icons. I want the tooltip and icon information, as well as the context menu and owner app. That would give me enough to make a copy of the systray in a vb form. Add a timer to rescan the info periodically and there you go

So, maybe some of the info in the ToolbarWindow32 class might be needed....(?)
0
 
LVL 85

Expert Comment

by:Mike Tomlinson
ID: 12162194
>> I get a list of 9 items (all blank) when I only have 4 visible items in my systray.

It is possible to have an icon in the tray but in a hidden state.

>> as well as the context menu and owner app.

It will probably be next to impossible to get the context menu for the tray icons.  The menus are not displayed by the tray but by the owner app in response to messages from the tray.

~IM
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12162256
»It is possible to have an icon in the tray but in a hidden state.

I think it would be possible. Reading in the msdn it talks about checking for system state changes to refresh the systray icon or info. Maybe my mixer app and smtp server don't actually poll for state changes, and so the app still thinks its notifyicon is still in the systray. Dunno whether the icon would still be held by the sytray - just not displayed, or whether it would simply release the icon 8^/

»It will probably be next to impossible to get the context menu for the tray icons.  The menus are not displayed by the tray but by the owner app in response to messages from the tray.

Well okay, the owner app would do, and I could always try and work out how to scan the app to see if I can find the Context Menu from within there...
0
Highfive Gives IT Their Time Back

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 
LVL 27

Expert Comment

by:Ark
ID: 12165746
Hi
I tried to send 2k/XP code which retrives tray icons. Unfortunatelly, EE doesn't support attaches - full app have size of 28k (4 modules + 1 form). Is it OK to post full source here?
0
 
LVL 27

Assisted Solution

by:Ark
Ark earned 125 total points
ID: 12166009
Ok, here is a code:

1. mTray.bas - main module to retrive info from system tray. For win2K, Me and XP tray is a toolbar (for w9x/NT it's a panel(picturebox):

'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------
Option Explicit

'Human readable tray icon data
Public Type TRAY_ICON_INFO
    hWnd As Long
    uID As Long
    uCallbackMessage As Long
    uFlags As Long
    iImage As Long
    hIcon As Long
    idCommand As Long
    dwState As Long
    dwStyle As Long
    sTip As String
    sExecutable As String
End Type

'Array of tray icons
Public Type TRAY_INFO
    nCount As Long
    hImageList As Long
    TII() As TRAY_ICON_INFO
End Type

'Internal tray icon info (almost same as NOTYFYICONDATA)
Public Type ICON_INFO
   hWnd As Long
   uID As Long
   uCallbackMessage As Long
   uFlags As Long
   dwUnknown As Long
   hIcon As Long
   lpszTip As Long 'String * 64
   dwState As Long
   dwStateMask As Long
   lpszInfo As String * 256
   dwUnion As Long
   lpszInfoTitle As Long 'String * 64
   dwInfoFlags As Long
End Type

'Real structure
Public Type NOTIFYICONDATA
   cbSize As Long
   hWnd As Long
   uID As Long
   uFlags As Long
   uCallbackMessage As Long
   hIcon As Long
   szTip As String * 128
   dwState As Long
   dwStateMask As Long
   szInfo As String * 256
   uTimeoutOrVersion As Long
   szInfoTitle As String * 64
   dwInfoFlags As Long
End Type

Public Const NIF_MESSAGE = 1
Public Const NIF_ICON = 2
Public Const NIF_TIP = 4
Public Const NIF_STATE = 8
Public Const NIF_ALL = NIF_MESSAGE Or NIF_ICON Or NIF_TIP Or NIF_STATE
Public Const NIS_HIDDEN = 1
Public Const NIM_ADD = &H0
Public Const NIM_MODIFY = &H1
Public Const NIM_DELETE = &H2

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 String
   cchText As Long
End Type
'UDT for retriving ToolTip text
Private Type TOOLTEXT
   sTipText As String * 80
End Type

Private Declare Function SendMessageA Lib "user32" (ByVal hWnd As Long, ByVal wMsg As
Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function SendMessageW Lib "user32" (ByVal hWnd As Long, ByVal wMsg As
Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal
lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1
As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Declare Sub CopyMemory Lib "Kernel32" Alias "RtlMoveMemory" (Destination As Any,
 Source As Any, ByVal Length As Long)
Private Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long,
lpdwProcessId As Long) As Long
Private Declare Function ReadProcessMemory Lib "Kernel32" (ByVal hProcess As Long,
lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As
Long) As Long
Private Declare Function WriteProcessMemory Lib "Kernel32" (ByVal hProcess As Long,
lpBaseAddress As Any, lpBuffer As Any, ByVal nSize As Long, lpNumberOfBytesWritten As
Long) As Long
'Private Declare Function GetImageCount Lib "comctl32" Alias "ImageList_GetImageCount"
(ByVal p As Long) As Long
 
Public Declare Function Shell_NotifyIcon Lib "shell32.dll" Alias "Shell_NotifyIconA" _
                         (ByVal dwMessage As Long, lpData As NOTIFYICONDATA) As Long
Public Const WM_USER = &H400&
Private Const TB_BUTTONCOUNT = (WM_USER + 24)
Private Const TB_GETBUTTONINFOW = (WM_USER + 63)
Private Const TB_GETBUTTONINFOA = (WM_USER + 65)
Private Const TB_GETIMAGELIST = (WM_USER + 49)
Private Const TB_GETBUTTONTEXTA = (WM_USER + 45)
Private Const TB_GETBUTTONTEXTW = (WM_USER + 75)

Private Const TBIF_IMAGE = &H1
Private Const TBIF_TEXT = &H2
Private Const TBIF_STATE = &H4
Private Const TBIF_STYLE = &H8
Private Const TBIF_LPARAM = &H10
Private Const TBIF_COMMAND = &H20
Private Const TBIF_SIZE = &H40
Private Const TBIF_ALL = TBIF_IMAGE Or TBIF_TEXT Or TBIF_STATE Or _
                         TBIF_STYLE Or TBIF_LPARAM Or TBIF_COMMAND _
                         Or TBIF_SIZE

Private Const TBIF_BYINDEX = &H80000000
Dim bInit As Boolean

Public Function GetTrayWnd() As Long
  GetTrayWnd = FindWindow("Shell_TrayWnd", vbNullString)
End Function

Public Function GetTrayNotifyWnd() As Long
  GetTrayNotifyWnd = FindWindowEx(GetTrayWnd, 0, "TrayNotifyWnd", vbNullString)
End Function

Public Function GetTraySysPager() As Long
  GetTraySysPager = FindWindowEx(GetTrayNotifyWnd, 0, "SysPager", vbNullString)
End Function

Public Function GetTrayNotifyToolBar() As Long
  Dim hParent As Long
  If WinVer = 0 Then GetWinVer
  If WinVer >= WinXP Then
     hParent = GetTraySysPager
  ElseIf ((WinVer = Win2K) Or (WinVer = WinME)) Then
     hParent = GetTrayNotifyWnd
  Else
     Exit Function
  End If
  GetTrayNotifyToolBar = FindWindowEx(hParent, 0, "ToolbarWindow32", vbNullString)
End Function

Public Function GetTrayIconCount() As Long
   Dim hTB As Long
   hTB = GetTrayNotifyToolBar
   GetTrayIconCount = SendMessageA(hTB, TB_BUTTONCOUNT, 0, ByVal 0&)
End Function

Public Function GetTrayInfo() As TRAY_INFO
   Dim hTB As Long
   Dim nCount As Long, i As Long, j As Long
   Dim tbi As TBBUTTONINFO
   Dim ti As TRAY_INFO
   Dim ii As ICON_INFO
   Dim tt As TOOLTEXT
   Dim tid As Long, pid As Long
   Dim hProcess As Long, lWritten As Long
   Dim hMapping As Long, hMapping2 As Long
   Dim lpSysShared As Long     'Shared memory pointer for TBBUTTONINFO
   Dim lpSysSharedTIP As Long  'Shared memory pointer for ToolTip
   
   hTB = GetTrayNotifyToolBar
   If hTB = 0 Then Exit Function 'Check if tray toolbar exists
   
   nCount = GetTrayIconCount
   If nCount = 0 Then Exit Function 'Check if icons exists
   
   ti.nCount = nCount
   ti.hImageList = SendMessageA(hTB, TB_GETIMAGELIST, 0, ByVal 0&)
   ReDim ti.TII(nCount - 1)
   tbi.cbSize = Len(tbi)
   tbi.dwMask = TBIF_ALL Or TBIF_BYINDEX
   tbi.cchText = Len(tt)
   tid = GetWindowThreadProcessId(hTB, pid)
   If IsWindowsNT Then
      lpSysShared = GetMemSharedNT(pid, Len(tbi), hProcess)
      lpSysSharedTIP = GetMemSharedNT(pid, Len(tt), hProcess)
      For i = 0 To nCount - 1
          WriteProcessMemory hProcess, ByVal lpSysShared, tbi, Len(tbi), lWritten
          Call SendMessageW(hTB, TB_GETBUTTONINFOW, i, ByVal lpSysShared)
          Call ReadProcessMemory(hProcess, ByVal lpSysShared, tbi, Len(tbi), lWritten)
          Call ReadProcessMemory(hProcess, ByVal tbi.lParam, ii, Len(ii), lWritten)
          WriteProcessMemory hProcess, ByVal lpSysSharedTIP, tt, Len(tt), lWritten
          Call SendMessageW(hTB, TB_GETBUTTONTEXTW, tbi.idCommand, ByVal
lpSysSharedTIP)
          Call ReadProcessMemory(hProcess, ByVal lpSysSharedTIP, tt, Len(tt), lWritten)
          With ti.TII(i)
            .dwState = tbi.fsState
            .dwStyle = tbi.fsStyle
            .hIcon = ii.hIcon
            .hWnd = ii.hWnd
            .idCommand = tbi.idCommand
            .iImage = tbi.iImage
            .sExecutable = TrimNULL(StrConv(ii.lpszInfo, vbFromUnicode))
            .sTip = TrimNULL(StrConv(tt.sTipText, vbFromUnicode))
            .uCallbackMessage = ii.uCallbackMessage
            .uFlags = ii.uFlags / &H100000
            .uID = ii.uID
          End With
      Next i
      FreeMemSharedNT hProcess, lpSysShared, Len(tbi)
      FreeMemSharedNT hProcess, lpSysSharedTIP, Len(tt)
   Else
      lpSysShared = GetMemShared95(Len(tbi), hMapping)
      lpSysSharedTIP = GetMemShared95(Len(tt), hMapping2)
      hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or
PROCESS_VM_WRITE, False, pid)
      For i = 0 To nCount - 1
          CopyMemory ByVal lpSysShared, tbi, Len(tbi)
          Call SendMessageA(hTB, TB_GETBUTTONINFOA, i, ByVal lpSysShared)
          CopyMemory tbi, ByVal lpSysShared, Len(tbi)
          Call ReadProcessMemory(hProcess, ByVal tbi.lParam, ii, Len(ii), lWritten)
          CopyMemory ByVal lpSysSharedTIP, tt, Len(tt)
          Call SendMessageA(hTB, TB_GETBUTTONTEXTA, tbi.idCommand, ByVal
lpSysSharedTIP)
          CopyMemory tt, ByVal lpSysSharedTIP, Len(tt)
          With ti.TII(i)
            .dwState = tbi.fsState
            .dwStyle = tbi.fsStyle
            .hIcon = CopyIcon(ii.hIcon)
            .hWnd = ii.hWnd
            .idCommand = tbi.idCommand
            .iImage = tbi.iImage
            .sExecutable = GetExeFromHandle(.hWnd)
            .sTip = TrimNULL(tt.sTipText)
            .uCallbackMessage = ii.uCallbackMessage
            .uFlags = ii.uFlags / &H100000
            .uID = ii.uID
          End With
      Next i
      FreeMemShared95 hMapping, lpSysShared
      FreeMemShared95 hMapping2, lpSysSharedTIP
      CloseHandle hProcess
   End If
   GetTrayInfo = ti
End Function

Public Function TrimNULL(startstr As String) As String
   Dim pos As Integer
   pos = InStr(startstr, Chr$(0))
   If pos Then
      TrimNULL = Left$(startstr, pos - 1)
      Exit Function
   End If
   TrimNULL = startstr
End Function
'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------

2. mSharedMemory.bas - module for working with shared memory (in-process memory for NT or file mapping for 9x), which allow interprocess memory communication:

'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------
Option Explicit
Public Enum OS_VER
    Win32
    Win95
    Win98
    WinME
    WinNT
    Win2K
    WinXP
    Win2003
End Enum
'=========Checking OS staff=============
Private Type OSVERSIONINFO
    dwOSVersionInfoSize As Long
    dwMajorVersion As Long
    dwMinorVersion As Long
    dwBuildNumber As Long
    dwPlatformId As Long
    szCSDVersion As String * 128
End Type
Private Declare Function GetVersionEx Lib "Kernel32" Alias "GetVersionExA"
(LpVersionInformation As OSVERSIONINFO) As Long

'========= Win95/98/ME Shared memory staff===============
Private Declare Function CreateFileMapping Lib "Kernel32" Alias "CreateFileMappingA"
(ByVal hFile As Long, ByVal lpFileMappigAttributes As Long, ByVal flProtect As Long,
ByVal dwMaximumSizeHigh As Long, ByVal dwMaximumSizeLow As Long, ByVal lpName As
String) As Long
Private Declare Function MapViewOfFile Lib "Kernel32" (ByVal hFileMappingObject As
Long, ByVal dwDesiredAccess As Long, ByVal dwFileOffsetHigh As Long, ByVal
dwFileOffsetLow As Long, ByVal dwNumberOfBytesToMap As Long) As Long
Private Declare Function UnmapViewOfFile Lib "Kernel32" (lpBaseAddress As Any) As Long
Const STANDARD_RIGHTS_REQUIRED = &HF0000
Const SECTION_QUERY = &H1
Const SECTION_MAP_WRITE = &H2
Const SECTION_MAP_READ = &H4
Const SECTION_MAP_EXECUTE = &H8
Const SECTION_EXTEND_SIZE = &H10
Const SECTION_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED Or SECTION_QUERY Or
SECTION_MAP_WRITE Or SECTION_MAP_READ Or SECTION_MAP_EXECUTE Or SECTION_EXTEND_SIZE
Const FILE_MAP_ALL_ACCESS = SECTION_ALL_ACCESS

Private Const PAGE_NOACCESS = &H1&
Private Const PAGE_READONLY = &H2&
Private Const PAGE_READWRITE = &H4&
Private Const PAGE_WRITECOPY = &H8&
Private Const PAGE_EXECUTE = &H10&
Private Const PAGE_EXECUTE_READ = &H20&
Private Const PAGE_EXECUTE_READWRITE = &H40&
Private Const PAGE_EXECUTE_WRITECOPY = &H80&
Private Const PAGE_GUARD = &H100&
Private Const PAGE_NOCACHE = &H200&

'============NT Shared memory staff======================
Public Declare Function OpenProcess Lib "Kernel32" (ByVal dwDesiredAccess As Long,
ByVal bInheritHandle As Long, ByVal dwProcessId As Long) As Long
Public Const PROCESS_VM_OPERATION = &H8
Public Const PROCESS_VM_READ = &H10
Public Const PROCESS_VM_WRITE = &H20
Public Const PROCESS_ALL_ACCESS = 0

Const MEM_COMMIT = &H1000
Const MEM_RESERVE = &H2000
Const MEM_DECOMMIT = &H4000
Const MEM_RELEASE = &H8000
Const MEM_FREE = &H10000
Const MEM_PRIVATE = &H20000
Const MEM_MAPPED = &H40000
Const MEM_TOP_DOWN = &H100000

Private Declare Function VirtualAllocEx Lib "Kernel32" (ByVal hProcess As Long, ByVal
lpAddress As Long, ByVal dwSize As Long, ByVal flAllocationType As Long, ByVal
flProtect As Long) As Long
Private Declare Function VirtualFreeEx Lib "Kernel32" (ByVal hProcess As Long,
lpAddress As Any, ByVal dwSize As Long, ByVal dwFreeType As Long) As Long
Private Declare Function CloseHandle Lib "Kernel32" (ByVal hObject As Long) As Long
Public WinVer As OS_VER

Public Function GetMemShared95(ByVal memSize As Long, hFile As Long) As Long
    hFile = CreateFileMapping(&HFFFFFFFF, 0, PAGE_READWRITE, 0, memSize, vbNullString)
    GetMemShared95 = MapViewOfFile(hFile, FILE_MAP_ALL_ACCESS, 0, 0, 0)
End Function

Public Sub FreeMemShared95(ByVal hFile As Long, ByVal lpMem As Long)
    UnmapViewOfFile lpMem
    CloseHandle hFile
End Sub

Public Function GetMemSharedNT(ByVal pid As Long, ByVal memSize As Long, hProcess As
Long) As Long
    hProcess = OpenProcess(PROCESS_VM_OPERATION Or PROCESS_VM_READ Or PROCESS_VM_WRITE,
 False, pid)
    GetMemSharedNT = VirtualAllocEx(ByVal hProcess, ByVal 0&, ByVal memSize,
MEM_RESERVE Or MEM_COMMIT, PAGE_READWRITE)
End Function

Public Sub FreeMemSharedNT(ByVal hProcess As Long, ByVal MemAddress As Long, ByVal
memSize As Long)
   Call VirtualFreeEx(hProcess, ByVal MemAddress, memSize, MEM_RELEASE)
   CloseHandle hProcess
End Sub

Public Function IsWindowsNT() As Boolean
   If WinVer = 0 Then GetWinVer
   IsWindowsNT = (WinVer > WinME)
End Function

Public Sub GetWinVer()
   Dim verinfo As OSVERSIONINFO
   verinfo.dwOSVersionInfoSize = Len(verinfo)
   If (GetVersionEx(verinfo)) = 0 Then Exit Sub
   With verinfo
      Select Case .dwPlatformId
         Case 0: WinVer = Win32
         Case 1 'Win9x
             Select Case .dwMinorVersion
                Case 0:  WinVer = Win95
                Case 10: WinVer = Win98
                Case 90: WinVer = WinME
             End Select
         Case 2
             If .dwMajorVersion < 5 Then
                WinVer = WinNT
             Else
                Select Case .dwMinorVersion
                   Case 0: WinVer = Win2K
                   Case 1: WinVer = WinXP
                   Case 2: WinVer = Win2003
                End Select
             End If
      End Select
   End With
End Sub
'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------

3. mIcon.bas - module to retrive IPicture (VB stdPicture object) from hIcon handle:

'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------
Option Explicit

Public Enum ICON_SIZE
    ICON_SMALL
    ICON_LARGE
End Enum

Private Type PictDesc
    cbSizeofStruct As Long
    PicType As Long
    hImage As Long
    xExt As Long
    yExt As Long
End Type

Private Type Guid
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(0 To 7) As Byte
End Type

Private Declare Function OleCreatePictureIndirect Lib "olepro32.dll" (lpPictDesc As _
PictDesc, riid As Guid, ByVal fPictureOwnsHandle As Long, ipic As IPicture) As Long

Private Declare Function DestroyIcon Lib "user32" (ByVal hIcon As Long) As Long
Public Declare Function CopyIcon Lib "user32" (ByVal hIcon As Long) As Long

Private Declare Function ExtractIconEx Lib "Shell32" Alias "ExtractIconExA" _
        (ByVal szFile As String, ByVal nIconIndex As Long, phIconLarge As Long, _
        phIconSmall As Long, ByVal nIcons As Long) As Long

Private Declare Function SHChangeIconDialog Lib "Shell32" Alias "#62" _
        (ByVal hOwner As Long, ByVal szFilename As String, _
        ByVal Reserved As Long, lpIconIndex As Long) As Long
Const MAX_PATH = 260

Public Function IconToPicture(ByVal hIcon As Long) As StdPicture
    If hIcon = 0 Then Exit Function
    Dim oNewPic As Picture
    Dim tPicConv As PictDesc
    Dim IGuid As Guid
    With tPicConv
       .cbSizeofStruct = Len(tPicConv)
       .PicType = vbPicTypeIcon
       .hImage = hIcon
    End With
    With IGuid
        .Data1 = &H7BF80980
        .Data2 = &HBF32
        .Data3 = &H101A
        .Data4(0) = &H8B
        .Data4(1) = &HBB
        .Data4(2) = &H0
        .Data4(3) = &HAA
        .Data4(4) = &H0
        .Data4(5) = &H30
        .Data4(6) = &HC
        .Data4(7) = &HAB
    End With
    OleCreatePictureIndirect tPicConv, IGuid, True, oNewPic
    Set IconToPicture = oNewPic
End Function

Public Function BitmapToPicture(ByVal hBmp As Long) As StdPicture
    Dim oNewPic As Picture, tPicConv As PictDesc, IGuid As Guid
    With tPicConv
       .cbSizeofStruct = Len(tPicConv)
       .PicType = vbPicTypeBitmap
       .hImage = hBmp
    End With
    With IGuid
       .Data1 = &H20400
       .Data4(0) = &HC0
       .Data4(7) = &H46
    End With
    OleCreatePictureIndirect tPicConv, IGuid, True, oNewPic
    Set BitmapToPicture = oNewPic
End Function

Public Function GetIconFromDialog(Optional IconSize As ICON_SIZE = ICON_SMALL) As Long
   Dim sFileName As String
   Dim nIconIdx As Long, hSmallIcon As Long, hLargeIcon As Long
   sFileName = String(MAX_PATH, 0)
   If SHChangeIconDialog(0, sFileName, MAX_PATH, nIconIdx) Then
      If IsWindowsNT Then sFileName = StrConv(sFileName, vbFromUnicode)
      If ExtractIconEx(sFileName, nIconIdx, hLargeIcon, hSmallIcon, 1) > 0 Then
         If IconSize = ICON_SMALL Then
            GetIconFromDialog = CopyIcon(hSmallIcon)
         Else
            GetIconFromDialog = CopyIcon(hLargeIcon)
         End If
         DestroyIcon hSmallIcon
         DestroyIcon hLargeIcon
      End If
   End If
End Function
'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------

4. mProcesses.bas - module to retrive executable name from hwnd (required fro w9x/Me only):

'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------
Option Explicit

Private Const TH32CS_SNAPPROCESS As Long = 2&
Private Const MAX_PATH As Long = 260

Private Type PROCESSENTRY32
    dwSize As Long
    cntUsage As Long
    th32ProcessID As Long
    th32DefaultHeapID As Long
    th32ModuleID As Long
    cntThreads As Long
    th32ParentProcessID As Long
    pcPriClassBase As Long
    dwflags As Long
    szexeFile As String * MAX_PATH
End Type

Public Declare Function GetWindowThreadProcessId Lib "user32" (ByVal hWnd As Long,
lpdwProcessId As Long) As Long
Public Declare Function CreateToolhelpSnapshot Lib "Kernel32" Alias
"CreateToolhelp32Snapshot" (ByVal lFlgas As Long, ByVal lProcessID As Long) As Long
Public Declare Function ProcessFirst Lib "Kernel32" Alias "Process32First" (ByVal
hSnapshot As Long, uProcess As PROCESSENTRY32) As Long
Public Declare Function ProcessNext Lib "Kernel32" Alias "Process32Next" (ByVal
hSnapshot As Long, uProcess As PROCESSENTRY32) As Long
Public Declare Sub CloseHandle Lib "Kernel32" (ByVal hPass As Long)

Public Function GetExeFromHandle(hWnd As Long) As String
    Dim threadID As Long, processID As Long, hSnapshot As Long
    Dim uProcess As PROCESSENTRY32, rProcessFound As Long
    Dim i As Integer, szExename As String
    threadID = GetWindowThreadProcessId(hWnd, processID)
    If threadID = 0 Or processID = 0 Then Exit Function
    hSnapshot = CreateToolhelpSnapshot(TH32CS_SNAPPROCESS, 0&)
    If hSnapshot = -1 Then Exit Function
    uProcess.dwSize = Len(uProcess)
    rProcessFound = ProcessFirst(hSnapshot, uProcess)
    Do While rProcessFound
        If uProcess.th32ProcessID = processID Then
            i = InStr(1, uProcess.szexeFile, Chr(0))
            If i > 0 Then szExename = Left$(uProcess.szexeFile, i - 1)
            Exit Do
        Else
            rProcessFound = ProcessNext(hSnapshot, uProcess)
        End If
    Loop
    Call CloseHandle(hSnapshot)
    GetExeFromHandle = szExename
End Function
'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------

5. Form1.frm - form for demo application. Require MSCOMCTL.OCX (I used ListView). Copy following code to Notepad and save as Form1.frm:

'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------
VERSION 5.00
Object = "{831FDD16-0C5C-11D2-A9FC-0000F8754DA1}#2.0#0"; "MSCOMCTL.OCX"
Begin VB.Form Form1
   Caption         =   "Form1"
   ClientHeight    =   3630
   ClientLeft      =   165
   ClientTop       =   855
   ClientWidth     =   9375
   LinkTopic       =   "Form1"
   ScaleHeight     =   3630
   ScaleWidth      =   9375
   StartUpPosition =   3  'Windows Default
   Begin MSComctlLib.ImageList ImageList1
      Left            =   240
      Top             =   2520
      _ExtentX        =   1005
      _ExtentY        =   1005
      BackColor       =   -2147483643
      MaskColor       =   12632256
      UseMaskColor    =   0   'False
      _Version        =   393216
   End
   Begin MSComctlLib.ListView ListView1
      Height          =   2055
      Left            =   240
      TabIndex        =   0
      Top             =   120
      Width           =   9015
      _ExtentX        =   15901
      _ExtentY        =   3625
      LabelEdit       =   1
      LabelWrap       =   -1  'True
      HideSelection   =   -1  'True
      FullRowSelect   =   -1  'True
      _Version        =   393217
      ForeColor       =   -2147483640
      BackColor       =   -2147483643
      BorderStyle     =   1
      Appearance      =   1
      NumItems        =   0
   End
   Begin VB.Menu mnuTray
      Caption         =   "&Tray"
      Begin VB.Menu mnuRefresh
         Caption         =   "&Refresh"
      End
      Begin VB.Menu mnuEdit
         Caption         =   "&Edit"
         Begin VB.Menu mnuDelete
            Caption         =   "&Delete"
         End
         Begin VB.Menu mnuHide
            Caption         =   "&Hide"
         End
         Begin VB.Menu mnuShow
            Caption         =   "&Show"
         End
         Begin VB.Menu mnuModify
            Caption         =   "&Modify"
            Begin VB.Menu mnuModifyToolTip
               Caption         =   "&Tooltip"
            End
            Begin VB.Menu mnuModifyIcon
               Caption         =   "&Icon"
            End
         End
      End
      Begin VB.Menu sep1
         Caption         =   "-"
      End
      Begin VB.Menu mnuExit
         Caption         =   "E&xit"
      End
   End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit

Private Declare Function LoadIcon Lib "user32" Alias "LoadIconA" (ByVal hInstance As Long, ByVal lpIconName As Long) As Long
Dim ti As TRAY_INFO

Private Sub EnumTray()
   Dim i As Long
   Dim pic As StdPicture
   Dim nIcon As Long
   Dim idx As Long
   Dim li As ListItem
       
   idx = 1
   Set li = ListView1.SelectedItem
   If Not li Is Nothing Then idx = ListView1.SelectedItem.Index
   Set li = Nothing
   ListView1.ListItems.Clear
   Set ListView1.SmallIcons = Nothing
   ImageList1.ListImages.Clear
   ImageList1.ImageWidth = 16
   ImageList1.ImageHeight = 16
   ImageList1.ListImages.Add , , Me.Icon
   Set ListView1.SmallIcons = ImageList1
   ti = GetTrayInfo
   For i = 0 To ti.nCount - 1
      Set pic = IconToPicture(ti.TII(i).hIcon)
      If Not pic Is Nothing Then
         ImageList1.ListImages.Add , , pic
         nIcon = ImageList1.ListImages.Count
         Set pic = Nothing
      Else
         nIcon = 0
      End If
      With ListView1.ListItems.Add(, , ti.TII(i).sExecutable, , nIcon)
           .ToolTipText = ti.TII(i).sTip
           .SubItems(1) = ti.TII(i).hWnd
           .SubItems(2) = ti.TII(i).uID
           .SubItems(3) = ti.TII(i).uCallbackMessage & " (0x" & Hex(ti.TII(i).uCallbackMessage) & ")" 'GetStyle(ti.TII(i).dwStyle)
           .SubItems(4) = GetState(ti.TII(i).dwState)
      End With
   Next i
   If idx > ListView1.ListItems.Count - 1 Then idx = ListView1.ListItems.Count - 1
   ListView1.ListItems(idx).Selected = True
   ListView1.ListItems(idx).EnsureVisible
End Sub

Private Sub Form_Load()
  Caption = "Who lives in my system tray?"
  Icon = IconToPicture(LoadIcon(0, 32514))
  ListView1.View = lvwReport
  ListView1.ColumnHeaders.Add , , "Icon and Executable", 4500
  ListView1.ColumnHeaders.Add , , "hWnd", 1000, 1
  ListView1.ColumnHeaders.Add , , "uID", 1000, 1
  ListView1.ColumnHeaders.Add , , "Callback msg", 1500, 2
  ListView1.ColumnHeaders.Add , , "State", 1000, 1
  Me.Width = 9210
  EnumTray
End Sub

Private Sub Form_Resize()
   If WindowState = vbMinimized Then Exit Sub
   ListView1.Move 0, 0, ScaleWidth, ScaleHeight
End Sub

Private Sub ListView1_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
   Dim li As ListItem
   If Button = vbRightButton Then
      Set li = ListView1.HitTest(x, y)
      If Not li Is Nothing Then
         li.Selected = True
         CheckShowHide
         PopupMenu mnuEdit
      End If
   End If
End Sub

Private Sub mnuDelete_Click()
   Dim nid As NOTIFYICONDATA
   nid.cbSize = Len(nid)
   With ti.TII(ListView1.SelectedItem.Index - 1)
        nid.hWnd = .hWnd
        nid.uID = .uID
   End With
   Shell_NotifyIcon NIM_DELETE, nid
   EnumTray
End Sub

Private Sub mnuEdit_Click()
   CheckShowHide
End Sub

Private Sub mnuExit_Click()
   Unload Me
End Sub

Private Sub mnuHide_Click()
   Dim nid As NOTIFYICONDATA
   nid.cbSize = Len(nid)
   With ti.TII(ListView1.SelectedItem.Index - 1)
        nid.hWnd = .hWnd
        nid.uID = .uID
        nid.uFlags = NIF_STATE
        nid.dwState = NIS_HIDDEN
        nid.dwStateMask = NIS_HIDDEN
   End With
   Shell_NotifyIcon NIM_MODIFY, nid
   EnumTray
End Sub

Private Sub mnuModifyIcon_Click()
   Dim nid As NOTIFYICONDATA
   Dim hIcon As Long
   nid.cbSize = Len(nid)
   With ti.TII(ListView1.SelectedItem.Index - 1)
        hIcon = GetIconFromDialog(ICON_SMALL)
        If hIcon = 0 Then Exit Sub
        nid.hWnd = .hWnd
        nid.uID = .uID
        nid.uFlags = NIF_ICON
        nid.hIcon = hIcon
   End With
   Shell_NotifyIcon NIM_MODIFY, nid
   EnumTray
End Sub

Private Sub mnuModifyToolTip_Click()
   Dim nid As NOTIFYICONDATA
   Dim sTip As String
   nid.cbSize = Len(nid)
   With ti.TII(ListView1.SelectedItem.Index - 1)
        sTip = InputBox("Enter new tooltip text:", "Modify tray icon tooltip", .sTip)
        If sTip = "" Then Exit Sub
        nid.hWnd = .hWnd
        nid.uID = .uID
        nid.uFlags = NIF_TIP
        nid.szTip = Trim(sTip) & Chr(0)
   End With
   Shell_NotifyIcon NIM_MODIFY, nid
   EnumTray
End Sub

Private Sub mnuRefresh_Click()
   EnumTray
End Sub

Private Sub mnuShow_Click()
   Dim nid As NOTIFYICONDATA
   nid.cbSize = Len(nid)
   With ti.TII(ListView1.SelectedItem.Index - 1)
        nid.hWnd = .hWnd
        nid.uID = .uID
        nid.uFlags = NIF_STATE
        nid.dwState = 0
        nid.dwStateMask = NIS_HIDDEN
   End With
   Shell_NotifyIcon NIM_MODIFY, nid
   EnumTray
End Sub

Private Sub CheckShowHide()
  Dim i As Integer
  mnuShow.Enabled = ((ti.TII(ListView1.SelectedItem.Index - 1).dwState And 8) = 8)
  mnuHide.Enabled = Not mnuShow.Enabled
End Sub

Private Function GetStyle(ByVal iStyle As Long) As String
   Dim sStyle As String
' Check for TBSTATE_ constants
   sStyle = "Button"
   If (iStyle And 1) = 1 Then sStyle = "Separator"
   If (iStyle And 2) = 2 Then sStyle = sStyle & " ,Check"
   If (iStyle And 4) = 4 Then sStyle = sStyle & " ,Group"
   If (iStyle And 8) = 8 Then sStyle = sStyle & " ,DropDown"
   If (iStyle And 10) = 10 Then sStyle = sStyle & " ,Autosize"
   If (iStyle And 20) = 20 Then sStyle = sStyle & " ,No prefix"
   If (iStyle And 100) = 100 Then sStyle = sStyle & " ,Tooltips"
   If (iStyle And 200) = 200 Then sStyle = sStyle & " ,Wrappable"
   If (iStyle And 400) = 400 Then sStyle = sStyle & " ,AltDrag"
   If (iStyle And 800) = 800 Then sStyle = sStyle & " ,Flat"
   If (iStyle And 1000) = 1000 Then sStyle = sStyle & " ,List"
   If (iStyle And 2000) = 2000 Then sStyle = sStyle & " ,Custom erase"
   If (iStyle And 4000) = 4000 Then sStyle = sStyle & " ,Register drop"
   If (iStyle And 8000) = 8000 Then sStyle = sStyle & " ,Transparent"
   GetStyle = sStyle
End Function

Private Function GetState(ByVal iState As Long) As String
   Dim sState As String
' Check for TBSTATE_ constants
   If (iState And 1) = 1 Then sState = sState & " ,Checked"
   If (iState And 2) = 2 Then sState = sState & " ,Pressed"
   If (iState And 4) = 4 Then sState = sState & " ,Enabled"
   If (iState And 8) = 8 Then sState = sState & " ,Hidden"
   If (iState And 10) = 10 Then sState = sState & " ,Inderterminate"
   If (iState And 20) = 20 Then sState = sState & " ,Wrap"
   If (iState And 40) = 40 Then sState = sState & " ,Ellipses"
   If (iState And 80) = 80 Then sState = sState & " ,Marked"
   If Len(sState) > 0 Then GetState = Mid(sState, 3)
End Function

Private Sub mnuTray_Click()
   CheckShowHide
End Sub
'---------------8<------------------8<-----------------------8<------------------8<-----------------8<--------------

Enjoy :)
0
 
LVL 27

Expert Comment

by:Ark
ID: 12167114
PS

To show popup menu, just change SendMessageA declaration from Private to Public (mTray.bas) and ListView1_MouseDown event (form1.frm):

Private Sub ListView1_MouseDown(Button As Integer, Shift As Integer, x As Single, y As Single)
   Dim li As ListItem
   Const WM_RBUTTONUP = &H205
   If Button = vbRightButton Then
      Set li = ListView1.HitTest(x, y)
      If Not li Is Nothing Then
         li.Selected = True
         With ti.TII(ListView1.SelectedItem.Index - 1)
            SendMessageA .hWnd, .uCallbackMessage, 0, ByVal WM_RBUTTONUP
         End With
'         CheckShowHide
'         PopupMenu mnuEdit
      End If
   End If
End Sub

Now after right clicking on lv item you'll get tray popup menu
0
 
LVL 27

Expert Comment

by:Ark
ID: 12167129
Oops, one important thing:

'At form1.frm
Private Declare Function SetForegroundWindow Lib "user32" (ByVal hWnd As Long) As Long

'..........
         With ti.TII(ListView1.SelectedItem.Index - 1)
'           To ensure menu dissapears after mouse clicked outside menu
            Call SetForegroundWindow(.hWnd)
            SendMessageA .hWnd, .uCallbackMessage, 0, ByVal WM_RBUTTONUP
         End With
'.............
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12168530
Thx Ark

I dont have time to check this code today, but will over the next 2 days or so, and if it works, the points are yours, along with a virtual kiss on the rude bits...

Not even looked at it yet, just pasted it into a txt file for now. Is it your own code, or based on that old W9x code? Just wondering.
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12180157
Hi Ark, just pasted your code into a project, and added the mods

It works to a degree, which is great, but the name of the app doesnt work (cept for print spool) but just shows mostly gibberish and mostly "?"s

Also some of my apps (Outlook2003, advanced smtp server, peer-peer sharing program, and C-Media mixer so far) show, but dont show a popup menu.

I know the smtp server and sharing program seem to have different menus (icons on the side of the menuitems)

My OS is Windows2000Pro - might make a difference if you wrote it on Wxp

Any ideas?
0
 
LVL 27

Expert Comment

by:Ark
ID: 12198003
Hello
I'm under winXP SP1 and using MUI
Try to check following string at tray.bas\GetTrayInfo function
       .sExecutable = TrimNULL(StrConv(ii.lpszInfo, vbFromUnicode)) - probably you OS doesn't use Unicode? And you can use just  .sExecutable = ii.lpszInfo

As for menu: probably, those apps use MouseDown event instead of MouseUp? I used:
SendMessageA .hWnd, .uCallbackMessage, 0, ByVal WM_RBUTTONUP
try:
SendMessageA .hWnd, .uCallbackMessage, 0, ByVal WM_RBUTTONDOWN
Also, they may require wParam (0 in my case). Usually such apps use wParam for mouse position (wParam = MAKEDWORD (pt.x,pt.y) where pt is GetCursorPos pt)
0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12202553
I've checked that.... and added the position too - doesnt help

On the menus that dont display, the .uCallbackMessage is 0, so maybe an addressing prob?

And I've had a quick look at the app name bit, and thats just total gibberish thats being brought out  8^(
0
 
LVL 27

Expert Comment

by:Ark
ID: 12531425
Hi
Seems you WinXP moves back from Unicode to ANSI :) Above code works fine with WinXP_NO_SP, but fail with WinXP_SP1/SP2. Fix:
 Change:
'**********************************
   If IsWindowsNT Then
      lpSysShared = GetMemSharedNT(pid, Len(tbi), hProcess)
      lpSysSharedTIP = GetMemSharedNT(pid, Len(tt), hProcess)
      For i = 0 To nCount - 1
          WriteProcessMemory hProcess, ByVal lpSysShared, tbi, Len(tbi), lWritten
          Call SendMessageW(hTB, TB_GETBUTTONINFOW, i, ByVal lpSysShared)
          Call ReadProcessMemory(hProcess, ByVal lpSysShared, tbi, Len(tbi), lWritten)
          Call ReadProcessMemory(hProcess, ByVal tbi.lParam, ii, Len(ii), lWritten)
          WriteProcessMemory hProcess, ByVal lpSysSharedTIP, tt, Len(tt), lWritten
          Call SendMessageW(hTB, TB_GETBUTTONTEXTW, tbi.idCommand, ByVal
lpSysSharedTIP)
          Call ReadProcessMemory(hProcess, ByVal lpSysSharedTIP, tt, Len(tt), lWritten)
          With ti.TII(i)
            .dwState = tbi.fsState
            .dwStyle = tbi.fsStyle
            .hIcon = ii.hIcon
            .hWnd = ii.hWnd
            .idCommand = tbi.idCommand
            .iImage = tbi.iImage
            .sExecutable = TrimNULL(StrConv(ii.lpszInfo, vbFromUnicode))
            .sTip = TrimNULL(StrConv(tt.sTipText, vbFromUnicode))
            .uCallbackMessage = ii.uCallbackMessage
            .uFlags = ii.uFlags / &H100000
            .uID = ii.uID
          End With
      Next i
      FreeMemSharedNT hProcess, lpSysShared, Len(tbi)
      FreeMemSharedNT hProcess, lpSysSharedTIP, Len(tt)
   Else
'**********************************
 With:
'**********************************
   If IsWindowsNT Then
      lpSysShared = GetMemSharedNT(pid, Len(tbi), hProcess)
      lpSysSharedTIP = GetMemSharedNT(pid, Len(tt), hProcess)
      For i = 0 To nCount - 1
          WriteProcessMemory hProcess, ByVal lpSysShared, tbi, Len(tbi), lWritten
          Call SendMessageA(hTB, TB_GETBUTTONINFOA, i, ByVal lpSysShared)
          Call ReadProcessMemory(hProcess, ByVal lpSysShared, tbi, Len(tbi), lWritten)
          Call ReadProcessMemory(hProcess, ByVal tbi.lParam, ii, Len(ii), lWritten)
          WriteProcessMemory hProcess, ByVal lpSysSharedTIP, tt, Len(tt), lWritten
          Call SendMessageA(hTB, TB_GETBUTTONTEXTA, tbi.idCommand, ByVal lpSysSharedTIP)
          Call ReadProcessMemory(hProcess, ByVal lpSysSharedTIP, tt, Len(tt), lWritten)
          With ti.TII(i)
            .dwState = tbi.fsState
            .dwStyle = tbi.fsStyle
            .hIcon = ii.hIcon
            .hWnd = ii.hWnd
            .idCommand = tbi.idCommand
            .iImage = tbi.iImage
            .sExecutable = TrimNULL(StrConv(ii.lpszInfo, vbFromUnicode))
            .sTip = TrimNULL(tt.sTipText)
            .uCallbackMessage = ii.uCallbackMessage
            .uFlags = ii.uFlags / &H100000
            .uID = ii.uID
          End With
      Next i
      FreeMemSharedNT hProcess, lpSysShared, Len(tbi)
      FreeMemSharedNT hProcess, lpSysSharedTIP, Len(tt)
   Else
'**********************************

0
 
LVL 4

Author Comment

by:FireW0lf
ID: 12535959
Heheh thats great that you've got it sorted for WXPSP2, but I still want it to work with my own OS, which is 2000Pro....
0

Featured Post

Threat Intelligence Starter Resources

Integrating threat intelligence can be challenging, and not all companies are ready. These resources can help you build awareness and prepare for defense.

Join & Write a Comment

Introduction I needed to skip over some file processing within a For...Next loop in some old production code and wished that VB (classic) had a statement that would drop down to the end of the current iteration, bypassing the statements that were c…
Have you ever wanted to restrict the users input in a textbox to numbers, and while doing that make sure that they can't 'cheat' by pasting in non-numeric text? Of course you can do that with code you write yourself but it's tedious and error-prone …
Get people started with the utilization of class modules. Class modules can be a powerful tool in Microsoft Access. They allow you to create self-contained objects that encapsulate functionality. They can easily hide the complexity of a process from…
Show developers how to use a criteria form to limit the data that appears on an Access report. It is a common requirement that users can specify the criteria for a report at runtime. The easiest way to accomplish this is using a criteria form that a…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

19 Experts available now in Live!

Get 1:1 Help Now