1

我有一个应用程序可以记录用户按下的任何内容,但是当我按下特殊字符(如´with a, 来获取á)时,我得到´´a; 当我想得到同样的东西时à,我得到``a了,所以所有特殊字符都输入两次,然后输入常规字符。

我一直在寻找,真的找不到任何东西。但我注意到问题出在ToAscii方法上,没有正确输入字符。

public string GetString(IntPtr lParam, int vCode)
{
    try
    {
        bool shift = Keys.Shift == Control.ModifierKeys || Console.CapsLock;

        string value = ""; 

        KeyboardHookStruct MyKeyboardHookStruct = 
            (KeyboardHookStruct)Marshal.PtrToStructure(
                lParam, typeof(KeyboardHookStruct));

        byte[] keyState = new byte[256];
        byte[] inBuffer = new byte[2];

        DllClass.GetKeyboardState(keyState);

        var ascii=
            DllClass.ToAscii(
                MyKeyboardHookStruct.vkCode, 
                MyKeyboardHookStruct.scanCode, 
                keyState, inBuffer, MyKeyboardHookStruct.flags
                );

        if (ascii == 1)
        {
            char key = (char)inBuffer[0];

            if ((shift) && Char.IsLetter(key))
                key = Char.ToUpper(key);

            value = key.ToString();
        }

        return value;
    }
    catch (Exception)
    {
        return "";
    }
}

我错过了什么或做错了什么?所有其他字符都可以正常工作,但特殊字符以双字符形式出现。


编辑:

尝试使用ToUnicode

[DllImport("USER32.DLL", CharSet = CharSet.Unicode)]
public static extern int ToUnicode(
    uint virtualKey, uint scanCode, byte[] keyStates, 
    [MarshalAs(UnmanagedType.LPArray)] [Out] char[] chars, 
    int charMaxCount, uint flags);

public string GetString(IntPtr lParam, int vCode)
{
    try
    {
        bool shift = Keys.Shift == Control.ModifierKeys || Console.CapsLock;

        string value = ""; 

        KeyboardHookStruct MyKeyboardHookStruct = 
            (KeyboardHookStruct)Marshal.PtrToStructure(
                lParam, typeof(KeyboardHookStruct));

        byte[] keyState = new byte[256];
        byte[] inBuffer = new byte[2];

        char[] chars = new char[2];

        DllClass.GetKeyboardState(keyState);

        int val = 0;

        val = ToUnicode(
                (uint)MyKeyboardHookStruct.vkCode, 
                (uint)MyKeyboardHookStruct.scanCode, 
                keyState, chars, chars.Length, 0
                );

        val = ToUnicode(
                (uint)MyKeyboardHookStruct.vkCode, 
                (uint)MyKeyboardHookStruct.scanCode, 
                keyState, chars, chars.Length, 0
                );

        if (val == 1)
        {
            char key = (char)chars[0];

            if ((shift) && Char.IsLetter(key))
                key = Char.ToUpper(key);

            value = key.ToString();
        }

        return value;
    }
    catch (Exception)
    {
        return "";
    }
}

有人请帮助我,我真的需要弄清楚这一点=/


编辑:

int val = -1;

if (IsDeadKey((uint)vCode))
{
    while (val == -1)
    {
        val = ToUnicode(
                (uint)MyKeyboardHookStruct.vkCode, 
                (uint)MyKeyboardHookStruct.scanCode, 
                keyState, chars, chars.Length, 0
                );
    }
}
else
    val = ToUnicode(
            (uint)MyKeyboardHookStruct.vkCode, 
            (uint)MyKeyboardHookStruct.scanCode, 
            keyState, chars, chars.Length, 0
            );

所以现在我尝试调用ToAsciiorToUnicode几次来刷新真实角色,但没有成功。我做错了吗?

就像 ASCII 一样,首先调用´I get -1,所以我再次调用它,然后我得到1; 然后我按like a,得到á,但后来我只得到a。同样的事情,如果我ToUnicode一个接一个地使用两次,我得到的只是a代替á,等等......

4

2 回答 2

5

但我注意到问题出在 ToAsciii 方法中,没有正确输入字符。

这正是我即将猜测的。我很感激你为我完成了腿部工作!:-)

