WinForm IME输入法BUG完美修复

本文来自http://hi.baidu.com/wingingbob/item/a2cb3fc0fe3bd1bb0d0a7b5b

《WinForm IME输入法BUG测试》里,我描述了在.NET Framework 2.0的WinForm中,中文输入法的BUG。这个BUG由来已久,听说在最新的VS2010中也没有真正得到解决。在文章后面,我怀疑是那些产生此类BUG的中文输入法本身在设计上存在的缺陷,才导致了WinForm无法正确识别。不管怎么样,问题出了就得想办法解决,今天的方法是一个“简单而有效的”解决办法,就是在窗体加载时将输入法预置为ImeMode.OnHalf。最终的结果如上图所示,王码五笔可以正确切换出来,而且以半角的方式显示(ImeMode.OnHalf不同于ImeMode.On的地方),只是默认使用英文标点符号。但是你不用担心,主流的中文输入法(如搜狗拼音)会默认采用中文标点的。代码片段如下:

  1. protected override void OnLoad(EventArgs e)      
  2. {   
  3.      KeyPreview = true;      
  4.      DrawTextboxes();      
  5.   
  6.    // 让输入法为开启半角状态      
  7.      ImeMode = ImeMode.OnHalf;      
  8.   
  9.    base.OnLoad(e);      
  10. }  

完美修复

现在是通过对imm32.dll API调用,使之输入法状态为开启,这样就保证了WinForm程序其它窗口的输入法状态也正确显示。重写第一个窗口的OnActivited事件即可。注意,不要再使用Control的ImeMode属性了。测试下面代码,点击按钮打开一个新的窗口,仍然可以正确使用输入法。OK,问题得到完美解决~~!

  1. /* WinForm IME输入法BUG完美修复
  2. * 编译:csc.exe /target:winexe WinformImeBugFixed.cs
  3. */  
  4. using System;   
  5. using System.Windows.Forms;   
  6. using System.Drawing;   
  7. using System.Runtime.InteropServices;   
  8.   
  9. namespace WinformImeBugFixed   
  10. {   
  11.     public class Form1 : Form   
  12.      {
  13.          #region 解决输入法BUG   
  14.         //解决输入法BUG   
  15.          [DllImport("imm32.dll")]   
  16.         public static extern IntPtr ImmGetContext(IntPtr hwnd);   
  17.          [DllImport("imm32.dll")]   
  18.         public static extern bool ImmSetOpenStatus(IntPtr himc, bool b);   
  19.   
  20.         protected override void OnActivated(EventArgs e)   
  21.          {   
  22.             base.OnActivated(e);   
  23.              IntPtr HIme = ImmGetContext(this.Handle);   
  24.              ImmSetOpenStatus(HIme, true);   
  25.          }
  26.          #endregion
  27.          #region 不感兴趣的   
  28.   
  29.         private void DrawTextboxes()   
  30.          {   
  31.              Controls.Clear();   
  32.             int x, y, d;   
  33.              x = y = d = 10;   
  34.             for (int i = 0; i < 2; i++)   
  35.              {   
  36.                  var textbox = new TextBox()   
  37.                  {   
  38.                      Width = 200,   
  39.                      Location = new Point(x, y)   
  40.                  };   
  41.                  y += textbox.Height + d;   
  42.                  textbox.DataBindings.Add("Text", textbox, "ImeMode");   
  43.                  Controls.Add(textbox);   
  44.              }   
  45.          }   
  46.   
  47.         private void DrawButton()   
  48.          {   
  49.              var button = new Button()   
  50.              {   
  51.                  Text = "Show Form2",   
  52.                  Location = new Point(10, 70)   
  53.              };   
  54.              button.Click += delegate  
  55.              {   
  56.                  var form2 = new Form();   
  57.                  form2.Text = "Form2";   
  58.                  var textbox = new TextBox()   
  59.                  {   
  60.                      Width = 200,   
  61.                      Location = new Point(10, 10)   
  62.                  };   
  63.                  form2.Controls.Add(textbox);   
  64.                  form2.Show();   
  65.              };   
  66.              Controls.Add(button);   
  67.          }   
  68.   
  69.         protected override void OnLoad(EventArgs e)   
  70.          {   
  71.              Text = "IME输入法BUG修复 F5-刷新 F1-博客";   
  72.              KeyPreview = true;   
  73.              DrawTextboxes();   
  74.              DrawButton();   
  75.             base.OnLoad(e);   
  76.          }   
  77.   
  78.         public Form1()   
  79.          {   
  80.              InitializeComponent();   
  81.          }   
  82.   
  83.         protected override void OnKeyDown(KeyEventArgs e)   
  84.          {   
  85.             try { HandleKeyDown(e); }   
  86.             finally { base.OnKeyDown(e); }   
  87.          }   
  88.   
  89.         private void HandleKeyDown(KeyEventArgs e)   
  90.          {   
  91.             if (e.KeyCode == Keys.F5) DrawTextboxes();   
  92.             else if (e.KeyCode == Keys.F1) NavigateBlog();   
  93.          }   
  94.   
  95.         private void NavigateBlog()   
  96.          {   
  97.              System.Diagnostics.Process.Start("http://hi.baidu.com/wingingbob/blog/item/20741734532af846251f14f1.html");   
  98.          }
  99.          #endregion
  100.          #region Form1设计器   
  101.         private System.ComponentModel.IContainer components = null;   
  102.         protected override void Dispose(bool disposing)   
  103.          {   
  104.             if (disposing && (components != null))   
  105.              {   
  106.                  components.Dispose();   
  107.              }   
  108.             base.Dispose(disposing);   
  109.          }   
  110.   
  111.         private void InitializeComponent()   
  112.          {   
  113.             this.SuspendLayout();   
  114.             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);   
  115.             this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;   
  116.             this.ClientSize = new System.Drawing.Size(240, 100);   
  117.             this.Name = "Form1";   
  118.             this.Text = "Form1";   
  119.             this.ResumeLayout(false);   
  120.          }
  121.          #endregion
  122.          #region 入口点   
  123.         static class Program   
  124.          {   
  125.              [STAThread]   
  126.             static void Main()   
  127.              {   
  128.                  Application.EnableVisualStyles();   
  129.                  Application.SetCompatibleTextRenderingDefault(false);   
  130.                  Application.Run(new Form1());   
  131.              }   
  132.          }
  133.          #endregion   
  134.      }   
  135. }  

