VS 2017 Cursor.Clip the Mouse to a Circle Instead of Rectangle-VBForums
Results 1 to 5 of 5

Thread: Cursor.Clip the Mouse to a Circle Instead of Rectangle

  1. #1

    Thread Starter
    New Member
    Join Date
    Aug 2017
    Posts
    2

    Question Cursor.Clip the Mouse to a Circle Instead of Rectangle

    So I've been playing around with slither.io and agar.io(I'm sure you've heard of them)
    I am using a Wii nunchuck to control the mouse, which determines which way the player will move. The problem I was facing was that it is incredibly difficult to play a pc game with a joystick. So I created an application that will prevent the movement of the mouse from leaving too far from the player. The issue I am dealing with is that the mouse travels in a rectangle(a square) instead of a circle.

    I researched online about how to make a "circle Cursor.clip" function, but all I found was to relocate the position of the Mouse with Cursor.Position, but that only made things worse because the Mouse starts to jitter and sometimes leave the circle boundary.

    So, I tried to learn about using the setWindowsHookex function. Apparently, it can hook the mouse and prevent it from moving in a circular shape.
    I know this because I saw this forum from AutoHotKey.com

    Unfortunately, I can't wrap my head around this "Hook" method. I need some help from those who are experienced enough with vb.net

    I'll include my source code below for those in the future to learn, it works beautifully if I had a square joystick.

    Once again, the question is: How to make a circular boundary for a mouse. The Meat of the code is the MoveCursor()
    Code:
    Public Class Form1
        Private Declare Function GetAsyncKeyState Lib "user32" (ByVal vkey As Int32) As Short
        Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
            Timer1.Enabled = True
            Timer1.Interval = 1
        End Sub
        Private Sub MoveCursor()
    
            Dim calcX As Double = CDbl(Cursor.Position.X)
            Dim calcY As Double = CDbl(Cursor.Position.Y)
            Dim calcRadius As Double = Math.Sqrt(Math.Pow((calcX - GlobalVariables.centerX), 2) + Math.Pow((calcY + GlobalVariables.centerY), 2))
            If (calcRadius >= GlobalVariables.Radius) Then
                Dim newSize As Size = New Size(GlobalVariables.Radius, GlobalVariables.Radius)
                Dim centerPoint As Point = New Point(GlobalVariables.centerX, GlobalVariables.centerY)
                Dim newPoint As Point = New Point(GlobalVariables.centerX - (GlobalVariables.Radius / 2), GlobalVariables.centerY - (GlobalVariables.Radius / 2))
                Cursor.Clip = New Rectangle(newPoint, newSize)
            End If
        End Sub
        Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
            Dim ESCkey As Boolean
            Dim ShiftKey As Boolean
            Dim CtrlKey As Boolean
            Dim AltKey As Boolean
            Dim sKey As Boolean
            ESCkey = GetAsyncKeyState(Keys.Escape)
            ShiftKey = GetAsyncKeyState(Keys.ShiftKey)
            CtrlKey = GetAsyncKeyState(Keys.ControlKey)
            AltKey = GetAsyncKeyState(Keys.Menu)
            sKey = GetAsyncKeyState(Keys.S)
            If (ESCkey) Then
                GlobalVariables.CenterFound = False
            End If
            If (sKey AndAlso (CtrlKey AndAlso ShiftKey AndAlso AltKey)) Then
                GlobalVariables.CenterFound = True
                GlobalVariables.centerX = CDbl(Cursor.Position.X)
                GlobalVariables.centerY = CDbl(Cursor.Position.Y)
                GlobalVariables.Saved = True
            End If
            If (sKey AndAlso (CtrlKey AndAlso Not ShiftKey AndAlso AltKey)) Then
                GlobalVariables.CenterFound = True
            End If
            If (GlobalVariables.CenterFound) Then
                MoveCursor()
            Else
                Cursor.Clip = Rectangle.Empty
            End If
    
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            GlobalVariables.Radius = CDbl(RadiusInput.Text)
        End Sub
    
        Private Sub BtnReset_Click(sender As Object, e As EventArgs) Handles BtnReset.Click
            If (GlobalVariables.CenterFound = False AndAlso GlobalVariables.Saved = True) Then
                GlobalVariables.Saved = False
                GlobalVariables.centerX = -1
                GlobalVariables.centerY = -1
                MessageBox.Show("Location has been reset!", "Location Reset")
            End If
        End Sub
    
        Private Sub BtnGoTo_Click(sender As Object, e As EventArgs) Handles BtnGoTo.Click
            If (GlobalVariables.Saved) Then
                GlobalVariables.CenterFound = True
            Else
                MessageBox.Show("You don't have a saved location!", "User Error")
            End If
        End Sub
    End Class
    Public Class GlobalVariables
        Public Shared Property CenterFound As Boolean = False
        Public Shared Property Saved As Boolean = False
        Public Shared Property Radius As Double = 50
        Public Shared Property centerX As Double = -1
        Public Shared Property centerY As Double = -1
    End Class
    Attached Images Attached Images  
    Attached Files Attached Files

  2. #2
    .NUT jmcilhinney's Avatar
    Join Date
    May 2005
    Location
    Sydney, Australia
    Posts
    96,862

    Re: Cursor.Clip the Mouse to a Circle Instead of Rectangle

    The Cursor.Clip property is type Rectangle so you're not going to be able to use it to limit the mouse pointer to any shape other than a rectangle.
    Why is my data not saved to my database? | MSDN Data Walkthroughs
    MSDN "How Do I?" Videos: VB | C#
    VBForums Database Development FAQ
    My CodeBank Submissions: VB | C#
    My Blog: Data Among Multiple Forms (3 parts)
    Beginner Tutorials: VB | C# | SQL

  3. #3

    Thread Starter
    New Member
    Join Date
    Aug 2017
    Posts
    2

    Re: Cursor.Clip the Mouse to a Circle Instead of Rectangle

    That's what I thought too. But when I saw the solution in AutoHotkey, I was amazed. This is because AutoHotkey was based on Visual studio and a little visual basic. So I just know there is an alternative than "Cursor.Clip" to make the mouse trapped inside a circular boundary.

    Here is their code.
    Code:
    RButton::
    	CircleClip(135)
    	KeyWait,RButton
    	CircleClip()
    Return
    
    
    CircleClip(radius=0, x:="", y:=""){
    	global CircleClipRadius, CircleClipX, CircleClipY
    	static hHookMouse, _:={base:{__Delete: "CircleClip"}}
    	If (radius>0)
    		CircleClipRadius:=radius
    		, CircleClipX:=x
    		, CircleClipY:=y
    		, hHookMouse := DllCall("SetWindowsHookEx", "int", 14, "Uint", RegisterCallback("CircleClip_WH_MOUSE_LL", "Fast"), "Uint", 0, "Uint", 0)
    	Else If (!radius && hHookMouse){
    		DllCall("UnhookWindowsHookEx", "Uint", hHookMouse)
    		CircleClipX:=CircleClipY:=""
    	}
    }
    
    CircleClip_WH_MOUSE_LL(nCode, wParam, lParam){
    	global CircleClipRadius, CircleClipX, CircleClipY
    	Critical
    
    	if !nCode && (wParam = 0x200){ ; WM_MOUSEMOVE 
    		nx := NumGet(lParam+0, 0, "Int") ; x-coord
    		ny := NumGet(lParam+0, 4, "Int") ; y-coord
    
    		If (CircleClipX="" || CircleClipY="")
    			CircleClipX:=nx, CircleClipY:=ny
    		  
    		dx := nx - CircleClipX
    		dy := ny - CircleClipY
    		dist := sqrt( (dx ** 2) + (dy ** 2) )
    
    		if ( dist > CircleClipRadius ) {
    			dist := CircleClipRadius / dist
    			dx *= dist
    			dy *= dist
    			
    			nx := CircleClipX + dx
    			ny := CircleClipY + dy
    		}
    
    		DllCall("SetCursorPos", "Int", nx, "Int", ny)
    		Return 1
    		
    	}else Return DllCall("CallNextHookEx", "Uint", 0, "int", nCode, "Uint", wParam, "Uint", lParam) 
    } 
    
     ~#q::ExitApp

  4. #4
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,039

    Re: Cursor.Clip the Mouse to a Circle Instead of Rectangle

    We don't usually talk about hooks here for a couple of reasons.

    First, a lot of them can't be implemented safely in .NET for architectural reasons. Anything that requires process injection can run into subtle problems that take a long time to explain. The global keyboard/mouse hooks happen to be a kind of hook that doesn't have this problem.

    Second: if you screw them up you break input for the user. I've done it before messing with keyboard hooks, and you can't fix it without rebooting. It's a pain in the butt and you really need to know the API well to get it right. That's kind of a bad reason to shy away from a topic, but...

    Third, the global mouse/keyboard hooks are vital to writing a successful keylogger. So it's a kind of dark magic we don't talk about a lot to make it a little harder for the wrong kinds of people to figure out the secret.

    So I can tell you WHAT it is doing, and where to look, but I don't like to post working hook code.

    If you want to try it, you'll have to find a way to call the API method SetWindowsHookEx(). That information should be fairly easy to find, I bet there's places that have copy-pastable infrastructure. Part of registering for a mouse hook is giving it the right callback, which is sort of like an event handler. That's where you'd write your code and it corresponds to CircleClip_WH_MOUSE_LL().

    Once you do that, your app gets to see all mouse actions before the apps that the mouse input was intended for! With great power comes great responsibility.

    If the callback's first parameter is < 0, you have to ignore the message. Even if you accept the message, you have to call CallNextHookEx() when you're done. Failure to do either of these will screw up your mouse input.

    The wparam tells you which mouse message was sent, and the lParam points to a structure with information about the mouse state/message. This implementation looks like it more or less considers a straight line from the center of the circle to the point, and if the length of the line is longer than the desired radius, it moves the cursor to the point where d = r.

    There is an error in the value that this procedure returns, or at least I think there is. If you can find the callback's documentation, read about its return value and see if you agree that 1 is an appropriate value.
    Nothing I post is production-ready. It is provided as-is, use it at your own risk.

  5. #5
    PowerPoster boops boops's Avatar
    Join Date
    Nov 2008
    Location
    Holland/France
    Posts
    2,966

    Re: Cursor.Clip the Mouse to a Circle Instead of Rectangle

    I think I just invented a new way to confine mouse movement to a circle. It uses Region.GetRegionScans to approximate the circle (or other shape) as an array of thin, flat rectangular slices or "scans". In the MouseMove event, it checks if any of the scans contains the mouse position, and if so uses that scan to set the Cursor.Clip; however the scan rectangle is inflated vertically by a couple of pixels so that the mouse can slip into the scan above or below. It's not super precise, but maybe it's good enough for the OP's purpose and it could probably be improved.

    Here's a code example on a Form. Click anywhere on the form to enable or disable mouse capturing by the circle.
    Code:
    Public Class Form1
    
       Private circleRect As New Rectangle(50, 50, 200, 200)
       Private constrainMouse As Boolean = False
       Private scans As RectangleF()
    
       Private Sub Form1_Click(sender As Object, e As EventArgs) Handles Me.Click
          constrainMouse = Not constrainMouse
          If constrainMouse = False Then Cursor.Clip = Nothing
       End Sub
    
       Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
          'generate the scans
          Using gp As New Drawing2D.GraphicsPath
             gp.AddEllipse(circleRect)
             Using rgn As New Region(gp)
                scans = rgn.GetRegionScans(New Drawing2D.Matrix)
             End Using
          End Using
       End Sub
    
       Private Sub Form1_MouseMove(sender As Object, e As MouseEventArgs) Handles Me.MouseMove
          If constrainMouse Then
             For Each rf In scans
                Dim r As Rectangle = Me.RectangleToScreen(Rectangle.Round(rf))
                r.Inflate(0, 2)
                If rf.Contains(e.Location) Then
                   Cursor.Clip = r
                   Exit For
                End If
             Next
          End If
       End Sub
    
       Private Sub Form1_Paint(sender As Object, e As PaintEventArgs) Handles Me.Paint
          e.Graphics.SmoothingMode = Drawing2D.SmoothingMode.HighQuality
          e.Graphics.FillEllipse(Brushes.Yellow, circleRect)
          e.Graphics.DrawEllipse(Pens.Red, circleRect)
       End Sub
    
    End Class

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Featured


Click Here to Expand Forum to Full Width

Survey posted by VBForums.