当Visual Studio具有焦点(或任何以管理员身份运行的应用程序)时,keybd_event以及PostMessage win32无法正常工作

Arc*_*ght 2 c# interop keyboard-shortcuts

这是一个程序,我已经使用了旧的xp日的许多变化它是一个cmd行程序,它将改变媒体应用程序(Spotify,vlc,mediaPlayer)中的轨道,就像具有下一个/上一个轨道按钮的键盘一样.

目前我正在使用Microsoft自然键盘,它没有那些按钮,但具有执行此编程的可编程键.

当Visual Studio 2012/2013具有焦点(Windows 7)(尚未尝试其他版本)时,这一切都有效,并且它可以在Sql管理工作室中使用.

using System;
using System.Runtime.InteropServices;

namespace NxtTrack
{    
class Program
{
    [DllImport("user32.dll")]
    private static extern bool PostMessage(IntPtr hWnd, uint Msg, UIntPtr wParam, IntPtr lParam);
    [DllImport("user32.dll")]
    public static extern void keybd_event(byte vkCode, byte scanCode, int flags, IntPtr extraInfo);

    enum TrackMove
    {
        Previous,Next
    }

    static void Main(string[] args)
    {

        TrackMove trackMove;

        try
        {
            if(args[0].ToLower().Contains("previous"))
                trackMove = TrackMove.Previous;
            else if(args[0].ToLower().Contains("next"))
                trackMove = TrackMove.Next;
            else
            {
                throw new Exception("wrong param");
            }
        }
        catch
        {
            Console.WriteLine("Params needed: Next or Previous");
            return;
        }
        TrackKeys(trackMove);
    }

    private static void TrackKeys(TrackMove trackMove)
    {
        //http://msdn.microsoft.com/en-us/library/dd375731%28v=VS.85%29.aspx

        byte msg = trackMove == TrackMove.Previous ? (byte)0xB1 : (byte)0xB0;
        keybd_event(msg, 0x45, 0, IntPtr.Zero);
    }
}
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 5

这些是VK_MEDIA_NEXT_TRACK和VK_MEDIA_PREV_TRACK虚拟键.对它们的处理非常复杂:

  • 无论什么程序拥有前台窗口,它都会在调用GetMessage()时从其消息队列中检索他的击键
  • 该程序的消息循环中的TranslateMessage()调用将击键转换为WM_APPCOMMAND消息,APPCOMMAND_MEDIA_NEXTTRACK或APPCOMMAND_MEDIA_PREVIOUSTRACK并将其发送到具有焦点的子窗口
  • 子窗口不会使用它并将消息传递给DefWindowProc()
  • 它将消息传递给子窗口的父级.这种情况会在子窗口嵌套时重复,最终到达顶级窗口
  • 当它调用DefWindowProc时,Windows然后调用一个shell挂钩来触发任何为WH_SHELL挂钩,HSHELL_APPCOMMAND通知调用SetWindowsHookEx()的程序中的回调.像Windows Media Player这样的程序会使用它
  • 如果没有钩子拦截它,它最终会在Explorer中作为最后一个钩子结束,然后最终执行操作.

这里的解决方案是不发送击键,而是向上移动先前列出的处理链.如果可以直接调用shell钩子但是没有公开该功能,那将是很好的.下一步是发送WM_APPCOMMAND消息.

这需要选择一个窗口来发送消息.由于UAC这一天很困难,一个名为UIPI(用户界面权限隔离)的功能可以防止非提升程序向高架程序发送消息.所以你不能只使用前台的窗口,它可能是一个以管理员权限运行的应用程序.与Visual Studio一样,PostMessage和keybd_event()在尝试时失败的可能原因.

诀窍是将它发送到拥有的窗口.你做的很难,你正在使用一个控制台模式项目.这可以解决.Project + Add Reference,选择System.Windows.Forms.在项目中添加一个新类并粘贴下面显示的代码.用,例如,替换你的keybd_event()调用

  AppCommand.Send(AppCommands.MediaNext);  
Run Code Online (Sandbox Code Playgroud)

我把你可以发送的所有命令放在整个套件中.我提供了一个Cleanup()方法来释放资源,没有必要调用它.该代码与.NET 2.0到4.5.1兼容

using System;
using System.Threading;
using System.Windows.Forms;
using System.Runtime.InteropServices;

public enum AppCommands {
    BrowserBack = 1,
    BrowserForward = 2,
    BrowserRefresh = 3,
    BrowserStop = 4,
    BrowserSearch = 5,
    BrowserFavorite = 6,
    BrowserHome = 7,
    VolumeMute = 8,
    VolumeDown = 9,
    VolumeUp = 10,
    MediaNext = 11,
    MediaPrevious = 12,
    MediaStop = 13,
    MediaPlayPause = 14,
    LaunchMail = 15,
    LaunchMediaSelect = 16,
    LaunchApp1 = 17,
    LaunchApp2 = 18,
    BassDown = 19,
    BassBoost = 20,
    BassUp = 21,
    TrebleUp = 22,
    TrebleDown = 23,
    MicrophoneMute = 24,
    MicrophoneVolumeUp = 25,
    MicrophoneVolumeDown = 26,
    Help = 27,
    Find = 28,
    New = 29,
    Open = 30,
    Close = 31,
    Save = 32,
    Print = 33,
    Undo = 34,
    Redo = 35,
    Copy = 36,
    Cut = 37,
    Paste = 38,
    ReplyToMail = 39,
    ForwardMail = 40,
    SendMail = 41,
    SpellCheck = 42,
    Dictate = 43,
    MicrophoneOnOff = 44,
    CorrectionList = 45,
    MediaPlay = 46,
    MediaPause = 47,
    MediaRecord = 48,
    MediaFastForward = 49,
    MediaRewind = 50,
    MediaChannelUp = 51,
    MediaChannelDown = 52,
    Delete = 53,
    Flip3D = 54
}

public static class AppCommand {
    public static void Send(AppCommands cmd) {
        if (frm == null) Initialize();
        frm.Invoke(new MethodInvoker(() => SendMessage(frm.Handle, WM_APPCOMMAND, frm.Handle, (IntPtr)((int)cmd << 16))));
    }

    private static void Initialize() {
        // Run the message loop on another thread so we're compatible with a console mode app
        var t = new Thread(() => {
            frm = new Form();
            var dummy = frm.Handle; 
            frm.BeginInvoke(new MethodInvoker(() => mre.Set()));
            Application.Run();
        });
        t.SetApartmentState(ApartmentState.STA);
        t.IsBackground = true;
        t.Start();
        mre.WaitOne();
    }
    public static void Cleanup() { 
        if (frm != null) {
            frm.BeginInvoke(new MethodInvoker(() => { 
                frm.Close();
                Application.ExitThread();
                mre.Set(); 
            }));
            mre.WaitOne();
            frm = null;
        }
    }

    private static ManualResetEvent mre = new ManualResetEvent(false);
    private static Form frm;

    // Pinvoke
    private const int WM_APPCOMMAND = 0x319;
    [DllImport("user32.dll")]
    private static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wp, IntPtr lp);
}
Run Code Online (Sandbox Code Playgroud)