VB Press and hold keystroke - vb.net

I am creating a macro program to record and play back mouse and keyboard input. Recording works fine, as does mouse playback, but I am having trouble in playing back the keyboard input - specifically pressing and holding a key for several seconds before releasing. This is not equivalent to repetitively pressing the key. This is what I have tried:
Technique 1: Me.KeyDown
Private Sub keyboard_pressed() Handles Me.KeyDown
Dim keypress = e.KeyData
MsgBox(keypress)
End Sub
Only works when window is in focus.
Technique 2: SendKeys
Private Sub timer_keyboardplayback_Tick() Handles timer_playback.Tick
SendKeys.Send("{LEFT}")
timer_playback.Interval = 30
End Sub
Works out of focus, but repetitively presses left arrow rather than press and hold arrow
Technique 3: keybd_event
Public Declare Sub mouse_event Lib "user32" Alias "mouse_event" (ByVal dwFlags As Long, ByVal dx As Long, ByVal dy As Long, ByVal cButtons As Long, ByVal dwExtraInfo As Long)
Private Sub timer_keyboardplayback_Tick() Handles timer_playback.Tick
Const keydown = &H1
Const keyup = &H2
Dim VK_LEFT = 37
keybd_event(VK_LEFT, 0, keydown, 0)
End Sub
Works out of focus, but still fails to press hold arrow
Can someone please show me how I can achieve a press & hold of the Left Arrow Key for several seconds, and then release.

The keybd_event and mouse_event functions are deprecated as of a few years ago. Instead you should use the SendInput() function.
Simulating input with it from .NET can sometimes be a bit tricky, fortunately though I've written a library called InputHelper (Download from GitHub) which is a wrapper around SendInput(). I've customized it so that it covers some of the many different ways of input handling and input simulation, mainly:
Simulate keystrokes (internally utilizing SendInput()).
Simulate mouse movement and mouse button clicks (also utilizing SendInput() internally).
Send virtual keystrokes and mouse clicks to the current/a specific window (internally utilizing Window Messages).
Create global, low-level mouse and keyboard hooks.
Unfortunately I've not yet had the time to write a proper documentation/wiki about this (apart from the XML documentation on each member of the library, which is shown by Visual Studio's IntelliSense), but so far you can find a little info about creating hooks on the project's wiki.
A short description of what this library consist of:
InputHelper.Hooks
For creating global, low-level mouse/keyboard hooks (utilizes SetWindowsHookEx() and other related methods). This is partially covered in the wiki.
InputHelper.Keyboard
For handling/simulating physical keyboard input (utilizes SendInput() and GetAsyncKeyState()).
InputHelper.Mouse
For handling/simulating physical mouse input (utilizes SendInput()).
InputHelper.WindowMessages
For handling/simulating virtual mouse/keyboard input, for instance to a specific window (utilizes SendMessage() and PostMessage()).
Sending a keystroke
Sending a "physical" keystroke can be done via two functions:
InputHelper.Keyboard.PressKey(Key As Keys, Optional HardwareKey As Boolean)
Sends two keystrokes (down and up) of the specified key.
If HardwareKey is set, the function will send the key's Scan Code instead of its Virtual Key Code (default is False).
InputHelper.Keyboard.SetKeyState(Key As Keys, KeyDown As Boolean, Optional HardwareKey As Boolean)
Sends a single keystroke of the specified key.
If KeyDown is True the key will be sent as a KEYDOWN event, otherwise KEYUP.
HardwareKey is the same as above.
You'd use the latter, since you want to control for how long you want the key to be held down.
Holding a key down for the specified amount of time
In order to do this you need to use some sort of timer, like you already do. However to make things a bit more dynamic I've written a function that'll let you specify which key to hold down, and also for how long.
'Lookup table for the currently held keys.
Private HeldKeys As New Dictionary(Of Keys, Tuple(Of Timer, Timer))
''' <summary>
''' Holds down (and repeats, if specified) the specified key for a certain amount of time.
''' Returns False if the specified key is already being held down.
''' </summary>
''' <param name="Key">The key to hold down.</param>
''' <param name="Time">The amount of time (in milliseconds) to hold the key down for.</param>
''' <param name="RepeatInterval">How often to repeat the key press (in milliseconds, -1 = do not repeat).</param>
''' <remarks></remarks>
Public Function HoldKeyFor(ByVal Key As Keys, ByVal Time As Integer, Optional ByVal RepeatInterval As Integer = -1) As Boolean
If HeldKeys.ContainsKey(Key) = True Then Return False
Dim WaitTimer As New Timer With {.Interval = Time}
Dim RepeatTimer As Timer = Nothing
If RepeatInterval > 0 Then
RepeatTimer = New Timer With {.Interval = RepeatInterval}
'Handler for the repeat timer's tick event.
AddHandler RepeatTimer.Tick, _
Sub(tsender As Object, te As EventArgs)
InputHelper.Keyboard.SetKeyState(Key, True) 'True = Key down.
End Sub
End If
'Handler for the wait timer's tick event.
AddHandler WaitTimer.Tick, _
Sub(tsender As Object, te As EventArgs)
InputHelper.Keyboard.SetKeyState(Key, False) 'False = Key up.
WaitTimer.Stop()
WaitTimer.Dispose()
If RepeatTimer IsNot Nothing Then
RepeatTimer.Stop()
RepeatTimer.Dispose()
End If
HeldKeys.Remove(Key)
End Sub
'Add the current key to our lookup table.
HeldKeys.Add(Key, New Tuple(Of Timer, Timer)(WaitTimer, RepeatTimer))
WaitTimer.Start()
If RepeatTimer IsNot Nothing Then RepeatTimer.Start()
'Initial key press.
InputHelper.Keyboard.SetKeyState(Key, True)
Return True
End Function
Example usage:
'Holds down 'A' for 5 seconds, repeating it every 50 milliseconds.
HoldKeyFor(Keys.A, 5000, 50)

