ElektroStudios ElektroStudios - 2 months ago 36
Vb.net Question

TBBUTTON struct not working with SendMessage

I'm trying to send the TB_GETBUTTON message to get info about the buttons inside this Toolbar control marked in red color:

enter image description here

( The System tray notification area )

The problem is that when I send the message, the Explorer refreshes itself, is very annonying because all the desktop refreshes, and also I'm not getting the proper values with the TBBUTTON structure definition that I'm using, I tested three different definitions, those with unions from pinvoke.net, and the one published here by @David Heffernan.

I'm running the code below in a 64-Bit Windows 10 and with the x64 config set in my project properties.

How can I fix the struct and the annonying system's refresh?.

These are the relevant definitions I'm using:

Const WM_USER As Integer = &H400
Const TB_BUTTONCOUNT As Integer = (WM_USER + 24)
Const TB_GETBUTTON As Integer = (WM_USER + 23)
' Toolbar values are defined in "CommCtrl.h" Windows SDK header files.

<StructLayout(LayoutKind.Sequential)>
Public Structure TBBUTTON64
Public iBitmap As Integer
Public idCommand As Integer
Public fsState As Byte
Public fsStyle As Byte
<MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst:=6)> ' 6 on x64
Public bReserved As Byte()
Public dwData As UIntPtr
Public iString As IntPtr
End Structure

<DllImport("User32.dll", SetLastError:=True)>
Public Shared Function SendMessage(ByVal hwnd As IntPtr,
ByVal msg As Integer,
ByVal wParam As IntPtr,
ByVal lParam As IntPtr
) As IntPtr
End Function

<SuppressUnmanagedCodeSecurity>
<DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto, BestFitMapping:=False, ThrowOnUnmappableChar:=True)>
Public Shared Function FindWindow(ByVal lpClassName As String,
ByVal lpWindowName As String
) As IntPtr
End Function

<SuppressUnmanagedCodeSecurity>
<DllImport("User32.dll", SetLastError:=True, CharSet:=CharSet.Auto, BestFitMapping:=False, ThrowOnUnmappableChar:=True)>
Public Shared Function FindWindowEx(ByVal hwndParent As IntPtr,
ByVal hwndChildAfter As IntPtr,
ByVal strClassName As String,
ByVal strWindowName As String
) As IntPtr
End Function


And this is the code to test them:

Dim tskBarHwnd As IntPtr =
NativeMethods.FindWindow("Shell_TrayWnd", Nothing)

Dim systrayBarHwnd As IntPtr =
NativeMethods.FindWindowEx(tskBarHwnd, IntPtr.Zero, "TrayNotifyWnd", Nothing)

Dim sysPagerHwnd As IntPtr =
NativeMethods.FindWindowEx(systrayBarHwnd, IntPtr.Zero, "SysPager", Nothing)

Dim ntfyBarHwnd As IntPtr =
NativeMethods.FindWindowEx(sysPagerHwnd, IntPtr.Zero, "ToolbarWindow32", Nothing)

Dim buttonCount As Integer =
NativeMethods.SendMessage(ntfyBarHwnd, TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32()

For index As Integer = 0 To (buttonCount - 1)

Dim btInfo As New TBBUTTON64
Dim alloc As IntPtr = Marshal.AllocHGlobal(Marshal.SizeOf(GetType(TBBUTTON64)))

Marshal.StructureToPtr(btInfo, alloc, fDeleteOld:=True)
NativeMethods.SendMessage(ntfyBarHwnd, TB_GETBUTTON, New IntPtr(index), alloc)
Marshal.PtrToStructure(Of TBBUTTON64)(alloc)
Marshal.FreeHGlobal(alloc)

' This line always prints "00000"
Console.WriteLine(btInfo.iBitmap &
btInfo.fsState &
btInfo.fsStyle &
btInfo.idCommand &
btInfo.iString.ToInt32())
Next index

Answer

You can't send the TB_GETBUTTON message to windows in another process and get valid results, because TB_GETBUTTON needs to manipulate a pointer to a struct, and window messages that do that have to marshal the struct. Very few windows messages do that. TB_GETBUTTONCOUNT works because it doesn't need to be marshaled.