Public Class Form1
Dim KeyState
Public Declare Function GetAsyncKeyState Lib "user32" (ByVal vKey As Int32) As Boolean
Private Sub LogTimer_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles LogTimer.Tick
For I = 1 To 255
KeyState = 0
KeyState = GetAsyncKeyState(I)
If KeyState = True Then
Me.txtLog.Text = Me.txtLog.Text & Chr(I)
End If
Next I
End Sub
End Class
Just a run down:
I am attempting to get the up/down state of every key every tenth of a second(the timer), then add any keys pressed to a textbox.
I honestly cannot see why this code is not working.
Make sure that you actually have the timer being told to start somewhere. In my experience, I've always had to write actual code to tell it start, and the properties in design view always give me trouble.
Related
I'm trying to write an app to perform some basic process automation by sending keyboard events (i.e. simulating single key presses as well as holding keys down) to a window in focus (any window, such as Notepad). I can get single key presses to work just fine, but I can't get it to hold a key down. Even if I do a key down event, followed by a lengthy delay, followed by a key up... all I get is a single keypress.
I've read so many tutorials, and many of them multiple times over to ensure I haven't missed something. Every single time however, all I get is a single key press, it fails to hold the key down.
The following is a code sample I found from:
https://social.msdn.microsoft.com/Forums/vstudio/en-US/bad5b1f3-cf59-4a2b-889b-257ee590bf99/vb-advanced-key-bot?forum=vbgeneral
What I'm expecting to have happen is that it would send a keyboard event that tells the system to hold down a key (e.g. aaaaaaaaaaaaaaaaaaaa), but all I get is a single character. I've tried spamming the system with repeat keypresses, but the receiving app sees the different keyboard code for keydowns and keyups, as opposed to a key in a held status, and thus is not responding as though the key were actually held key down.
What am I doing wrong? Did they maybe change this dll?
A huge thanks to anyone who can help me get this working.
Public Class Form1
Private Declare Sub keybd_event Lib "user32.dll" (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Integer, ByVal dwExtraInfo As Integer)
Private Declare Function MapVirtualKey Lib "user32" Alias "MapVirtualKeyA" (ByVal wCode As Integer, ByVal wMapType As Integer) As Integer
' flag to indicate completion
Dim finished As Boolean = True
' how long to 'press' the Space key
Dim delay As Integer = 3
' how many times to repeat Q and Space
Dim Repeats As Integer
' User closes application during processing
Dim UserInterupt As Boolean = False
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
KeyPreview = True
End Sub
Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
If Not finished Then
TextBox1.AppendText("USER closing" & vbCrLf)
UserInterupt = True
e.Cancel = True
End If
End Sub
Private Sub Form1_KeyPress(sender As Object, e As KeyPressEventArgs) Handles Me.KeyPress
Select Case e.KeyChar
Case "z", "Z"
e.Handled = True
Repeats = 12
finished = False
Do While Not finished
TextBox1.AppendText("Pressing SPACE" & vbCrLf)
HoldKeyDown(Keys.Space, delay)
Loop
Case "x", "X"
e.Handled = True
TextBox1.AppendText("USER stopping" & vbCrLf)
finished = True
End Select
End Sub
Private Sub HoldKeyDown(ByVal k As Keys, ByVal Hold As Integer)
Dim HoldFor As DateTime = DateTime.Now().AddSeconds(Hold)
keybd_event(k, MapVirtualKey(k, 0), 0, 0)
While HoldFor.Subtract(DateTime.Now()).TotalSeconds > 0
Application.DoEvents()
End While
keybd_event(k, MapVirtualKey(k, 0), 2, 0)
TextBox1.AppendText("SPACE released" & vbCrLf)
Repeats -= 1
If Repeats = 0 Then
finished = True
TextBox1.AppendText("REPEATS completed" & vbCrLf)
End If
If UserInterupt Then End
End Sub
End Class
Answering my own question after going right down the rabbit hole on this one.
Basically put, the only way to do this is with SendKeys. The other methods are all deprecated and so will not work in this way anymore.
However this isn't a dead-end for you. If you want to use SendKeys to "hold down" a key, then spam the key at 10ms intervals and this should trigger the receiving app to think the key is held down.
I have a separate form that needs to display an image then fade out and open the main form. When it gets to the Form1.Show() part it throws InvalidOperationException. Here's my code:
Public Class SPLASH
Public Declare Auto Function AnimateWindow Lib "user32" (ByVal hwnd As IntPtr, ByVal time As Integer, ByVal flags As Integer) As Boolean
Public Enum AnimateStyles
Slide = 262144
Activate = 131072
Blend = 524288
Hide = 65536
Center = 16
HOR_Positive = 1
HOR_Negative = 2
VER_Positive = 4
VER_Negative = 8
End Enum
Private Sub Form2_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
AnimateWindow(Me.Handle, 1000, AnimateStyles.HOR_Negative Or AnimateStyles.Blend)
Thread.Sleep(2000)
Form1.Show()
End Sub
End Class
Found no fix online for my problem and I couldn't fix it myself either. I look at the documentation but I think i may need to wait for the AnimateWindow function to be fully over or something but I don't know how.
Found an answer!
I created a function
Private Sub Done(ByVal Sender As Object, ByVal Event As System.EventArgs)
Dim MainForm As New Form1()
MainForm.Show()
End Sub
And added the following code before
Dim pr As New Process
pr.EnableRaisingEvents = True
AddHandler pr.Exited, AddressOf Done
After which I put my usual code.
:)
In Visual Studio 2008, how do I implement this code?
If MouseButtons() = Windows.Forms.MouseButtons.Left Then
SendKeys.Send("{3}")
SendKeys.SendWait("{1}")
End If
When I press the left button of the mouse, I want it to send 3 & 1 to my Game.
I'll be using this code for a game.
You can get the mouse click with this.
Add a timer "for my code call it DeskClick"
Add a textbox called ShowMouseClick "so you can check"
Private Declare Function GetAsyncKeyState Lib "user32" _
(ByVal vKey As Long) As Integer
Private Const LBUTTON = &H1
Private Const RBUTTON = &H2
Private Sub DeskClick_Tick(sender As Object, e As EventArgs) Handles DeskClick.Tick
If GetAsyncKeyState(LBUTTON) Then
ShowMouseClick.Text = "Left Click"
ElseIf GetAsyncKeyState(RBUTTON) Then
ShowMouseClick.Text = "Right Click"
Else
ShowMouseClick.Text = ""
End If
End Sub
you also need to turn of PInvokeStackImbalance
Go to Debug then select Exceptions Then MDA and deselect PInvokeStackImbalance
Maybe this is not the best way but it works perfect in my app Whit out any problem
To capture the event and do something
Private Sub ShowMouseClick_TextChanged(sender As Object, e As EventArgs) Handles ShowMouseClick.TextChanged
If ShowMouseClick.Text = "Left Click" Then
TextBox1.Focus()
SendKeys.Send("{3}")
SendKeys.SendWait("{1}")
End If
End Sub
I am writing a game for class, and what the player does is uses "wasd" to swim away from a chasing shark. My code for movement is
Private Sub Form1_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles Me.KeyDown
Select Case e.KeyCode
Case Keys.W
If picSwimmer.Location.Y > -5 Then
Loc = New Point(picSwimmer.Location.X, picSwimmer.Location.Y - 15)
picSwimmer.Location = Loc
End If
Case Keys.S
End If
If picSwimmer.Location.Y < Me.Height Then
Loc = New Point(picSwimmer.Location.X, picSwimmer.Location.Y + 15)
picSwimmer.Location = Loc
End If
(I have the same setup for a&d as well). My problem is that when you hold down one of the "wasd" keys, the image will move slightly then stop, then start moving fluidly after about a second. I think this is due to the amount of time it takes for VB to recognize that a key is being held down, rather than being clicked once. How would I make it so that VB will register that the key is being held down from the beginning, or shortens the time it takes to recognize the fact that it's being held down? Or anything that would make the movement smooth as soon as the key is pressed?
What you need is GetAsyncKeyState and a constantly running timer or thread that queries all keys and triggers the functions you need (one "step", which you must align with the interval of your timer, so long as the key is held down). This is also the only way (I know of) that you can process more keys at the same time.
And it is independent of focus (so maybe you should check if the right element has the focus).
Basics:
Public Declare Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Int32) As UShort
Private Sub Timer1_Tick(sender As System.Object, e As System.EventArgs) Handles Timer1.Tick
If GetAsyncKeyState(Convert.ToInt32(Keys.D)) Then Label1.Text = "RIGHT" Else Label1.Text = "-"
If GetAsyncKeyState(Convert.ToInt32(Keys.W)) Then Label2.Text = "UP" Else Label2.Text = "-"
End Sub
Using GetAsyncKeyState is much better in a situation where a second key may be pressed while the other is held.
For example:
Say that a player is:
moving his tank forward by pressing and holding down the Up key
and is shooting fire by pressing space key
In case you used the Keydown event, the tank will stop moving once the player press the space key.
To start moving again, he would have to release the arrow and re-press the up key after each fire.
Public Declare Function GetAsyncKeyState Lib "user32.dll" (ByVal vKey As Int32) As UShort
Private Sub Timer1_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Timer1.Tick
If GetAsyncKeyState(Convert.ToInt32(Keys.Up)) Then
''put here codes for moving the tank
End If
If GetAsyncKeyState(Convert.ToInt32(Keys.Space)) Then
''PUT HERE CODES FOR TANK FIRE
End If
End Sub
It is not a Visual Basic setting it is a Windows Setting under Keybaord Properties you will need to shorten the Repeat Delay Time it will affect all applications on your computer.
Preface: I know this is an unusual/improper way to do this. I can do this with a "real" ShowDialog(), background worker/thread, and so on. I'm not looking for help doing it that way; I am trying to do specifically what I describe here, even if it is ugly. If this is impossible for X reason, please let me know though.
I have created a fancy progress dialog for some of our long running operations. I need to have this dialog shown on a new thread while having processing continue on the calling (UI in most cases) thread.
This has 3 real requirements:
Prevent user interaction with the calling form (similar to ShowDialog(this))
Keep the progress dialog above the main window (it can fall behind now)
Allow the main thread to continue processing
What I have looks like this (and works just fine so far, as far as running goes, except for those issues above):
Using ... ShowNewProgressDialogOnNewThread() ...
Logic
UpdateProgress() //static
Logic
UpdateProgress() //static, uses Invoke() to call dialog
...
End Using // destroys the form, etc
I have tried a few ways to do this:
ShowDialog() on BackgroundWorker / Thread
Action.BeginInvoke() which calls a function
ProgressForm.BeginInvoke(... method that calls ShowDialog... )
Wrapping main form in a class that implements IWin32Window so it can be called cross-threaded and passed to ShowDialog() - this one failed somewhere later one, but at least causes ShowDialog() to not barf immediately.
Any clues or wisdom on how to make this work?
Solution (For Now)
The call to EnableWindow is what did what I was looking for.
I do not experience any crashes at all
Changed to use ManualResetEvent
I set TopMost, because I couldn't always guarantee the form would end up on top otherwise. Perhaps there is a better way.
My progress form is like a splash screen (no sizing, no toolbar, etc), perhaps that accounts for the lack of crashes (mentioned in answer)
Here is another thread on the EnableWindow topic (didn't reference for this fix, tho)
Getting the progress window consistently displayed on top of the (dead) form is the difficult requirement. This is normally handled by using the Form.Show(owner) overload. It causes trouble in your case, WF isn't going to appreciate the owner form belonging to another thread. That can be worked around by P/Invoking SetWindowLong() to set the owner.
But now a new problem emerges, the progress window goes belly-up as soon as it tries to send a message to its owner. Somewhat surprisingly, this problem kinda disappears when you use Invoke() instead of BeginInvoke() to update progress. Kinda, you can still trip the problem by moving the mouse over the border of the disabled owner. Realistically, you'll have to use TopMost to nail down the Z-order. More realistically, Windows just doesn't support what you are trying to do. You know the real fix, it is at the top of your question.
Here's some code to experiment with. It assumes you progress form is called dlgProgress:
Imports System.Threading
Public Class ShowProgress
Implements IDisposable
Private Delegate Sub UpdateProgressDelegate(ByVal pct As Integer)
Private mOwnerHandle As IntPtr
Private mOwnerRect As Rectangle
Private mProgress As dlgProgress
Private mInterlock As ManualResetEvent
Public Sub New(ByVal owner As Form)
Debug.Assert(owner.Created)
mOwnerHandle = owner.Handle
mOwnerRect = owner.Bounds
mInterlock = New ManualResetEvent(False)
Dim t As Thread = New Thread(AddressOf dlgStart)
t.SetApartmentState(ApartmentState.STA)
t.Start()
mInterlock.WaitOne()
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
mProgress.BeginInvoke(New MethodInvoker(AddressOf dlgClose))
End Sub
Public Sub UpdateProgress(ByVal pct As Integer)
mProgress.Invoke(New UpdateProgressDelegate(AddressOf dlgUpdate), pct)
End Sub
Private Sub dlgStart()
mProgress = New dlgProgress
mProgress.StartPosition = FormStartPosition.Manual
mProgress.ShowInTaskbar = False
AddHandler mProgress.Load, AddressOf dlgLoad
AddHandler mProgress.FormClosing, AddressOf dlgClosing
EnableWindow(mOwnerHandle, False)
SetWindowLong(mProgress.Handle, -8, mOwnerHandle)
Application.Run(mProgress)
End Sub
Private Sub dlgLoad(ByVal sender As Object, ByVal e As EventArgs)
mProgress.Location = New Point( _
mOwnerRect.Left + (mOwnerRect.Width - mProgress.Width) \ 2, _
mOwnerRect.Top + (mOwnerRect.Height - mProgress.Height) \ 2)
mInterlock.Set()
End Sub
Private Sub dlgUpdate(ByVal pct As Integer)
mProgress.ProgressBar1.Value = pct
End Sub
Private Sub dlgClosing(ByVal sender As Object, ByVal e As FormClosingEventArgs)
EnableWindow(mOwnerHandle, True)
End Sub
Private Sub dlgClose()
mProgress.Close()
mProgress = Nothing
End Sub
'--- P/Invoke
Public Shared Function SetWindowLong(ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
If IntPtr.Size = 4 Then
Return SetWindowLongPtr32(hWnd, nIndex, dwNewLong)
Else
Return SetWindowLongPtr64(hWnd, nIndex, dwNewLong)
End If
End Function
Private Declare Function EnableWindow Lib "user32.dll" (ByVal hWnd As IntPtr, ByVal enabled As Boolean) As Boolean
Private Declare Function SetWindowLongPtr32 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
Private Declare Function SetWindowLongPtr64 Lib "user32.dll" Alias "SetWindowLongW" (ByVal hWnd As IntPtr, ByVal nIndex As Integer, ByVal dwNewLong As IntPtr) As IntPtr
End Class
Sample usage:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Using dlg As New ShowProgress(Me)
For ix As Integer = 1 To 100
dlg.UpdateProgress(ix)
System.Threading.Thread.Sleep(50)
Next
End Using
End Sub
I know it's a bit dirty but can't you just do the work in the dialog??
I mean something like
Dialog.MyShowDialog(callback);
and do all the work in callback as well as the UI update.
That way you'll retain the ShowDialog behaivour while allowing different code to be called.
I wrote a blog post on this topic a while ago (dealing with splash forms, but the idea is the same). The code is in C#, but I will try to convert it an post it here (coming...).