Related

Empty pre-filled TextBox on MouseDown/MouseUp only FirstTime

I'm newbie in word-vba (just to let you know that my question could be really stupid).
I would like to clear a textbox only when I click in the textbox the first time.
I've tried For... Next but I wasn't able to comfigure it correctly
Private Sub SWName_Field_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
SWName_Field.Text = ""
End Sub
I would like that the code will work exactly the same way it works but when I put some text and for example the user make a mistake or typo error the second click in the textbox shouldn't clear the text inside.
Thank you for the support
There's no inbuilt activity state identifier in any UserForm control. So you need to use meta data to specify and identify whether or not your mousedown is happening for the first time.
Use Tag property of the control for that.
See the code comments for details.
Private Sub TextBox1_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
'/ Use the Tag field to determine and store the state of Text Box.
If Len(Me.TextBox1.Tag) < 1 Then
'/ If Mousedown for the very first time then TextBox's tag is empty.
'/ Go ahead, clean the textbox.
'/ And set a text in tag.
Me.TextBox1.Text = ""
Me.TextBox1.Tag = "Text Cleared"
End If
End Sub
You can use a Static local variable to "remember" whether the handler was executed at least once or not:
Private Sub SWName_Field_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single)
Static executed As Boolean
If Not executed Then
SWName_Field.Text = ""
executed = True
End If
End Sub
The state of the Static local is tied to your UserForm instance - the value will be "remembered" for as long as the form instance is alive.
This means, if you're showing the form's default instance, the state won't necessarily be reset. You will want to ensure you get a fresh default form state every time the form is shown, not just the first time - to do this you New up the form:
With New UserForm1
.Show
End With
If you just do UserForm1.Show, then you don't control when the form instance gets created - VBA does.
You'll also want to control when the form instance gets destroyed - you can do that by handling the form's QueryClose event:
Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
If CloseMode = VbQueryClose.vbFormControlMenu Then
Cancel = True ' cancel the destruction of the object
Me.Hide ' hide the instance instead
End If
End Sub
With that, the object will be destroyed when execution reaches End With. Without it, the object will be destroyed if the user clicks the "X" button, and you probably don't want that to happen (especially if you need to access the form's state after it's closed).

