Best practice for SendKeys or equivalent

R

Rich007

Hi,
I need to use some form of command to send the Enter key to Word.
I could use
SendKeys "~", True or SendKeys "{ENTER}", True
but I'm concerned about how these behave on international versions of Word
(e.g. German setting, etc.)

Also, I have looked at using API equivalents, first with keybd_event
(http://msdn.microsoft.com/en-us/library/ms646304(VS.85).aspx), but since
this says "Windows NT/2000/XP/Vista: This function has been superseded. Use
SendInput instead", I have also looked at SendInput
(http://msdn.microsoft.com/en-us/library/ms646310(VS.85).aspx)

I have to admit that while I have a pretty good understanding of VBA, APIs
look like a whole new world of pain.

Can anyone please provide me with just the code I need to send the Enter key
100% reliably on any PC running XP (and beyond)?

Cheers
Rich
 
K

Karl E. Peterson

Rich007 said:
Hi,
I need to use some form of command to send the Enter key to Word.
I could use
SendKeys "~", True or SendKeys "{ENTER}", True
but I'm concerned about how these behave on international versions of Word
(e.g. German setting, etc.)

Also, I have looked at using API equivalents, first with keybd_event
(http://msdn.microsoft.com/en-us/library/ms646304(VS.85).aspx), but since
this says "Windows NT/2000/XP/Vista: This function has been superseded. Use
SendInput instead", I have also looked at SendInput
(http://msdn.microsoft.com/en-us/library/ms646310(VS.85).aspx)

I have to admit that while I have a pretty good understanding of VBA, APIs
look like a whole new world of pain.

Can anyone please provide me with just the code I need to send the Enter key
100% reliably on any PC running XP (and beyond)?

Well, here's a SendInput-based replacement for SendKeys --
http://vb.mvps.org/samples/SendInput -- if you'd like to see how to implement that.
It's been out in the wild now for some time, with the only reports of problems
having do to with using Unicode characters. I believe those were addressed
successfully, as well. I'd sure like to know if anyone has any issues with it,
though! (That all said, re-reading the text on that page, I can't recall for the
life of me why I wrote it may not work in Office 2007. Weird.)
 
R

Rich007

Thanks Karl for your response. I'm feeling pretty vindicated on the whole
"API is too complicated to try to master" issue now!

So I downloaded your example an imported the MSendInput.bas into my Word
2003 VBA project.

If I want to send the "a" key, for example, would I use:
Call MySendKeys("a") ? This works great.

But what would I use for the input "data" to send the Enter key?

Thanks again.

Cheers
Rich
 
K

Karl E. Peterson

Rich007 said:
Thanks Karl for your response. I'm feeling pretty vindicated on the whole
"API is too complicated to try to master" issue now!

Well, that was actually a rather fun exercise, probably carried to the extreme for
publication. <g> This sample is really a demonstration of string parsing with a
state machine. Didn't have to be "that hard" but that did end up providing a very
versatile solution. That said, this is also an uncommonly difficult API to cut your
teeth on.
So I downloaded your example an imported the MSendInput.bas into my Word
2003 VBA project.

If I want to send the "a" key, for example, would I use:
Call MySendKeys("a") ? This works great.

But what would I use for the input "data" to send the Enter key?

It fully emulates VB(A)'s built-in SendKeys, so you'd do just the same by sending
either "{ENTER}" or "~". You could take a look into the BuildCollections method to
see how these shortcuts are managed.
 
R

Rich007

Thanks so much Karl,
I can now get the code to run using either Call MySendKeys("{ENTER}") or
Call MySendKeys("{~}").

However, I have noticed a very strange situation. Let me give you the
background (but if you are short of time, just jump to the very end of this
post).

I have a macro called "OnReturn" which is bound to the return key, i.e. it
runs everytime the return key is pressed. It is assigned in design time via
a macro called "AssignReturnKey", using the code"
KeyBindings.Add KeyCode:=BuildKeyCode(wdKeyReturn), KeyCategory:= _
wdKeyCategoryMacro, Command:="OnReturn"

All of these macros live in a global addin.

Now the OnReturn macro needs to do some stuff, then perform the return
keystroke as if the return key had never been assigned to a macro at all. To
do this, I came up with the following code for OnReturn:

Sub OnReturn()
'DO OTHER STUFF HERE...
UnAssignReturnKey
Call MySendKeys("{ENTER}", True)
AssignReturnKey
End Sub

The logic is this; first unassign the return key via a macro that runs the
line:
KeyBindings.Key(KeyCode:=BuildKeyCode(wdKeyReturn)).Clear
to unassign the return key binding.

Second, this is where I need to perform the unadulterated Return key-stroke.

Third, I reassign the key-binding using the AssignReturnKey macro that was
run during design time.

Both AssignReturnKey and UnassignReturnKey macros have code to catch errors
which is why I haven't included the full code for now.

So I have tried using various different methods for calling the "pure"
return key-stroke ( Selection.TypeParagraph or SendKeys "~", True )
and these worked ok, but I was looking for a bullet-proof way that would work
for all international key-boards and language settings.

Now when I use your MySendKeys("{ENTER}") the code jumps into a loop. It
hangs until I either click the mouse or press a keyboard button. If I hit
one of the extended keys, the Enter "executes" in combination with that
extended key. So the code hangs, until say I hit Ctrl, then I get a
pagebreak!

What I think is happening here is that when I hit Enter, the OnReturn macro
fires, this calls the UnAssignReturnKey macro to clear the keybinding, then
the MySendKeys("{ENTER}") is called which effectively re-hits the return key
and this then tries to run the OnReturn macro! This is my theory, I have no
way to prove it yet, but the MySendKeys("{ENTER}") shouldn't call OnReturn,
since the key was unbound in the previous line of code. Could there be some
kind of lag? The strangest thing is that if you replace the line:
Call MySendKeys("{ENTER}", True)
in OnReturn, with just:
SendKeys "~", True
(so using the original SendKeys rather than the API version) it works
perfectly!

Any ideas why the MySendKeys macro doesn't recognise that the return key has
just been unbound? Thanks again (especially if you've read this far!).

Cheers
Rich
PS. Having re-read that I can see this could be quite confusing, so here is
the full code that I think is runable (if you have the MSendInput.bas
imported from your site):

Sub AssignReturnKey()
'Assigns a macro to intercept the Return key
'
Dim AddinPath As String

AddinPath = AddIns("test.dot").Path
CustomizationContext = Templates(AddinPath & "\test.dot")
KeyBindings.Add KeyCode:=BuildKeyCode(wdKeyReturn), KeyCategory:= _
wdKeyCategoryMacro, Command:="OnReturn"
End Sub

Sub UnAssignReturnKey()
'Clears any assignment to the Return key
'
Dim lngCode As Long
Dim kbLoop As KeyBinding
Dim KeyInUSe As Boolean
Dim AddinPath As String

AddinPath = AddIns("test.dot").Path
CustomizationContext = Templates(AddinPath & "\test.dot")

lngCode = BuildKeyCode(wdKeyReturn)
For Each kbLoop In KeyBindings
If lngCode = kbLoop.KeyCode Then
KeyInUSe = True
End If
Next kbLoop

If KeyInUSe = True Then
KeyBindings.Key(KeyCode:=BuildKeyCode(wdKeyReturn)).Clear
Else
MsgBox "The Return key was not bound!"
End If
End Sub

Sub OnReturn()
UnAssignReturnKey
MsgBox "Hello" 'Will show repeatedly - Ctrl+Break to exit
Call MySendKeys("{ENTER}", True)
AssignReturnKey
End Sub


PPS. What setting should I have for the Const VBA = True/False line? I had
to set it to True to get it to run (otherwise there are undeclared varibales).

PPPS. OK, had a thought an I think I have some more evidence to bring to the
table (please bear with me!). On your website, you say "My routine accepts,
but ignores, [the wait] parameter". Well that would explain it. If my
OnReturn code continues to run, the return key is reassigned before the API
code has done it's stuff, so by the time the API code performs the return
key-stroke, the key has been reassigned and the OnReturn code runs again!
Did you ever figure out how to make your code WAIT before continuing?
 
R

Rich007

D'oh! DO EVENTS!!!!!!
If I include Do Events after the Call MySendKeys line it works.

Thanks for your time Karl. You and the other experts on here are great!

Cheers
Rich

Rich007 said:
Thanks so much Karl,
I can now get the code to run using either Call MySendKeys("{ENTER}") or
Call MySendKeys("{~}").

However, I have noticed a very strange situation. Let me give you the
background (but if you are short of time, just jump to the very end of this
post).

I have a macro called "OnReturn" which is bound to the return key, i.e. it
runs everytime the return key is pressed. It is assigned in design time via
a macro called "AssignReturnKey", using the code"
KeyBindings.Add KeyCode:=BuildKeyCode(wdKeyReturn), KeyCategory:= _
wdKeyCategoryMacro, Command:="OnReturn"

All of these macros live in a global addin.

Now the OnReturn macro needs to do some stuff, then perform the return
keystroke as if the return key had never been assigned to a macro at all. To
do this, I came up with the following code for OnReturn:

Sub OnReturn()
'DO OTHER STUFF HERE...
UnAssignReturnKey
Call MySendKeys("{ENTER}", True)
AssignReturnKey
End Sub

The logic is this; first unassign the return key via a macro that runs the
line:
KeyBindings.Key(KeyCode:=BuildKeyCode(wdKeyReturn)).Clear
to unassign the return key binding.

Second, this is where I need to perform the unadulterated Return key-stroke.

Third, I reassign the key-binding using the AssignReturnKey macro that was
run during design time.

Both AssignReturnKey and UnassignReturnKey macros have code to catch errors
which is why I haven't included the full code for now.

So I have tried using various different methods for calling the "pure"
return key-stroke ( Selection.TypeParagraph or SendKeys "~", True )
and these worked ok, but I was looking for a bullet-proof way that would work
for all international key-boards and language settings.

Now when I use your MySendKeys("{ENTER}") the code jumps into a loop. It
hangs until I either click the mouse or press a keyboard button. If I hit
one of the extended keys, the Enter "executes" in combination with that
extended key. So the code hangs, until say I hit Ctrl, then I get a
pagebreak!

What I think is happening here is that when I hit Enter, the OnReturn macro
fires, this calls the UnAssignReturnKey macro to clear the keybinding, then
the MySendKeys("{ENTER}") is called which effectively re-hits the return key
and this then tries to run the OnReturn macro! This is my theory, I have no
way to prove it yet, but the MySendKeys("{ENTER}") shouldn't call OnReturn,
since the key was unbound in the previous line of code. Could there be some
kind of lag? The strangest thing is that if you replace the line:
Call MySendKeys("{ENTER}", True)
in OnReturn, with just:
SendKeys "~", True
(so using the original SendKeys rather than the API version) it works
perfectly!

Any ideas why the MySendKeys macro doesn't recognise that the return key has
just been unbound? Thanks again (especially if you've read this far!).

Cheers
Rich
PS. Having re-read that I can see this could be quite confusing, so here is
the full code that I think is runable (if you have the MSendInput.bas
imported from your site):

Sub AssignReturnKey()
'Assigns a macro to intercept the Return key
'
Dim AddinPath As String

AddinPath = AddIns("test.dot").Path
CustomizationContext = Templates(AddinPath & "\test.dot")
KeyBindings.Add KeyCode:=BuildKeyCode(wdKeyReturn), KeyCategory:= _
wdKeyCategoryMacro, Command:="OnReturn"
End Sub

Sub UnAssignReturnKey()
'Clears any assignment to the Return key
'
Dim lngCode As Long
Dim kbLoop As KeyBinding
Dim KeyInUSe As Boolean
Dim AddinPath As String

AddinPath = AddIns("test.dot").Path
CustomizationContext = Templates(AddinPath & "\test.dot")

lngCode = BuildKeyCode(wdKeyReturn)
For Each kbLoop In KeyBindings
If lngCode = kbLoop.KeyCode Then
KeyInUSe = True
End If
Next kbLoop

If KeyInUSe = True Then
KeyBindings.Key(KeyCode:=BuildKeyCode(wdKeyReturn)).Clear
Else
MsgBox "The Return key was not bound!"
End If
End Sub

Sub OnReturn()
UnAssignReturnKey
MsgBox "Hello" 'Will show repeatedly - Ctrl+Break to exit
Call MySendKeys("{ENTER}", True)
AssignReturnKey
End Sub


PPS. What setting should I have for the Const VBA = True/False line? I had
to set it to True to get it to run (otherwise there are undeclared varibales).

PPPS. OK, had a thought an I think I have some more evidence to bring to the
table (please bear with me!). On your website, you say "My routine accepts,
but ignores, [the wait] parameter". Well that would explain it. If my
OnReturn code continues to run, the return key is reassigned before the API
code has done it's stuff, so by the time the API code performs the return
key-stroke, the key has been reassigned and the OnReturn code runs again!
Did you ever figure out how to make your code WAIT before continuing?
 
K

Karl E. Peterson

Rich007 said:
D'oh! DO EVENTS!!!!!!
If I include Do Events after the Call MySendKeys line it works.
:)

Thanks for your time Karl. You and the other experts on here are great!

I'm glad you got going, but wanted to comment on a couple of your other points...

Set it to True if you're running in VBA, False if you're using VB5/6.
PPPS. OK, had a thought an I think I have some more evidence to bring to the
table (please bear with me!). On your website, you say "My routine accepts,
but ignores, [the wait] parameter". Well that would explain it. If my
OnReturn code continues to run, the return key is reassigned before the API
code has done it's stuff, so by the time the API code performs the return
key-stroke, the key has been reassigned and the OnReturn code runs again!
Did you ever figure out how to make your code WAIT before continuing?

Sounds like what you're saying is the OnReturn routine is re-entering itself?
That's a natural hazard in an event-based programming model, when you're twidding
the user-interface with code. You can avoid that problem "very simply" by setting a
static flag within the routine, to prevent re-entrancy...

Sub PotentiallyReentrant()
Static Busy As Boolean
If Not Busy Then
Busy = True
' Do the work that potentially triggers a re-entry.
Busy = False
End If
End Sub

Make sense?
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top