我已经为WPF编写了一个热键控件,并且想要向用户显示友好的名称。为此,我正在使用
GetKeyNameText
。
Key.MediaNextTrack
作为输入时,GetKeyNameText
返回 P
,这显然看起来是错误的。谁能帮我得到这些深奥钥匙的正确名称?
我的代码执行以下操作:
KeyInterop.VirtualKeyFromKey
获取Win32虚拟密钥MapVirtualKey
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"},
{ 0xe019, "Next Track"},
{ 0xe020, "Volume Mute"},
{ 0xe021, "Launch App 2"},
{ 0xe022, "Media Play/Pause"},
{ 0xe024, "Media Stop"},
{ 0xe02e, "Volume Down"},
{ 0xe030, "Volume Up"},
{ 0xe032, "Browser Home"},
{ 0xe05e, "System Power"},
{ 0xe05f, "System Sleep"},
{ 0xe063, "System Wake"},
{ 0xe065, "Browser Search"},
{ 0xe066, "Browser Favorites"},
{ 0xe067, "Browser Refresh"},
{ 0xe068, "Browser Stop"},
{ 0xe069, "Browser Forward"},
{ 0xe06a, "Browser Back"},
{ 0xe06b, "Launch App 1"},
{ 0xe06c, "Launch Mail"},
{ 0xe06d, "Launch Media Selector"}
};
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
if (raw->header.dwType == RIM_TYPEKEYBOARD)
{
RAWKEYBOARD& keyboard = raw->data.keyboard;
// Ignore key overrun state
if (keyboard.MakeCode == KEYBOARD_OVERRUN_MAKE_CODE)
return 0;
// Ignore keys not mapped to any virtual key code
if (keyboard.VKey >= UCHAR_MAX)
return 0;
WORD scanCode = keyboard.MakeCode;
BOOL keyUp = keyboard.Flags & RI_KEY_BREAK;
if (scanCode != 0)
{
// Some apps may send wrong make scan codes with
// high-order bit set (key break code).
// Strip high-order bit and add extended scan code value.
scanCode = MAKEWORD(scanCode & 0x7f, ((keyboard.Flags & RI_KEY_E0) ? 0xe0 : ((keyboard.Flags & RI_KEY_E1) ? 0xe1 : 0x00)));
}
else
{
// Scan codes may be empty for some buttons (like multimedia buttons).
// Try to map them from the virtual key code.
scanCode = LOWORD(MapVirtualKey(keyboard.VKey, MAPVK_VK_TO_VSC_EX));
}
std::string scanCodeName = GetScanCodeName(scanCode);
}
&ffff
。