Search code examples
c#.netkeyboardpinvokesendkeys

Change keyboard layout from C# code with .NET 4.5.2


I am coding away on my plugin for SDL Trados Studio.

The last part of the plugin requires some automation that is not exposed by the APIs at all, so all I have (hold on to something) is to automate the default keyboard shortcuts.

I have the code working perfectly for the English keyboard layout (and Hungarian, too!), but it of course does not work for Greek, Russian and so forth.

I have been searching for the solution but I was not able to find it until now, not on the web nor on SO, such as this post: Change keyboard layouts through code c#

I need to change the keyboard layout to English so it can take the correct shortcuts (and other character strings). Then I need to switch it back to what it was before. I am working with a very limited API, so I only have SendKeys at my disposal.

Here is the working code:

//Save the document
SendKeys.SendWait("^s");
//Open files view
SendKeys.SendWait("%v");

SendKeys.SendWait("i");
SendKeys.SendWait("1");
Application.DoEvents();

//get url and credentials from a custom input form
string[] psw = UploadData.GetPassword(
    Settings.GetValue("Upload", "Uri", ""), 
    Vars.wsUsername == null ? Settings.GetValue("Upload", "User", "") : Vars.wsUsername, 
    Vars.wsPassword == null ? "" : Vars.wsPassword
    );
Application.DoEvents();

if (psw != null)
{
    try
    {
        //start upload
        SendKeys.SendWait("%h");
        SendKeys.Send("r");

        //select all files
        SendKeys.Send("%a");
        SendKeys.Send("%n");
        //enter login url
        SendKeys.Send("%l");
        SendKeys.Send("{TAB}");
        SendKeys.Send(psw[0]);
        SendKeys.Send("{TAB}");
        SendKeys.Send("{ENTER}");

        //enter username
        SendKeys.Send("%l");
        SendKeys.Send("+{END}");
        SendKeys.Send(psw[1]);
        //enter credentials
        SendKeys.Send("%p");
        SendKeys.Send(SendEscape(psw[2]));
        SendKeys.Send("{ENTER}");
        //start upload
        SendKeys.SendWait("%f");
    }
    catch (Exception)
    {
        MessageBox.Show("Cannot do automatic upload, please use the default method of Trados Studio.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
    }
    finally
    {
        //switch back to editor view
        SendKeys.SendWait("%vd");
    }
}

So the questions I have:

  1. Can somebody help me with a code to actually store the current keyboard layout and switch to English, then switch it back at the end?

  2. Is there a simpler solution? I tried to look at the native methods but it is too high for me, so I would really appreciate any help to convert my code into native if that is the way to go instead of switching the keyboard layout. Any suggestions?


Solution

  • Switching the keyboard layout requires some P/Invoke; you´ll need at least the following Windows functions to get it working: LoadKeyboardLayout, GetKeyboardLayout and ActivateKeyboardLayout. The following import declarations worked for me...

    [DllImport("user32.dll", 
        CallingConvention = CallingConvention.StdCall, 
        CharSet = CharSet.Unicode, 
        EntryPoint = "LoadKeyboardLayout", 
        SetLastError = true, 
        ThrowOnUnmappableChar = false)]
    static extern uint LoadKeyboardLayout(
        StringBuilder pwszKLID, 
        uint flags);
    
    [DllImport("user32.dll", 
        CallingConvention = CallingConvention.StdCall,
        CharSet = CharSet.Unicode, 
        EntryPoint = "GetKeyboardLayout", 
        SetLastError = true, 
        ThrowOnUnmappableChar = false)]
    static extern uint GetKeyboardLayout(
        uint idThread);
    
    [DllImport("user32.dll", 
        CallingConvention = CallingConvention.StdCall, 
        CharSet = CharSet.Unicode, 
        EntryPoint = "ActivateKeyboardLayout", 
        SetLastError = true, 
        ThrowOnUnmappableChar = false)]
    static extern uint ActivateKeyboardLayout(
        uint hkl,
        uint Flags);
    
    static class KeyboardLayoutFlags
    {
        public const uint KLF_ACTIVATE = 0x00000001;
        public const uint KLF_SETFORPROCESS = 0x00000100;
    }
    

    Whenever I have to use native API methods I try to encapsulate them in a class that hides their declaration from the rest of the project´s codebase. So, I came up with a class called KeyboardLayout; that class can load and activate a layout by a given CultureInfo, which comes in handy...

    internal sealed class KeyboardLayout
    {
        ...
    
        private readonly uint hkl;
    
        private KeyboardLayout(CultureInfo cultureInfo)
        {
            string layoutName = cultureInfo.LCID.ToString("x8");
    
            var pwszKlid = new StringBuilder(layoutName);
            this.hkl = LoadKeyboardLayout(pwszKlid, KeyboardLayoutFlags.KLF_ACTIVATE);
        }
    
        private KeyboardLayout(uint hkl)
        {
            this.hkl = hkl;
        }
    
        public uint Handle
        {
            get
            {
                return this.hkl;
            }
        }
    
        public static KeyboardLayout GetCurrent()
        {
            uint hkl = GetKeyboardLayout((uint)Thread.CurrentThread.ManagedThreadId);
            return new KeyboardLayout(hkl);
        }
    
        public static KeyboardLayout Load(CultureInfo culture)
        {
            return new KeyboardLayout(culture);
        }
    
        public void Activate()
        {
            ActivateKeyboardLayout(this.hkl, KeyboardLayoutFlags.KLF_SETFORPROCESS);
        }
    }
    

    If you only need to have the layout be active for a short while - and you want make sure to properly restore the layout when done, you could write some kind of a scope type using the IDiposable interface. For instance...

    class KeyboardLayoutScope : IDiposable
    {
        private readonly KeyboardLayout currentLayout;
    
        public KeyboardLayoutScope(CultureInfo culture)
        {
            this.currentLayout = KeyboardLayout.GetCurrent();
            var layout = KeyboardLayout.Load(culture);
            layout.Activate();
        }
    
        public void Dispose()
        {
            this.currentLayout.Activate();
        }
    }
    

    Than you can use it like this...

    const int English = 1033;
    using (new KeyboardLayoutScope(CultureInfo.GetCultureInfo(English))
    {
        // the layout will be valid within this using-block
    }
    

    You should know that in newer versions of Windows (beginning in Windows 8) the keyboard layout cannot be set for a certain process anymore, instead it is set globally for the entire system - and the layout can also be changed by other applications, or by the user (using the Win + Spacebar shortcut).

    I would also recommend to not use SendKeys (or its native counterpart SendInput) since it simulates keyboard input which will be routed to the active/focused window. Using the SendMessage function instead is suitable, but you might want combine that with functionality that can properly determine the target window; but to explain such technique would go beyond the scope of the this question and answer. This answer here illustrates a possible solution: How to send keystrokes to a window?