Bind Mouse Side Buttons to VisualStudio Actions
Asked Answered
L

1

5

I try to redirect the XButton 1 and 2 (Side Buttons of the mouse) to specific Visual Studio actions.

When I press the XButton1 I want to compile the project / build it. This action is bound to F6 by default.

When I press the XButton2 I want to switch between code and design view (WinForms). This is bound to F7.

After several attempts using using Visual Studio built-in tools, I created the following script using AutoHotKey:

XButton2:: 
IfWinActive Microsoft Visual Studio
{ 
  Send {F7}

  return 
}


XButton1:: 
IfWinActive Microsoft Visual Studio
{ 
  Send {F6}

  return 
} 

However I am wondering if someone knows a native way of achieving the same with Visual Studio 2015?

Lipid answered 12/8, 2016 at 19:48 Comment(1)
I didn't have access to VS2015, so implemented the solution using VS2013. But it should work also using VS2015. When you create a VS2015 Visual Studio Package project, copy the InstalledProductRegistration attribute of VSPackage1Package class and use instead of the attribute which I used.Bilodeau
B
7

Solution

The main idea is registering a global mouse hook and handling desired mouse event and run a visual studio command. To do so:

  1. Start by creating a Visual Studio Package project.
  1. Register a global mouse hook using SetWindowsHookEx by passing WH_MOUSE_LL and handle desired mouse event, for example WM_XBUTTONDOWN. Perform registration when the solution loads.
  1. Run desired Visual Studio Command using DTE.ExecuteCommand passing suitable command, for example Build.BuildSolution:

    var dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE));
    dte.ExecuteCommand("Build.BuildSolution");
    
  1. Don't forget to unhook using UnhookWindowsHookEx when the solution closed.

Note:

  • To find the command that you need, go to Tools → Options → Environment → KeyBoard and find the command which you need.

  • You will find lots of resources about how to register a global mouse hook like this which I changed a bit and used for test. At the end of the post you can find full source code.

  • In Visual Studio 2013 Add ins are deprecated, so while you can do the same using a Visual Studio Add-in project but it's better to do it using VSPackages.

Implementaion

Start by creating a Visual Studio Package project and change the code of package to the code which I post here. Also add the classes which I used for global mouse hook and windows API to the solution.

Package

Here is the full code for my Package:

using Microsoft.VisualStudio.Shell;
using System;
using System.Runtime.InteropServices;

[PackageRegistration(UseManagedResourcesOnly = true)]
[InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
[Guid(GuidList.guidVSPackage1PkgString)]
[ProvideAutoLoad(Microsoft.VisualStudio.Shell.Interop.UIContextGuids80.SolutionExists)]
public sealed class VSPackage1Package : Package
{
    public VSPackage1Package() { }
    EnvDTE.DTE dte;
    protected override void Initialize()
    {
        base.Initialize();
        dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE));
        dte.Events.SolutionEvents.Opened += SolutionEvents_Opened;
        dte.Events.SolutionEvents.AfterClosing += SolutionEvents_AfterClosing;
    }
    void SolutionEvents_AfterClosing() { MouseHook.Stop(); }
    void SolutionEvents_Opened()
    {
        MouseHook.Start();
        MouseHook.MouseAction += MouseHook_MouseAction;
    }
    void MouseHook_MouseAction(object sender, EventArgs e)
    {
        dte.ExecuteCommand("Build.BuildSolution");
    }
}

Windows API messages, structures and methods

using System;
using System.Runtime.InteropServices;
public class Win32
{
    public delegate IntPtr LowLevelMouseProc(int nCode, IntPtr wParam, IntPtr lParam);
    public const int WH_MOUSE_LL = 14;
    public enum MouseMessages
    {
        WM_LBUTTONDOWN = 0x0201, WM_LBUTTONUP = 0x0202,
        WM_MOUSEMOVE = 0x0200, WM_MOUSEWHEEL = 0x020A,
        WM_RBUTTONDOWN = 0x0204, WM_RBUTTONUP = 0x0205
    }
    [StructLayout(LayoutKind.Sequential)]
    public struct POINT { public int x; public int y; }
    [StructLayout(LayoutKind.Sequential)]
    public struct MSLLHOOKSTRUCT
    {
        public POINT pt;
        public uint mouseData;
        public uint flags;
        public uint time;
        public IntPtr dwExtraInfo;
    }
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelMouseProc lpfn,
        IntPtr hMod, uint dwThreadId);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool UnhookWindowsHookEx(IntPtr hhk);
    [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam,
        IntPtr lParam);
    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr GetModuleHandle(string lpModuleName);
}

Global Mouse Hook

Since I don't have XButton in my mouse, I handled WM_RBUTTONDOWN event.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
public static class MouseHook
{
    public static event EventHandler MouseAction = delegate { };
    private static Win32.LowLevelMouseProc _proc = HookCallback;
    private static IntPtr _hookID = IntPtr.Zero;
    public static void Start() { _hookID = SetHook(_proc); }
    public static void Stop() { Win32.UnhookWindowsHookEx(_hookID); }
    private static IntPtr SetHook(Win32.LowLevelMouseProc proc)
    {
        using (Process curProcess = Process.GetCurrentProcess())
        using (ProcessModule curModule = curProcess.MainModule)
        {
            var handle = Win32.GetModuleHandle(curModule.ModuleName);
            return Win32.SetWindowsHookEx(Win32.WH_MOUSE_LL, proc, handle, 0);
        }
    }
    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode >= 0 && 
            Win32.MouseMessages.WM_RBUTTONDOWN == (Win32.MouseMessages)wParam)
        {
            Win32.MSLLHOOKSTRUCT hookStruct = 
                (Win32.MSLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, 
                    typeof(Win32.MSLLHOOKSTRUCT));
            MouseAction(null, new EventArgs());
        }
        return Win32.CallNextHookEx(_hookID, nCode, wParam, lParam);
    }
}
Bilodeau answered 12/8, 2016 at 22:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.