在c#中记录击键时键入特殊字符时显示的双字符

时间:2013-03-18 23:09:51

标签: c# winforms logging character-encoding keycode

我有一个应用程序会记录用户按下的内容,但是当我按´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
            );

所以现在我尝试过多次调用ToAsciiToUnicode来冲洗真实角色,但没有成功。我做错了吗?

对于ASCII,首先调用´我得到-1,所以我再次调用它,然后我得到1;然后我像a一样按,以获得á,但之后我只获得a。如果我相互使用ToUnicode两次相同,我只会a而不是á,依此类推......

2 个答案:

答案 0 :(得分:5)

  

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

这正是我猜的。我感谢你为我做过腿部工作! : - )

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

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

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

答案 1 :(得分:4)

  • 关于ToAsciiToUnicode 的神话

    在这个问题中,您提到您已经尝试了ToAsciiToUnicode但没有成功。我还搜索了一个相对的问题:

    ToAscii/ToUnicode in a keyboard hook destroys dead keys

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

    • A(不那么)棘手的游戏

      在这个游戏中,我一次给玩家一个随机翻转的50美分硬币。一个人可以挑战从我那里拿一美元的钞票,如果谁收集了一对50美分的硬币一个是头另一个是尾巴

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

      如何在不丢失任何硬币的情况下从我那里获得1美元的账单?

    也许是时间旅行..


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

和我很老的回答:Konami Code in C#

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

  • 代码

    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

  • 关于重音

    我搜索时有两种一般的重音,它们是grave accentacute accent
    急性重音的变音是´,可能的字母是áǽćéǵíḱĺḿńóǿṕŕśúẃýź
    重音的变音是“, and the possible letters areàèìǹòùẁỳ`。

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

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

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

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

    是:

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

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

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

  • 将密钥发送到活动应用

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

    变化

    #if false
    

    #if !false
    

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

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

    一旦识别为特定组合,它就会使用退格字符擦除之前键入的“or”。退格这里是时间机器..

在此演示之后,我想您将知道如何修改它们以合并到您的代码中。