c# - 将 GetKeyNameText 与特殊键一起使用

标签 c# .net wpf winapi interop

我为 WPF 编写了一个热键控件,并希望向用户显示友好名称。为此,我正在使用 GetKeyNameText .

但是,例如使用 Key.MediaNextTrack 时作为输入,GetKeyNameText 返回 P,这显然看起来不对。任何人都可以帮助我获得这些深奥的 key 的正确名称吗?

我的代码执行以下操作:

  1. 调用KeyInterop.VirtualKeyFromKey获取 Win32 虚拟 key
  2. 调用MapVirtualKey将虚拟键翻译成扫码
  3. 调用GetKeyNameText

完整代码是这样的(需要引用WindowsBase):

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Input;

namespace ConsoleApplication1 {
    class Program {
        static void Main() {
            var key = Key.MediaNextTrack;
            var virtualKeyFromKey = KeyInterop.VirtualKeyFromKey(key);
            var displayString = GetLocalizedKeyStringUnsafe(virtualKeyFromKey);

            Console.WriteLine($"{key}: {displayString}");
        }

        private static string GetLocalizedKeyStringUnsafe(int key) {
            // strip any modifier keys
            long keyCode = key & 0xffff;

            var sb = new StringBuilder(256);

            long scanCode = MapVirtualKey((uint) keyCode, MAPVK_VK_TO_VSC);

            // shift the scancode to the high word
            scanCode = (scanCode << 16); // | (1 << 24);
            if (keyCode == 45 ||
                keyCode == 46 ||
                keyCode == 144 ||
                (33 <= keyCode && keyCode <= 40)) {
                // add the extended key flag
                scanCode |= 0x1000000;
            }

            GetKeyNameText((int) scanCode, sb, 256);
            return sb.ToString();
        }

        private const uint MAPVK_VK_TO_VSC = 0x00;

        [DllImport("user32.dll")]
        private static extern int MapVirtualKey(uint uCode, uint uMapType);

        [DllImport("user32.dll", EntryPoint = "GetKeyNameTextW", CharSet = CharSet.Unicode)]
        private static extern int GetKeyNameText(int lParam, [MarshalAs(UnmanagedType.LPWStr), Out] StringBuilder str, int size);
    }
}

最佳答案

这些媒体键的名称不包含在键盘布局 dll 中,因此无法通过 Win32 API 获得。

这是我在 C++ 中对 GetKeyNameTextW API 的包装:

// Clears keyboard buffer
// Needed to avoid side effects on other calls to ToUnicode API
// http://archives.miloush.net/michkap/archive/2007/10/27/5717859.html
inline void ClearKeyboardBuffer(uint16_t vkCode)
{
    std::array<wchar_t, 10> chars{};
    const uint16_t scanCode = LOWORD(::MapVirtualKeyW(vkCode, MAPVK_VK_TO_VSC_EX));
    int count = 0;
    do
    {
        count = ::ToUnicode(vkCode, scanCode, nullptr, chars.data(), static_cast<int>(chars.size()), 0);
    } while (count < 0);
}

std::string GetStringFromKeyPress(uint16_t scanCode)
{
    std::array<wchar_t, 10> chars{};
    const uint16_t vkCode = LOWORD(::MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX));
    std::array<uint8_t, 256> keyboardState{};

    // Turn on CapsLock to return capital letters
    keyboardState[VK_CAPITAL] = 0b00000001;

    ClearKeyboardBuffer(VK_DECIMAL);

    // For some keyboard layouts ToUnicode() API call can produce multiple chars: UTF-16 surrogate pairs or ligatures.
    // Such layouts are listed here: https://kbdlayout.info/features/ligatures
    int count = ::ToUnicode(vkCode, scanCode, keyboardState.data(), chars.data(), static_cast<int>(chars.size()), 0);

    ClearKeyboardBuffer(VK_DECIMAL);

    return utf8::narrow(chars.data(), std::abs(count));
}

