【问题标题】:Change keyboard layout from C# code with .NET 4.5.2使用 .NET 4.5.2 从 C# 代码更改键盘布局
【发布时间】:2016-05-18 06:07:31
【问题描述】:

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

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

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

我一直在寻找解决方案,但直到现在我都无法找到它,不是在网络上也不是在 SO 上,例如这个帖子: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");
    }
}

所以我的问题是:

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

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

【问题讨论】:

    标签: c# .net keyboard pinvoke sendkeys


    【解决方案1】:

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

    [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 加载和激活布局,这很方便......

    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),因为它模拟键盘输入,将路由到活动/聚焦窗口。改用SendMessage 函数是合适的,但您可能希望将其与可以正确确定目标窗口的功能结合起来;但解释这种技术将超出本问答的范围。这里的答案说明了一个可能的解决方案:How to send keystrokes to a window?

    【讨论】:

      【解决方案2】:

      更改类@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();
          }
      

      其他类

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

      【讨论】:

      • NativeMethods.LoadKeyboardLayout 在哪里?
      【解决方案3】:

      试试这个:

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

      【讨论】:

      • 用法:switch_keyboard("en")
      【解决方案4】:

      通过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" ...
      }
      

      【讨论】:

        【解决方案5】:

        在 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.
                    }
                }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-10-03
          • 2012-12-20
          • 2018-10-03
          • 1970-01-01
          相关资源
          最近更新 更多