romieb69
asked on
How to determine 3rd party command button state
Good afternoon experts! I have written a low level keyboard hook that is specifically looking to trap the key down event of the enter key. What I'm looking to do is to find out if a particular OK button has focus when the enter key is pressed. I have the windows handle of the OK button that I'm monitoring.
It seems at some point I read something about an API call out there that could find out the state of a command button but can not find what I'm looking for.
Any suggestions are greatly appreciated! I've attached the code that I have written so far.
Thanks!!!
R
It seems at some point I read something about an API call out there that could find out the state of a command button but can not find what I'm looking for.
Any suggestions are greatly appreciated! I've attached the code that I have written so far.
Thanks!!!
R
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Reflection
Imports System.Threading
Public Class clsKeyboardHook
Public Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Integer) As Integer
Public Declare Function SetWindowsHookEx Lib "user32" _
Alias "SetWindowsHookExA" (ByVal idHook As Integer, _
ByVal lpfn As KeyboardHookDelegate, ByVal hmod As Integer, _
ByVal dwThreadId As Integer) As Integer
Private Declare Function GetAsyncKeyState Lib "user32" _
(ByVal vKey As Integer) As Integer
Private Declare Function CallNextHookEx Lib "user32" _
(ByVal hHook As Integer, _
ByVal nCode As Integer, _
ByVal wParam As Integer, _
ByVal lParam As KBDLLHOOKSTRUCT) As Integer
Public Structure KBDLLHOOKSTRUCT
Public vkCode As Integer
Public scanCode As Integer
Public flags As Integer
Public time As Integer
Public dwExtraInfo As Integer
End Structure
' Low-Level Keyboard Constants
Private Const HC_ACTION As Integer = 0
Private Const LLKHF_EXTENDED As Integer = &H1
Private Const LLKHF_INJECTED As Integer = &H10
Private Const LLKHF_DOWN As Integer = &H81
' Virtual Keys
Public Const VK_ENTER = &HD
Private Const WH_KEYBOARD_LL As Integer = 13&
Public KeyboardHandle As Integer
Private k_btnClicked As Boolean = False
Public Property BtnClicked() As Boolean
Get
Return k_btnClicked
End Get
Set(ByVal value As Boolean)
k_btnClicked = value
End Set
End Property
Public Function IsHooked( _
ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean
If (Hookstruct.vkCode = VK_ENTER) Then
Return True
End If
'Note: you can add any key to this routine if you need to hook something other than enter
Return False
End Function
Public Function KeyboardCallback(ByVal Code As Integer, _
ByVal wParam As Integer, _
ByRef lParam As KBDLLHOOKSTRUCT) As Integer
If (Code = HC_ACTION) Then
If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
'Here I determine if the enter key down event took place on the OK button I'm intercepting
BtnClicked = True
Return 1
End If
End If
Return CallNextHookEx(KeyboardHandle, _
Code, wParam, lParam)
End Function
Public Delegate Function KeyboardHookDelegate( _
ByVal Code As Integer, _
ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
As Integer
<MarshalAs(UnmanagedType.FunctionPtr)> _
Private callback As KeyboardHookDelegate
Public Function HookKeyboard(ByVal btnHndl As Integer) As Boolean
callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)
KeyboardHandle = SetWindowsHookEx( _
WH_KEYBOARD_LL, callback, _
Marshal.GetHINSTANCE( _
[Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)
If CheckHooked() Then
Return True
Else
Return False
End If
End Function
Public Function CheckHooked() As Boolean
If Not (Hooked()) Then
Throw New Exception("Could not set keyboard hook")
Return False
Else
Return True
End If
End Function
Private Function Hooked()
Hooked = KeyboardHandle <> 0
End Function
Public Sub UnhookKeyboard()
If (Hooked()) Then
Call UnhookWindowsHookEx(KeyboardHandle)
End If
End Sub
End Class
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Alrighty then...try using SendMessage() with BM_GETSTATE and check for a return value of BST_FOCUS:
http://msdn.microsoft.com/en-us/library/bb775988(VS.85).aspx
=)
http://msdn.microsoft.com/en-us/library/bb775988(VS.85).aspx
=)
ASKER
Aaahhh yeah good idea. I'll give it a shot and see if I can make that do it on Monday. Thanks!!
ASKER
Hey IM ...
I'm just now getting a chance to play with this again. I've given up on SendKeys... you can find out what a button's state is (depressed etc) through this avenue but not whether or not it has focus. So I've gone back to the first suggestion you have made.
Not quite making it work and I suspect I'm calling AttachThreadInput incorrectly.
On attempt number 1 I tried GetWindowThreadProcessId(w inHndl, intPID) where winHndl is the actual window that contains the button. On attempt number 2 I tried GetWindowThreadProcessId(b tnHndl, intPID) where btnHndl is the actual button being monitored.
I'm missing something ...the boolean blnAttach is always false.
I'm just now getting a chance to play with this again. I've given up on SendKeys... you can find out what a button's state is (depressed etc) through this avenue but not whether or not it has focus. So I've gone back to the first suggestion you have made.
Not quite making it work and I suspect I'm calling AttachThreadInput incorrectly.
On attempt number 1 I tried GetWindowThreadProcessId(w
I'm missing something ...the boolean blnAttach is always false.
Class...
<DllImport("user32", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> _
Public Shared Function AttachThreadInput(ByVal idAttach As Integer, ByVal idAttachTo As Integer, ByVal fAttach As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Shared Function GetWindowThreadProcessId(ByVal handle As IntPtr, ByRef processId As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
Public Shared Function GetFocus() As IntPtr
End Function
Public Function KeyboardCallback(ByVal Code As Integer, _
ByVal wParam As Integer, _
ByRef lParam As KBDLLHOOKSTRUCT) As Integer
Dim ptrOK As IntPtr
Dim intPID As Integer
Dim blnAttach As Boolean = False
Dim thCurThread As Thread
If (Code = HC_ACTION) Then
If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
thCurThread = Thread.CurrentThread()
'Here I determine if the enter key down event took place on the OK button I'm intercepting
'GetWindowThreadProcessId(winHndl, intPID)
GetWindowThreadProcessId(btnHndl, intPID)
AttachThreadInput(thCurThread.ManagedThreadId, intPID, blnAttach)
If blnAttach Then
ptrOK = GetFocus()
If ptrOK.ToInt32 = btnHndl Then
BtnClicked = True
btnHndl = 0
Return 1
End If
End If
End If
End If
Return CallNextHookEx(KeyboardHandle, _
Code, wParam, lParam)
If (Code = HC_ACTION) Then
If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
thCurThread = Thread.CurrentThread()
'Here I determine if the enter key down event took place on the OK button I'm intercepting
'GetWindowThreadProcessId(winHndl, intPID)
GetWindowThreadProcessId(btnHndl, intPID)
AttachThreadInput(thCurThread.ManagedThreadId, intPID, blnAttach)
If blnAttach Then
ptrOK = GetFocus()
If ptrOK.ToInt32 = btnHndl Then
BtnClicked = True
btnHndl = 0
Return 1
End If
End If
End If
End If
Return CallNextHookEx(KeyboardHandle, _
Code, wParam, lParam)
End Function
Hmmm...haven't used it in awhile. You need the PID for your thread and the PID for the external thread.
Next you use AttachThreadInput() to attach your thread to the external thread.
Something like:
Dim ThreadID1 As Integer = GetWindowThreadProcessId(M e.Handle, 0) ' <-- Our PID
Dim ThreadID2 As Integer = GetWindowThreadProcessId(b tnHndl, 0) ' <-- External PID
Call AttachThreadInput(ThreadID 1, ThreadID2, True) ' <-- Attach
' ... use GetFocus() in here ...
Call AttachThreadInput(ThreadID 1, ThreadID2, False) ' <-- Unattach
What kind of app are you using though? I've used "Me.Handle" which assumes a WinForms app and that the code is running in the context of the Form. You may need to pass in a form/control into your class or we can figure out a different route to get your PID (possibly through Process class).
Next you use AttachThreadInput() to attach your thread to the external thread.
Something like:
Dim ThreadID1 As Integer = GetWindowThreadProcessId(M
Dim ThreadID2 As Integer = GetWindowThreadProcessId(b
Call AttachThreadInput(ThreadID
' ... use GetFocus() in here ...
Call AttachThreadInput(ThreadID
What kind of app are you using though? I've used "Me.Handle" which assumes a WinForms app and that the code is running in the context of the Form. You may need to pass in a form/control into your class or we can figure out a different route to get your PID (possibly through Process class).
ASKER
This code is a class in a DLL that is called by a win form app when I need to activate my keyboard hook delegate...
So I was thinking that something like this:
Dim thCurThread As Thread
thCurThread = Thread.CurrentThread() 'set thCurThread to the current thread
intMyPID = thCurThread.ManagedThreadI d 'obtain the current threads ID
AttachThreadInput(intMyPID , intBtnPID, blnAttach)
Interestingly enough intMyPID is always 10 ...
Also I read somewhere that the first parameter of AttachThreadInput can't be a system thread. I wonder if that is what I'm actually providing it...
I've attached all of the code in the class thus far.
So I was thinking that something like this:
Dim thCurThread As Thread
thCurThread = Thread.CurrentThread() 'set thCurThread to the current thread
intMyPID = thCurThread.ManagedThreadI
AttachThreadInput(intMyPID
Interestingly enough intMyPID is always 10 ...
Also I read somewhere that the first parameter of AttachThreadInput can't be a system thread. I wonder if that is what I'm actually providing it...
I've attached all of the code in the class thus far.
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Reflection
Imports System.Threading
Public Class clsKeyboardHook
<DllImport("user32", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> _
Public Shared Function AttachThreadInput(ByVal idAttach As Integer, ByVal idAttachTo As Integer, ByVal fAttach As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Shared Function GetWindowThreadProcessId(ByVal handle As IntPtr, ByRef processId As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
Public Shared Function GetFocus() As IntPtr
End Function
Public Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Integer) As Integer
Public Declare Function SetWindowsHookEx Lib "user32" _
Alias "SetWindowsHookExA" (ByVal idHook As Integer, _
ByVal lpfn As KeyboardHookDelegate, ByVal hmod As Integer, _
ByVal dwThreadId As Integer) As Integer
Private Declare Function GetAsyncKeyState Lib "user32" _
(ByVal vKey As Integer) As Integer
Private Declare Function CallNextHookEx Lib "user32" _
(ByVal hHook As Integer, _
ByVal nCode As Integer, _
ByVal wParam As Integer, _
ByVal lParam As KBDLLHOOKSTRUCT) As Integer
Public Structure KBDLLHOOKSTRUCT
Public vkCode As Integer
Public scanCode As Integer
Public flags As Integer
Public time As Integer
Public dwExtraInfo As Integer
End Structure
' Low-Level Keyboard Constants
Private Const HC_ACTION As Integer = 0
Private Const LLKHF_EXTENDED As Integer = &H1
Private Const LLKHF_INJECTED As Integer = &H10
Private Const LLKHF_DOWN As Integer = &H81
' Virtual Keys
Public Const VK_ENTER = &HD
Private Const WH_KEYBOARD_LL As Integer = 13&
Public KeyboardHandle As Integer
Public btnHndl As Integer
Public winHndl As Integer
Private k_btnClicked As Boolean = False
Public Property BtnClicked() As Boolean
Get
Return k_btnClicked
End Get
Set(ByVal value As Boolean)
k_btnClicked = value
End Set
End Property
Public Function IsHooked( _
ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean
If (Hookstruct.vkCode = VK_ENTER) Then
Return True
End If
'Note: you can add any key to this routine if you need to hook something other than enter
Return False
End Function
Public Function KeyboardCallback(ByVal Code As Integer, _
ByVal wParam As Integer, _
ByRef lParam As KBDLLHOOKSTRUCT) As Integer
Dim ptrOK As IntPtr
Dim intMyPID As Integer = 0
Dim intBtnPID As Integer = GetWindowThreadProcessId(btnHndl, 0)
Dim blnAttach As Boolean = False
Dim thCurThread As Thread
If (Code = HC_ACTION) Then
If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
thCurThread = Thread.CurrentThread()
intMyPID = thCurThread.ManagedThreadId
'Here I determine if the enter key down event took place on the OK button I'm intercepting
AttachThreadInput(intMyPID, intBtnPID, blnAttach)
If blnAttach Then
ptrOK = GetFocus()
If ptrOK.ToInt32 = btnHndl Then
BtnClicked = True
btnHndl = 0
Return 1
End If
End If
End If
End If
Return CallNextHookEx(KeyboardHandle, _
Code, wParam, lParam)
End Function
Public Delegate Function KeyboardHookDelegate( _
ByVal Code As Integer, _
ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
As Integer
<MarshalAs(UnmanagedType.FunctionPtr)> _
Private callback As KeyboardHookDelegate
Public Function HookKeyboard(ByVal btnHndlPassIn As Integer, ByVal winHndlInsuranceScr As Integer) As Boolean
callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)
btnHndl = btnHndlPassIn
winHndl = winHndlInsuranceScr
KeyboardHandle = SetWindowsHookEx( _
WH_KEYBOARD_LL, callback, _
Marshal.GetHINSTANCE( _
[Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)
If CheckHooked() Then
Return True
Else
Return False
End If
End Function
Public Function CheckHooked() As Boolean
If Not (Hooked()) Then
Throw New Exception("Could not set keyboard hook")
Return False
Else
Return True
End If
End Function
Private Function Hooked()
Hooked = KeyboardHandle <> 0
End Function
Public Sub UnhookKeyboard()
If (Hooked()) Then
Call UnhookWindowsHookEx(KeyboardHandle)
End If
End Sub
End Class
I'm not that good at the lower level stuff...I think you need ID() instead of ManagedThreadID():
intMyPID = Thread.CurrentThread.ID
intMyPID = Thread.CurrentThread.ID
ASKER
This solution got it done!
Idle Mind thanks for an excellent suggestion!!
Idle Mind thanks for an excellent suggestion!!
ASKER
Here's the mistakes I made in the last version of this
1. I tried to use this to set intMyPID
'thCurThread = Thread.CurrentThread()
'intMyPID = thCurThread.ManagedThreadI d
What I needed to use was this
intMyPID = AppDomain.GetCurrentThread Id
But it generates the following warning
Public Shared Function GetCurrentThreadId() As Integer' is obsolete: 'AppDomain.GetCurrentThrea dId has been deprecated because it does not provide a stable Id when managed threads are running on fibers (aka lightweight threads). To get a stable identifier for a managed thread, use the ManagedThreadId property on Thread. http://go.microsoft.com/fwlink/?linkid=14202'.
2. I was attaching to the btn pid incorrectly... this is the correct way:
AttachThreadInput(intMyPID , intBtnPID, True) with the third parameter being a boolean toggle
3. This one killed me and took forever to think to pop in there before calling GetFocus()
SetForegroundWindow(winHnd l)
Thanks again IM for the great suggestion and all the guidance.
1. I tried to use this to set intMyPID
'thCurThread = Thread.CurrentThread()
'intMyPID = thCurThread.ManagedThreadI
What I needed to use was this
intMyPID = AppDomain.GetCurrentThread
But it generates the following warning
Public Shared Function GetCurrentThreadId() As Integer' is obsolete: 'AppDomain.GetCurrentThrea
2. I was attaching to the btn pid incorrectly... this is the correct way:
AttachThreadInput(intMyPID
3. This one killed me and took forever to think to pop in there before calling GetFocus()
SetForegroundWindow(winHnd
Thanks again IM for the great suggestion and all the guidance.
Imports System.Runtime.InteropServices
Imports System.Drawing
Imports System.Reflection
Imports System.Threading
Public Class clsKeyboardHook
<DllImport("user32", CharSet:=CharSet.Ansi, SetLastError:=True, ExactSpelling:=True)> _
Public Shared Function AttachThreadInput(ByVal idAttach As Integer, ByVal idAttachTo As Integer, ByVal fAttach As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, SetLastError:=True)> _
Public Shared Function GetWindowThreadProcessId(ByVal handle As IntPtr, ByRef processId As Integer) As Integer
End Function
<DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> _
Public Shared Function GetFocus() As IntPtr
End Function
Public Declare Function UnhookWindowsHookEx Lib "user32" _
(ByVal hHook As Integer) As Integer
Public Declare Function SetWindowsHookEx Lib "user32" _
Alias "SetWindowsHookExA" (ByVal idHook As Integer, _
ByVal lpfn As KeyboardHookDelegate, ByVal hmod As Integer, _
ByVal dwThreadId As Integer) As Integer
Private Declare Function GetAsyncKeyState Lib "user32" _
(ByVal vKey As Integer) As Integer
Private Declare Function CallNextHookEx Lib "user32" _
(ByVal hHook As Integer, _
ByVal nCode As Integer, _
ByVal wParam As Integer, _
ByVal lParam As KBDLLHOOKSTRUCT) As Integer
Public Structure KBDLLHOOKSTRUCT
Public vkCode As Integer
Public scanCode As Integer
Public flags As Integer
Public time As Integer
Public dwExtraInfo As Integer
End Structure
' Low-Level Keyboard Constants
Private Const HC_ACTION As Integer = 0
Private Const LLKHF_EXTENDED As Integer = &H1
Private Const LLKHF_INJECTED As Integer = &H10
Private Const LLKHF_DOWN As Integer = &H81
' Virtual Keys
Public Const VK_ENTER = &HD
Private Const WH_KEYBOARD_LL As Integer = 13&
Public KeyboardHandle As Integer
Public btnHndl As Integer
Public winHndl As Integer
Public intMyPID As Integer = 0
Public intBtnPID As Integer
Private k_btnClicked As Boolean = False
Public Property BtnClicked() As Boolean
Get
Return k_btnClicked
End Get
Set(ByVal value As Boolean)
k_btnClicked = value
End Set
End Property
Public Function IsHooked( _
ByRef Hookstruct As KBDLLHOOKSTRUCT) As Boolean
If (Hookstruct.vkCode = VK_ENTER) Then
Return True
End If
'Note: you can add any key to this routine if you need to hook something other than enter
Return False
End Function
Public Function KeyboardCallback(ByVal Code As Integer, _
ByVal wParam As Integer, _
ByRef lParam As KBDLLHOOKSTRUCT) As Integer
Dim ptrOK As IntPtr
If (Code = HC_ACTION) Then
If (IsHooked(lParam)) Then 'is hooked will only be true on enter key
'thCurThread = Thread.CurrentThread()
'intMyPID = thCurThread.ManagedThreadId
'intMyPID = AppDomain.GetCurrentThreadId
'Here I determine if the enter key down event took place on the OK button I'm intercepting
AttachThreadInput(intMyPID, intBtnPID, True)
SetForegroundWindow(winHndl)
ptrOK = GetFocus()
If ptrOK.ToInt32 = btnHndl Then
BtnClicked = True
btnHndl = 0
blnAttach = False
AttachThreadInput(intMyPID, intBtnPID, False)
Return 1
End If
End If
End If
AttachThreadInput(intMyPID, intBtnPID, False)
Return CallNextHookEx(KeyboardHandle, _
Code, wParam, lParam)
End Function
Public Delegate Function KeyboardHookDelegate( _
ByVal Code As Integer, _
ByVal wParam As Integer, ByRef lParam As KBDLLHOOKSTRUCT) _
As Integer
<MarshalAs(UnmanagedType.FunctionPtr)> _
Private callback As KeyboardHookDelegate
Public Function HookKeyboard(ByVal btnHndlPassIn As Integer, ByVal winHndlInsuranceScr As Integer) As Boolean
Dim thCurThread As Thread
callback = New KeyboardHookDelegate(AddressOf KeyboardCallback)
btnHndl = btnHndlPassIn
intBtnPID = GetWindowThreadProcessId(btnHndl, 0)
winHndl = winHndlInsuranceScr
KeyboardHandle = SetWindowsHookEx( _
WH_KEYBOARD_LL, callback, _
Marshal.GetHINSTANCE( _
[Assembly].GetExecutingAssembly.GetModules()(0)).ToInt32, 0)
If CheckHooked() Then
intMyPID = AppDomain.GetCurrentThreadId
thCurThread = Thread.CurrentThread()
'AttachThreadInput(intMyPID, intBtnPID, True)
Return True
Else
Return False
End If
End Function
Public Function CheckHooked() As Boolean
If Not (Hooked()) Then
Throw New Exception("Could not set keyboard hook")
Return False
Else
Return True
End If
End Function
Private Function Hooked()
Hooked = KeyboardHandle <> 0
End Function
Public Sub UnhookKeyboard()
If (Hooked()) Then
Call UnhookWindowsHookEx(KeyboardHandle)
End If
End Sub
End Class
Yay!...glad you were able to hammer something together. =)
ASKER
Isn't there something like GetButtonState or something similar that I can provide the winId for that button to and find out what it's various properties are? Or is that kind of functionality limited to my own app and not necessairly a button running in an external application?