Ctrl+Shift+X conflicts with Ctrl+X action

We kept a menu item short cut key (Ctrl+Shift+X) to pop up another form in Windows VB application. But when user tried to cut using (Ctrl+X) text from any field the above short cut action is firing and popping up another form.
No sure anything we missed or we should not use (Ctrl+Shift+X) as shortcut.
Me.mnuTools.Shortcut = System.Windows.Forms.Shortcut.CtrlShiftX
worst case we can change short cut but we wish to know is it something wrong using the short cut.
Please provide your views.
I can't answer why it thinks CTRL + X should call a CTRL + SHIFT + X hotkey, but I do believe I have a workaround.
When a cut action is performed Windows sends a WM_CUT message to the focused control. If we override ProcessCmdKey() we can stop the CTRL + X combination from reaching any child controls of the form and instead send the WM_CUT message ourselves.
Untested, but I believe it should work:
<DllImport("user32.dll")> _
Private Shared Function SendMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr)
End Function
Private Const WM_CUT As Integer = &H300
Protected Overrides Function ProcessCmdKey (ByRef msg As Message, keyData As Keys) As Boolean
If keyData = (Keys.ControlKey Or Keys.X) AndAlso _
Me.ActiveControl IsNot Nothing Then
SendMessage(Me.ActiveControl.Handle, WM_CUT, IntPtr.Zero, IntPtr.Zero) 'Send the WM_CUT message to the currently focused control.
Return True 'Indicate that we've handled the message and stop the key strokes from reaching any child controls.
End If
End Function
EDIT:
Future readers: I am aware that you can define the hotkeys on your own and call the respective methods in KeyDown or ProcessCmdKey, but I used the method above so that the menu item still can display its CTRL + SHIFT + X hint.

Listen to key press when the program is in the background

I am currently working with a program which is supposed to run in the background but also check if "mod + o" is pressed then do something. But I cannot figure out how a vb.net program can listen to key presses when the program is not Selected / Opened.
You can use P/Invocation to be able to use WinAPI's GetAsyncKeyState() function, then check that in a timer.
<DllImport("user32.dll")> _
Public Shared Function GetAsyncKeyState(ByVal vKey As System.Windows.Forms.Keys) As Short
End Function
Const KeyDownBit As Integer = &H8000
Private Sub Timer1_Tick(sender As Object, e As System.EventArgs) Handles Timer1.Tick
If (GetAsyncKeyState(Keys.LWin) And KeyDownBit) = KeyDownBit AndAlso (GetAsyncKeyState(Keys.O) And KeyDownBit) = KeyDownBit Then
'Do whatever you want when 'Mod + O' is held down.
End If
End Sub
EDIT:
To make the code only execute one time per key press, you can add a little While-loop to run until either of the buttons are released (add it inside your If-statement):
While GetAsyncKeyState(Keys.LWin) AndAlso GetAsyncKeyState(Keys.O)
End While
This will stop your code from executing more than once while you hold the keys down.
When using this in a Console Application just replace every System.Windows.Forms.Keys and Keys with ConsoleKey, and replace LWin with LeftWindows.

Detecting whether a mouse button is down in vb.net

