使用 .NET 4.5.2 从 C# 代码更改键盘布局
Change keyboard layout from C# code with .NET 4.5.2
我正在编写我的 SDL Trados Studio 插件。
插件的最后一部分需要一些 APIs 根本没有公开的自动化,所以我所拥有的(坚持一些东西)是自动化默认键盘快捷键。
我的代码非常适合英语键盘布局(还有匈牙利语!),但它当然不适用于希腊语、俄语等。
我一直在寻找解决方案,但直到现在我才找到它,不是在网络上,也不是在 SO 上,比如这个 post:Change keyboard layouts through code c#
我需要将键盘布局更改为英文,以便它可以使用正确的快捷键(和其他字符串)。然后我需要将它切换回之前的状态。我使用的 API 非常有限,所以我只有 SendKeys
可供使用。
这是工作代码:
//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");
}
}
所以我的问题是:
谁能帮我写一个代码来实际存储当前的键盘布局并切换到英文,然后在最后切换回来?
有没有更简单的解决方案?我试图查看本机方法,但它对我来说太高了,所以我真的很感激任何帮助将我的代码转换为本机方法,如果这是要走的路而不是切换键盘布局。有什么建议吗?
切换键盘布局需要一些P/Invoke;您至少需要以下 Windows 函数才能使其正常工作:LoadKeyboardLayout
、GetKeyboardLayout
和 ActivateKeyboardLayout
。以下导入声明对我有用...
[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;
}
每当我必须使用本机 API 方法时,我都会尝试将它们封装在一个 class 中,从而对项目代码库的其余部分隐藏它们的声明。所以,我想出了一个叫做 KeyboardLayout
的 class; class 可以通过给定的 CultureInfo
加载和激活布局,这很方便...
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);
}
}
如果您只需要让布局在短时间内处于活动状态 - 并且您希望确保在完成后正确恢复布局,您可以使用 IDiposable
接口编写某种范围类型.例如...
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();
}
}
你可以这样使用它...
const int English = 1033;
using (new KeyboardLayoutScope(CultureInfo.GetCultureInfo(English))
{
// the layout will be valid within this using-block
}
你应该知道,在Windows的较新版本中(从Windows 8开始)键盘布局不能再为某个进程设置,而是为整个系统全局设置 -并且布局也可以由其他应用程序或用户更改(使用 Win + Spacebar 快捷方式)。
我还建议不要使用 SendKeys
(或其本机对应 SendInput
),因为它模拟键盘输入,将被路由到 active/focused window。使用 SendMessage
函数是合适的,但您可能希望将其与可以正确确定目标 window 的功能结合起来;但解释这种技术将超出本问答的范围。这个答案说明了一个可能的解决方案:How to send keystrokes to a window?
更改 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();
}
其他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);
}
试试这个:
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]; }
}
}
通过 C# 代码更改键盘布局:
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" ...
}
在 4.6.1 中,此处发布的所有方法均无法正常工作>
我得到了我的解决方案:
// set keyboard layout
LoadKeyboardLayout("en-EN", 0x00000100);
// methods
[DllImport("user32.dll")] public static extern IntPtr GetKeyboardLayout(uint thread);
[DllImport("user32.dll")]
public static extern long LoadKeyboardLayout(
string pwszKLID, // input locale identifier
uint Flags // input locale identifier options
);
public static CultureInfo GetCurrentKeyboardLayout()
{
try
{
int keyboardLayout = GetKeyboardLayout(0).ToInt32() & 0xFFFF;
return new CultureInfo(keyboardLayout);
}
catch
{
return new CultureInfo(1033); // Assume English if something went wrong.
}
}
我正在编写我的 SDL Trados Studio 插件。
插件的最后一部分需要一些 APIs 根本没有公开的自动化,所以我所拥有的(坚持一些东西)是自动化默认键盘快捷键。
我的代码非常适合英语键盘布局(还有匈牙利语!),但它当然不适用于希腊语、俄语等。
我一直在寻找解决方案,但直到现在我才找到它,不是在网络上,也不是在 SO 上,比如这个 post:Change keyboard layouts through code c#
我需要将键盘布局更改为英文,以便它可以使用正确的快捷键(和其他字符串)。然后我需要将它切换回之前的状态。我使用的 API 非常有限,所以我只有 SendKeys
可供使用。
这是工作代码:
//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");
}
}
所以我的问题是:
谁能帮我写一个代码来实际存储当前的键盘布局并切换到英文,然后在最后切换回来?
有没有更简单的解决方案?我试图查看本机方法,但它对我来说太高了,所以我真的很感激任何帮助将我的代码转换为本机方法,如果这是要走的路而不是切换键盘布局。有什么建议吗?
切换键盘布局需要一些P/Invoke;您至少需要以下 Windows 函数才能使其正常工作:LoadKeyboardLayout
、GetKeyboardLayout
和 ActivateKeyboardLayout
。以下导入声明对我有用...
[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;
}
每当我必须使用本机 API 方法时,我都会尝试将它们封装在一个 class 中,从而对项目代码库的其余部分隐藏它们的声明。所以,我想出了一个叫做 KeyboardLayout
的 class; class 可以通过给定的 CultureInfo
加载和激活布局,这很方便...
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);
}
}
如果您只需要让布局在短时间内处于活动状态 - 并且您希望确保在完成后正确恢复布局,您可以使用 IDiposable
接口编写某种范围类型.例如...
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();
}
}
你可以这样使用它...
const int English = 1033;
using (new KeyboardLayoutScope(CultureInfo.GetCultureInfo(English))
{
// the layout will be valid within this using-block
}
你应该知道,在Windows的较新版本中(从Windows 8开始)键盘布局不能再为某个进程设置,而是为整个系统全局设置 -并且布局也可以由其他应用程序或用户更改(使用 Win + Spacebar 快捷方式)。
我还建议不要使用 SendKeys
(或其本机对应 SendInput
),因为它模拟键盘输入,将被路由到 active/focused window。使用 SendMessage
函数是合适的,但您可能希望将其与可以正确确定目标 window 的功能结合起来;但解释这种技术将超出本问答的范围。这个答案说明了一个可能的解决方案:How to send keystrokes to a window?
更改 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();
}
其他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);
}
试试这个:
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]; }
}
}
通过 C# 代码更改键盘布局:
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" ...
}
在 4.6.1 中,此处发布的所有方法均无法正常工作> 我得到了我的解决方案:
// set keyboard layout
LoadKeyboardLayout("en-EN", 0x00000100);
// methods
[DllImport("user32.dll")] public static extern IntPtr GetKeyboardLayout(uint thread);
[DllImport("user32.dll")]
public static extern long LoadKeyboardLayout(
string pwszKLID, // input locale identifier
uint Flags // input locale identifier options
);
public static CultureInfo GetCurrentKeyboardLayout()
{
try
{
int keyboardLayout = GetKeyboardLayout(0).ToInt32() & 0xFFFF;
return new CultureInfo(keyboardLayout);
}
catch
{
return new CultureInfo(1033); // Assume English if something went wrong.
}
}