补充:采用OnActivited事件激活输入法,那么这个窗体的TopMost属性应该为false才能使输入法设置有效。这个原因可能是由于设置TopMost为True时,将此句代码写入构造函数里,而此时修复输入法的代码还没有被执行。也就是说,将TopMost=True的代码写在窗体的OnLoad事件中,就没有问题了。于是,我把输入法修复的代码修改如下:

  1. #region 解决输入法BUG   
  2. [DllImport("imm32.dll")]   
  3. public static extern IntPtr ImmGetContext(IntPtr hwnd);   
  4. [DllImport("imm32.dll")]   
  5. public static extern bool ImmSetOpenStatus(IntPtr himc, bool b);   
  6.   
  7. delegate void fixImeDele();   
  8. protected override void OnLoad(EventArgs e)   
  9. {   
  10.      fixImeDele fixime = delegate  
  11.      {   
  12.          IntPtr HIme = ImmGetContext(this.Handle);   
  13.          ImmSetOpenStatus(HIme, true);   
  14.      };   
  15.     this.BeginInvoke(fixime);   
  16.     this.TopMost = true;   
  17.     base.OnLoad(e);   
  18. }
  19. #endregion  

注意这里使用了一个小技巧,用包含BeginInvoke语句的OnLoad方法代替了原先的OnActivited方法,TopMost=True也写在这个OnLoad重载里,这样,窗口置顶和输入法开启的代码全部有效。BTW,在Onload中加入BeginInvoke的方式还可以解决Control.Focus()方法在Load中失效的问题,原理和这个一样,写在委托里调用就是了,范例参见:《WinForm IME输入法BUG测试》

原文地址:https://www.cnblogs.com/xiaofengfeng/p/3357307.html