问题是这些“特殊”字符不是ASCII 字符。也就是说,它们实际上是某种不属于 ASCII 字符集的花哨的 Unicode 字符。

当您尝试将它们转换为 ASCII 字符时,该函数可能会尽其所能,将构成单独字符的代码点分解á为单独的字符´a.

显然这不是你想要的。您想将á其视为单个字符,因此您需要使用 Unicode。这不是一个真正的问题:Windows 在内部使用 Unicode 至少十年了。抛弃过时的ToAscii功能;相反,您需要使用MapVirtualKeyMapVirtualKeyEx将通过低级键盘挂钩获得的虚拟键代码转换为字符值。

于 2013-03-18T23:34:11.107 回答
3
  • ToAscii关于和的神话ToUnicode

    在问题中,您提到您已经尝试过两者但ToAscii均未ToUnicode成功。我还搜索了一个相对的问题:

    键盘钩子中的 ToAscii/ToUnicode 会破坏死键

    我不是说任何答案是对还是错。但是,我们可以考虑:

    • 一个(不是那么)棘手的游戏

      在这个游戏中,我一次给玩家一个随机翻转的 50 美分硬币。如果谁收集了一对 50 美分硬币,一个是正面另一个是反面,可以挑战从我这里得到一美元钞票。

      如果一个人放弃挑战,那么谁能保留这 50 美分,然后游戏重新开始。如果谁尝试但没有收集到两个符合规则,那么我要求将我给的那些归还给我。

      一个人怎么能从我这里得到一美元的钞票而不会丢失任何硬币?

    也许是穿越时空..


