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

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

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

当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); } } } 

这些是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钩子但是没有公开该function,那将是很好的。 下一步是发送WM_APPCOMMAND消息。

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

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

  AppCommand.Send(AppCommands.MediaNext); 

我把你可以发送的所有命令放在整个套件中。 我提供了一个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); } 

问题是VS在发送消息时具有焦点,您只能将此工作与重点放在正确的应用程序上。

要解决此问题,请在睡眠前调用keybd_event和F5之前放置一个Thread.Sleep ,并在此时将焦点更改为正确的位置。