2

这是我使用过的程序,与旧 xp 日的许多更改相比,它是一个 cmd 行程序,它将更改媒体应用程序(Spotify、vlc、mediaPlayer)中的轨道,就像带有下一个/上一个轨道按钮的键盘一样。

目前我正在使用没有这些按钮但具有执行此程序的可编程键的 Microsoft 自然键盘。

这一切都有效,除了 Visual Studio 2012/2013 具有焦点(Windows 7)(没有尝试过其他版本),它在 Sql management studio 中有效。

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

2 回答 2

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() 在您尝试时失败的可能原因。

诀窍是将它发送到拥有的窗口。您遇到困难,您正在使用控制台模式项目。这可以解决。项目 + 添加引用,选择 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);
}
于 2014-02-01T23:05:22.740 回答
-1

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

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

于 2014-01-27T15:59:50.510 回答