基于 CodeProject上的 [ Processing Global Mouse and Keyboard Hooks in C# ]

和我很老的答案:C# 中的 Konami 代码

为了回答你的问题,我做了一些修改。

  • 代码

    namespace Gma.UserActivityMonitor {
        using System.Diagnostics;
        using System.Windows.Forms;
    
        using System.Collections.Generic;
        using System.Linq;
    
        partial class HookManager {
            private static int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) {
                // indicates if any of underlaing events set e.Handled flag
                bool handled=false;
    
                if(nCode>=0) {
                    // read structure KeyboardHookStruct at lParam
                    var MyKeyboardHookStruct=
                        (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
    
                    // raise KeyDown
                    if(s_KeyDown!=null&&(wParam==WM_KEYDOWN||wParam==WM_SYSKEYDOWN)) {
                        Keys keyData=(Keys)MyKeyboardHookStruct.VirtualKeyCode;
                        KeyEventArgs e=new KeyEventArgs(keyData);
                        s_KeyDown.Invoke(null, e);
                        handled=e.Handled;
                    }
    
                    // raise KeyPress
                    if(s_KeyPress!=null&&wParam==WM_KEYDOWN) {
                        var keyText=GetString(lParam, nCode, ref handled);
    
                        if(""!=keyText) {
                            var keyChar=keyText.First();
                            Debug.Print("keyText => {0}", keyText);
    
    #if false
                            if(AccentFormatter.Combination.Values.Contains(keyChar)) {
                                SendKeys.Send("\b"+keyText);
                                return -1;
                            }
    #endif
                        }
                    }
    
                    // raise KeyUp
                    if(s_KeyUp!=null&&(wParam==WM_KEYUP||wParam==WM_SYSKEYUP)) {
                        Keys keyData=(Keys)MyKeyboardHookStruct.VirtualKeyCode;
                        KeyEventArgs e=new KeyEventArgs(keyData);
                        s_KeyUp.Invoke(null, e);
                        handled=handled||e.Handled;
                    }
                }
    
                // if event handled in application do not handoff to other listeners
                if(handled)
                    return -1;
    
                // forward to other application
                return CallNextHookEx(s_KeyboardHookHandle, nCode, wParam, lParam);
            }
    
            public static String GetString(IntPtr lParam, int vCode, ref bool handled) {
                var MyKeyboardHookStruct=
                    (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
    
                bool isDownShift=((GetKeyState(VK_SHIFT)&0x80)==0x80?true:false);
                bool isDownCapslock=(GetKeyState(VK_CAPITAL)!=0?true:false);
    
                byte[] keyState=new byte[256];
                GetKeyboardState(keyState);
                byte[] inBuffer=new byte[2];
    
                var keyText="";
    
                var ascii=
                    ToAscii(
                        MyKeyboardHookStruct.VirtualKeyCode,
                        MyKeyboardHookStruct.ScanCode,
                        keyState, inBuffer, MyKeyboardHookStruct.Flags
                        );
    
                if(ascii==1) {
                    char key=(char)inBuffer[0];
    
                    if((isDownCapslock^isDownShift)&&Char.IsLetter(key))
                        key=Char.ToUpper(key);
    
                    KeyPressEventArgs e=new KeyPressEventArgs(key);
                    s_KeyPress.Invoke(null, e);
                    handled=handled||e.Handled;
    
                    keyText=new String(new[] { e.KeyChar });
                    var sequence=KeySequence.Captured(e.KeyChar);
    
                    if(null!=sequence)
                        keyText=sequence.ToString(AccentFormatter.Default);
                }
    
                return keyText;
            }
        }
    
        public class KeySequence {
            public String ToString(IFormatProvider provider) {
                return
                    null==provider
                        ?new String(Sequence.Select(x => (char)x).ToArray())
                        :String.Format(provider, "{0}", Sequence);
            }
    
            public override String ToString() {
                return this.ToString(default(IFormatProvider));
            }
    
            public bool Captures(int keyValue) {
                for(var i=Sequence.Length; i-->0; ) {
                    if(Sequence[i]!=keyValue) {
                        if(0==i)
                            Count=0;
    
                        continue;
                    }
    
                    if(Count!=i)
                        continue;
    
                    ++Count;
                    break;
                }
    
                var x=Sequence.Length==Count;
                Count=x?0:Count;
                return x;
            }
    
            public KeySequence(int[] newSequence) {
                Sequence=newSequence;
            }
    
            public static KeySequence Captured(int keyValue) {
                return m_List.FirstOrDefault(x => x.Captures(keyValue));
            }
    
            public int Count {
                private set;
                get;
            }
    
            public int[] Sequence {
                set;
                get;
            }
    
            static KeySequence() {
                m_List.AddRange(
                    from x in AccentFormatter.Combination.Keys
                    let intArray=x.Select(c => (int)c).ToArray()
                    select new KeySequence(intArray)
                    );
            }
    
            static readonly List<KeySequence> m_List=new List<KeySequence>();
        }
    
        public class AccentFormatter: IFormatProvider, ICustomFormatter {
            String ICustomFormatter.Format(String format, object arg, IFormatProvider formatProvider) {
                return GetAccent(new String((arg as int[]).Select(x => (char)x).ToArray()));
            }
    
            object IFormatProvider.GetFormat(Type formatType) {
                return typeof(ICustomFormatter)!=formatType?null:this;
            }
    
            public static String GetAccent(String input) {
                return
                    Combination.Keys.Contains(input, StringComparer.OrdinalIgnoreCase)
                        ?Combination[input].ToString()
                        :"";
            }
    
            static AccentFormatter() {
                AcuteSymbol=((char)0xb4).ToString();
                GraveSymbol=('`').ToString();
    
                var ae=(char)0xe6;
                var oe=(char)0xf8;
                AcuteCandidates="acegiklmnoprsuwyz".ToArray().Concat(new[] { ae, oe }).ToArray();
                GraveCandidates="aeinouwy".ToArray();
    
                var lowerAcuteAccents=(
                    new[] { 
                        0xe1, 0x107, 
                        0xe9, 0x1f5, 
                        0xed, 0x1e31, 0x13a, 0x1e3f, 0x144, 
                        0xf3, 0x1e55, 0x155, 0x15b, 
                        0xfa, 0x1e83, 0xfd, 0x17a, 
                        0x1fd, 0x1ff
                    }
                    ).Select(
                        (x, i) => new {
                            Key=AcuteSymbol+AcuteCandidates[i],
                            Value=(char)x
                        }
                        );
    
                var upperAcuteAccents=(
                    new[] { 
                        0xc1, 0x106, 
                        0xc9, 0x1f4, 
                        0xcd, 0x1e30, 0x139, 0x1e3e, 0x143, 
                        0xd3, 0x1e54, 0x154, 0x15a, 
                        0xda, 0x1e82, 0xdd, 0x179, 
                        0x1fc, 0x1fe
                    }
                    ).Select(
                        (x, i) => new {
                            Key=AcuteSymbol+char.ToUpper(AcuteCandidates[i]),
                            Value=(char)x
                        }
                        );
    
                var lowerGraveAccents=(
                    new[] { 0xe0, 0xe8, 0xec, 0x1f9, 0xf2, 0xf9, 0x1e81, 0x1ef3 }
                    ).Select(
                        (x, i) => new {
                            Key=GraveSymbol+GraveCandidates[i],
                            Value=(char)x
                        }
                        );
    
                var upperGraveAccents=(
                    new[] { 0xc0, 0xc8, 0xcc, 0x1f8, 0xd2, 0xd9, 0x1e80, 0x1ef2 }
                    ).Select(
                        (x, i) => new {
                            Key=GraveSymbol+char.ToUpper(GraveCandidates[i]),
                            Value=(char)x
                        }
                        );
    
                Combination=
                    lowerAcuteAccents
                        .Concat(upperAcuteAccents)
                        .Concat(lowerGraveAccents)
                        .Concat(upperGraveAccents)
                        .ToDictionary(x => x.Key, x => x.Value);
            }
    
            public static readonly Dictionary<String, char> Combination;
            public static readonly String AcuteSymbol, GraveSymbol;
            public static readonly char[] AcuteCandidates, GraveCandidates;
            public static readonly AccentFormatter Default=new AccentFormatter();
        }
    }
    

    首先,使用从 CodeProject 下载的代码,找到HookManager.Callbacks.cs删除整个方法 KeyboardHookProc

    然后您可以将上面的代码保存在一个新文件中,例如HookManager.Modified.cs并将其添加到项目中Gma.UserActivityMonitor,或者将其粘贴到HookManager.Callbacks.cs.

    记得设置Gma.UserActivityMonitorDemo启动项目。将目标框架设置为 2.0,您可能希望设置为更高。

  • 输入→输出

    Vmh1A.jpg

  • 关于重音

    正如我搜索的那样,有两种一般口音,它们是重音锐音
    重音的变音符号是´,可能的字母是áǽćéǵíḱĺḿńóǿṕŕśúẃýź
    重音的变音符号是`` , and the possible letters areàèìǹòùẁỳ`。

    它们都可以大写或小写,但我没有找到一种足够简单的方法在框架内置的类中使用它们。例如,我不能使用ToUpperorToLower来获得与Ǹand的相反情况ǹ,我什至尝试过ToUpperInvariantand ToLowerInvariant。因此,我选择在 中使用硬编码序列AccentFormatter,您可以将其更改为从文件中读取,或者自己实现另一个自定义格式化程序。

  • 十六进制表示的数组排列

    在代码中,我将重音序列排列为:

    0xc1, 0x106,
    0xc9, 0x1f4,
    0xcd, 0x1e30, 0x139, 0x1e3e, 0x143,
    0xd3, 0x1e54, 0x154, 0x15a,
    0xda, 0x1e82, 0xdd, 0x179,
    0x1fc, 0x1fe
    

    这是:

    阿克
    ÉǴ
    ÍḰĹḾŃ
    ÓṔŔŚ
    ÚẂÝŹ
    ǼǾ
    

    ǼandǾ放在后面是因为Æand在ØASCII(非扩展)中没有对应的字符。

    我更喜欢使用默认代码页将代码保存在 ANSI 中,因为我CultureInfo与字母表中的语言不同。您可能希望将它们更改为精确的字符以提高可读性。

  • 将密钥发送到活动应用程序

    如果要将密钥发送到活动应用程序,请在代码中进行以下更改:

    改变

    #if false
    

    #if !false
    

    在条件编译块内部,代码片段是

    if(AccentFormatter.Combination.Values.Contains(keyChar)) {
        SendKeys.Send("\b"+keyText);
        return -1;
    }
    

    or一旦识别为特定组合,它会使用退格字符删除先前键入的 ` ´`。这里的退格是时间机器..

在这个演示之后,我想你会知道如何修改它们以合并到你的代码中。

于 2013-03-30T16:12:02.160 回答