问题
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:
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?
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?
回答1:
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?
回答2:
Change class @Matze,
internal class KeyboardLayoutScope : IDisposable, IKeyboardLayoutScope
{
private readonly KeyboardLayout currentLayout;
public KeyboardLayoutScope() { }
public KeyboardLayoutScope(CultureInfo culture)
{
currentLayout = KeyboardLayout.GetCurrent();
KeyboardLayout.Load(culture).Activate();
}
public KeyboardLayoutScope(KeyboardLayout currentLayout) => this.currentLayout = currentLayout;
public void Dispose() => currentLayout.Activate();
}
internal interface IKeyboardLayoutScope
{
void Dispose();
}
Other Class
internal sealed class KeyboardLayout
{
private readonly uint hkl;
private KeyboardLayout(CultureInfo cultureInfo) => hkl = NativeMethods.LoadKeyboardLayout(new StringBuilder(cultureInfo.LCID.ToString("x8")), KeyboardLayoutFlags.KLF_ACTIVATE);
private KeyboardLayout(uint hkl) => this.hkl = hkl;
public uint Handle => hkl;
public static KeyboardLayout GetCurrent() => new KeyboardLayout(NativeMethods.GetKeyboardLayout((uint)Thread.CurrentThread.ManagedThreadId));
public static KeyboardLayout Load(CultureInfo culture) => new KeyboardLayout(culture);
public void Activate() => NativeMethods.ActivateKeyboardLayout(hkl, KeyboardLayoutFlags.KLF_SETFORPROCESS);
}
回答3:
Try thisone:
public void switch_keyboard(string lang)
{
string keyboard_lang = InputLanguage.CurrentInputLanguage.Culture.TwoLetterISOLanguageName;
if (keyboard_lang != lang)
{
int en_index = -1;
for (int i = 0; i < System.Windows.Forms.InputLanguage.InstalledInputLanguages.Count - 1; i++)
{
try
{
if (System.Windows.Forms.InputLanguage.InstalledInputLanguages[i].Culture.TwoLetterISOLanguageName == lang)
{ en_index = i; break; }
}
catch { break; }
}
if (en_index != -1)
{ InputLanguage.CurrentInputLanguage = System.Windows.Forms.InputLanguage.InstalledInputLanguages[en_index]; }
}
}
回答4:
Change keyboard layouts through c# code:
using System;
using System.Linq;
using System.Globalization;
using System.Windows.Forms;
...
public static void Switch_keyboard(string lang)
{
CultureInfo cultureInfo = CultureInfo.CreateSpecificCulture(lang);
InputLanguage inputLanguage = InputLanguage.FromCulture(cultureInfo);
InputLanguage.CurrentInputLanguage = inputLanguage;
}
private void button1_Click(object sender, EventArgs e)
{
var list = InputLanguage.InstalledInputLanguages.Cast<InputLanguage>().Select(c => c.Culture.Name).ToList();
Switch_keyboard(list[0]); // "ru-RU" or "ru-BY" ...
}
来源:https://stackoverflow.com/questions/37291533/change-keyboard-layout-from-c-sharp-code-with-net-4-5-2