本文介绍了使用 .NET 4.5.2 从 C# 代码更改键盘布局的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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.

我一直在寻找解决方案,但直到现在我才找到它,不是在网络上也不是在 SO 上,例如这篇文章:通过代码c#更改键盘布局

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#

我需要将键盘布局更改为英文,以便它可以使用正确的快捷键(和其他字符串).然后我需要将其切换回以前的状态.我正在使用非常有限的 API,所以我只有 SendKeys 可供我使用.

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.

这是工作代码:

//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");
    }
}

所以我的问题是:

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

  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?

有没有更简单的解决方案?我试图查看本机方法,但它对我来说太高了,所以如果这是代替切换键盘布局的方法,我将非常感谢将我的代码转换为本机的任何帮助.有什么建议吗?

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?

推荐答案

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

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;
}

每当我必须使用本机 API 方法时,我都会尝试将它们封装在一个类中,该类对项目代码库的其余部分隐藏它们的声明.所以,我想出了一个名为 KeyboardLayout 的类;该类可以通过给定的 CultureInfo 加载和激活布局,这很方便......

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);
    }
}

如果您只需要让布局在短时间内处于活动状态 - 并且您希望确保在完成后正确恢复布局,您可以使用 IDiposable 编写某种范围类型界面.比如……

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
}

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

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 + shortcut).

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

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?

这篇关于使用 .NET 4.5.2 从 C# 代码更改键盘布局的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

查看更多