使用 .NET 4.5.2 从 C# 代码更改键盘布局

ib1*_*b11 4 .net c# keyboard pinvoke sendkeys

我正在编写我的 SDL Trados Studio 插件。

插件的最后一部分需要一些 API 根本没有公开的自动化,所以我所拥有的(坚持一些东西)就是自动化默认的键盘快捷键。

我的代码非常适合英语键盘布局(还有匈牙利语!),但它当然不适用于希腊语、俄语等。

我一直在寻找解决方案,但直到现在我都无法找到它,无论是在网络上还是在 SO 上,例如这篇文章:通过代码 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

所以我的问题是:

  1. 有人可以帮我写一个代码来实际存储当前的键盘布局并切换到英语,然后在最后切换回来吗?

  2. 有没有更简单的解决方案?我试图查看本机方法,但它对我来说太高了,所以我真的很感激任何帮助将我的代码转换为本机,如果这是要走的路,而不是切换键盘布局。有什么建议?

Mat*_*tze 5

切换键盘布局需要一些 P/Invoke;您至少需要以下 Windows 函数才能使其正常工作:LoadKeyboardLayout,GetKeyboardLayoutActivateKeyboardLayout. 以下导入声明对我有用...

[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;
}
Run Code Online (Sandbox Code Playgroud)

每当我必须使用本机 API 方法时,我都会尝试将它们封装在一个类中,该类对项目代码库的其余部分隐藏它们的声明。所以,我想出了一个名为KeyboardLayout; 该类可以通过给定的 加载和激活布局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);
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您只需要让布局在短时间内处于活动状态 - 并且您希望确保在完成后正确恢复布局,您可以使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

比你可以这样使用它......

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

您应该知道,在较新版本的 Windows(从 Windows 8 开始)中,不能再为某个进程设置键盘布局,而是为整个系统全局设置 - 并且布局也可以由其他应用程序更改,或通过用户(使用Win+Spacebar快捷方式)。

我还建议不要使用SendKeys(或其本机对应物SendInput),因为它模拟将路由到活动/聚焦窗口的键盘输入。SendMessage改用该函数是合适的,但您可能希望将其与可以正确确定目标窗口的功能结合起来;但解释这种技术将超出本问答的范围。这里的答案说明了一个可能的解决方案:如何将按键发送到窗口?