std::string GetScanCodeName(uint16_t scanCode)
{
    static struct
    {
        uint16_t scanCode;
        const char* keyText;
    } mediaKeys[] =
    {
        { 0xe010, "Previous Track"}, // VK_MEDIA_PREV_TRACK
        { 0xe019, "Next Track"}, // VK_MEDIA_NEXT_TRACK
        { 0xe020, "Volume Mute"}, // VK_VOLUME_MUTE
        { 0xe021, "Launch App 2"}, // VK_LAUNCH_APP2
        { 0xe022, "Media Play/Pause"}, // VK_MEDIA_PLAY_PAUSE
        { 0xe024, "Media Stop"},// VK_MEDIA_STOP
        { 0xe02e, "Volume Down"}, // VK_VOLUME_DOWN
        { 0xe030, "Volume Up"}, // VK_VOLUME_UP
        { 0xe032, "Browser Home"}, // VK_BROWSER_HOME
        { 0xe05e, "System Power"}, // System Power (no VK code)
        { 0xe05f, "System Sleep"}, // VK_SLEEP
        { 0xe063, "System Wake"}, // System Wake (no VK code)
        { 0xe065, "Browser Search"}, // VK_BROWSER_SEARCH
        { 0xe066, "Browser Favorites"}, // VK_BROWSER_FAVORITES
        { 0xe067, "Browser Refresh"}, // VK_BROWSER_REFRESH
        { 0xe068, "Browser Stop"}, // VK_BROWSER_STOP
        { 0xe069, "Browser Forward"}, // VK_BROWSER_FORWARD
        { 0xe06a, "Browser Back"}, // VK_BROWSER_BACK
        { 0xe06b, "Launch App 1"}, // VK_LAUNCH_APP1
        { 0xe06c, "Launch Mail"}, // VK_LAUNCH_MAIL
        { 0xe06d, "Launch Media Selector"} // VK_LAUNCH_MEDIA_SELECT
    };

    auto it = std::find_if(std::begin(mediaKeys), std::end(mediaKeys),
        [scanCode](auto& key) { return key.scanCode == scanCode; });
    if (it != std::end(mediaKeys))
        return it->keyText;

    std::string keyText = GetStringFromKeyPress(scanCode);
    std::wstring keyTextWide = utf8::widen(keyText);
    if (!keyTextWide.empty() && !std::iswblank(keyTextWide[0]) && !std::iswcntrl(keyTextWide[0]))
    {
        return keyText;
    }

    std::array<wchar_t, 128> buffer{};
    const LPARAM lParam = MAKELPARAM(0, ((scanCode & 0xff00) ? KF_EXTENDED : 0) | (scanCode & 0xff));
    int count = ::GetKeyNameTextW(static_cast<LONG>(lParam), buffer.data(), static_cast<int>(buffer.size()));

    return utf8::narrow(buffer.data(), count);
}

在RawInput API中你可以通过这样的代码获取完整的扫描码:

// ...got `RAWINPUT* input` from WM_INPUT message

const RAWKEYBOARD& keyboard = input->data.keyboard;

if (keyboard.MakeCode == KEYBOARD_OVERRUN_MAKE_CODE || keyboard.VKey == 0xff/*VK__none_*/)
    return;

bool keyUp = (keyboard.Flags & RI_KEY_BREAK) == RI_KEY_BREAK;

uint16_t scanCode = keyboard.MakeCode;
scanCode |= (keyboard.Flags & RI_KEY_E0) ? 0xe000 : 0;
scanCode |= (keyboard.Flags & RI_KEY_E1) ? 0xe100 : 0;

constexpr uint16_t c_BreakScanCode = 0xe11d; // emitted on Ctrl+NumLock
constexpr uint16_t c_NumLockScanCode = 0xe045;
constexpr uint16_t c_PauseScanCode = 0x0045;

// These are special for historical reasons
// https://en.wikipedia.org/wiki/Break_key#Modern_keyboards
// Without it GetKeyNameTextW API will fail for these keys
if (scanCode == c_BreakScanCode)
    scanCode = c_PauseScanCode;
else if (scanCode == c_PauseScanCode)
    scanCode = c_NumLockScanCode;

std::string scanCodeName = GetScanCodeName(scanCode);

关于c# - 将 GetKeyNameText 与特殊键一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38584015/

相关文章:

c# - 如何防止 .Net Web API 的动态 sql 中的 sql 注入(inject)?

wpf - 在 WPF 中串联静态资源

c# WPF 维护加载程序的单个实例

c# - 自动属性的 Fluent NHibernate PropertyNotFoundException

c# - 使用命令行构建 C#/.NET 时如何设置配置?

c# - 从俄罗斯网站读取 XML 时的编码问题

c# - 使用 OPC UA .NET 从 Kepserver 读取 "Channel1.Device1.Tag1"值

c# - 为什么我的应用程序在注销/关闭时没有关闭(c#/.net winforms)?

c# - 分析重复序列

wpf - "Nested"MVVM问题