I don't want to use the mouseup event because I want to allow the user to drag outside the control. I need to check the state via a timer. I vaguely remember doing this before at some point by using
If MouseButtons = MouseButtons.Left Then...
But now it says that MouseButtons is a type and cannot be used as an expression (which is true, its an enum type).
Maybe they changed things or maybe I just remember wrong.. either way, how would I check if the button is still down?
This is tried and tested. I created a new class: testmouseclass and created a shared function you can use anytime to determine if the LeftMouseButton is down. This is possible with the help of GetAsyncKeyState call.
Imports System.Runtime.InteropServices 'Need to import...
Public Class testmouseclass
<DllImport("user32.dll", CharSet:=CharSet.Auto, ExactSpelling:=True)> Public Shared Function GetAsyncKeyState(ByVal vkey As Long) As Long
End Function
Public Shared Function LeftMouseIsDown() As Boolean
Return GetAsyncKeyState(Keys.LButton) > 0 And &H8000
End Function
End Class
Example Usage
Private Sub Timer1_Tick(sender As Object, e As System.EventArgs) Handles Timer1.Tick
If testmouseclass.LeftMouseIsDown() Then MsgBox("It IS!")
End Sub
Depending on the timer tick's this can be a headache as well. Not sure how you are using this, but in my example I had the timer at 3 seconds and when I held the LeftMouseButton down a message popped up and when I clicked it again it popped up again because of the timer and the left mouse was down. Also this work's even outside of your application...
What you want is to test the Mouse.LeftButton property.
If System.Windows.Input.Mouse.LeftButton = Windows.Input.MouseButtonState.Pressed Then

execute button from command propmpt

I am working on a vb.net project and i have a "start" and "Pause" Buttons on the FormPost.exe
I am trying to schedule a batch process to run every day in the morning at 4:00 AM.
How can i run a command prompt to execuite FormPost.exe and them click on "start" button, all via command prompt?
Please let me know. Thanks
What you can do is this override the OnControlCreateMethod() as follows:
Public Class Form1
Public Sub New()
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
End Sub
Protected Overrides Sub OnCreateControl()
MyBase.OnCreateControl()
If Environment.CommandLine.Contains("/clickme") Then
ClickMeButton.PerformClick()
// ... need to wait here until click event handler has finished, e.g.
// using synchronization objects
Close()
End If
End Sub
End Class
If you pass "/clickme" on the command line it will execute the click event and then close the form. If the form is the only one in the application it will terminate.
Be aware, though, that you will need to add some logic that waits for the click event handler to finish. Try avoid using polling or sleep. Instead try using synchronization objects.
If this is your application; you can modify the code so that it checks if you are running it from the command line / with appropriate arguments and fire the button click() itself. That'd be the easiest approach (I think John's answer shows this)
If it's not your application; you can still accomplish the same thing, but it's not as pretty. You can write code that will execute the winForm then activate it (to ensure it has focus)
Public Shared Sub ActivateWoW()
Dim myApp As Process = Process.GetProcessesByName("MyApp").First
AppActivate(myApp.Id)
End Sub
Then, you can use SendKeys() to simulate interaction with the form. Let's say the start button takes two 'tab' keys to be selected...
SendKeys.Send("{TAB}{TAB}")
Then a quick pause...
Thread.Sleep(25)
Then hit the enter key (which is almost always just as good as mouse click when the button is selected)
SendKeys.Send("{ENTER}")
If you want to get more involved than that; you need to start using WIN32 API calls. Here is some sample code for a Mouse click...
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As IntPtr
Private Const WM_LBUTTONUP As Long = &H202
Private Const WM_RBUTTONUP As Long = &H205
Private Const WM_LBUTTONDOWN As Long = &H201
Private Const WM_RBUTTONDOWN As Long = &H204
Private Shared Function MakeDWord(ByVal LoWord As Integer, ByVal HiWord As Integer) As Long
Return (HiWord * &H10000) Or (LoWord And &HFFFF&)
End Function
Public Shared Sub SendMouseClick()
Dim Wow As Long = FindWindow("GxWindowClass", "MyWindow")
Dim dWord As Long = MakeDWord(LastX - LastRectX, LastY - LastRectY)
SendMessage(Wow, WM_RBUTTONDOWN, 1&, dWord)
Threading.Thread.Sleep(100)
SendMessage(Wow, WM_RBUTTONUP, 1&, dWord)
End Sub