在UI上的InputField中, 中文输入法的备选框不会跟随在光标旁边, 造成输入不方便.
看到有一个相似的, 可是是WebGL的 : https://blog.csdn.net/Rowley123456/article/details/103726927/
它通过添加Html的Input控件的方式来修改备选框位置, 直接跟平台相关了, 不具有泛用性.
按照这个思路, 直接找Windows的输入控制模块:
[DllImport("imm32.dll")] public static extern IntPtr ImmGetContext(IntPtr hWnd); [DllImport("imm32.dll")] public static extern int ImmReleaseContext(IntPtr hWnd, IntPtr hIMC); [DllImport("imm32.dll")] public static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM lpCompForm); [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern System.IntPtr GetActiveWindow();
然后获取窗口句柄, 设置位置的返回都是正确的, 可是结果并没有改变备选框位置:
void SetInputPos() { IntPtr hImc = ImmGetContext(GetWindowHandle()); COMPOSITIONFORM cf = new COMPOSITIONFORM(); cf.dwStyle = 2; cf.ptCurrentPos.X = 500; cf.ptCurrentPos.Y = 500; bool setcom = ImmSetCompositionWindow(hImc, ref cf); // setcom == true ImmReleaseContext(GetWindowHandle(), hImc); }// 结构体略
这就比较尴尬了, 设置没有反应没有报错......
考虑到Unity应该有各个平台的底层接口的, 以实现标准化的输入(IME接口), 所以在BaseInputModule里面去找一找, 发现它下面有个BaseInput组件:
//StandaloneInputModule : PointerInputModule //PointerInputModule : BaseInputModule public abstract class BaseInputModule : UIBehaviour { protected BaseInput m_InputOverride; // // 摘要: // The current BaseInput being used by the input module. public BaseInput input { get; } ...... }
这个跟输入貌似有关系, 看到里面的变量跟Windows的API有点像:
public class BaseInput : UIBehaviour { public BaseInput(); // // 摘要: // Interface to Input.imeCompositionMode. Can be overridden to provide custom input // instead of using the Input class. public virtual IMECompositionMode imeCompositionMode { get; set; } // // 摘要: // Interface to Input.compositionCursorPos. Can be overridden to provide custom // input instead of using the Input class. public virtual Vector2 compositionCursorPos { get; set; } ...... }
估计只要继承它自己设置compositionCursorPos就能达到效果了, 直接创建一个继承类型, 然后通过反射的方式给StandaloneInputModule设定BaseInput:
[RequireComponent(typeof(InputField))] public class IME_InputFollower : BaseInput { public InputField inputField; public override Vector2 compositionCursorPos { get { return base.compositionCursorPos; } set { base.compositionCursorPos = new Vector2(200,200); // test } } private static void SetCurrentInputFollower(IME_InputFollower target) { var inputModule = EventSystem.current.currentInputModule; if(inputModule) { var field = inputModule.GetType().GetField("m_InputOverride", BindingFlags.Instance | BindingFlags.NonPublic); if(field != null) { field.SetValue(inputModule, target); if(target) { target.inputField.OnPointerDown(new PointerEventData(EventSystem.current)); int caretPosition = string.IsNullOrEmpty(target.inputField.text) == false ? target.inputField.text.Length : 0; target.inputField.caretPosition = caretPosition; } } } } }
当InputField被focus的时候, SetCurrentInputFollower使用反射的方式设定BaseInput到当前的InputModule中, 然后手动触发一下OnPointerDown和设定光标位置, 这样就能刷新输入法备选框了, 不会因为切换InputField而窗口不跟随. 还有就是在编辑器下窗口的大小为Game窗口的大小, 而不是渲染部分的大小, 所以在编辑器下窗口大小与渲染不同的时候计算位置是不对的.
PS : 在测试时发现在Windows下compositionCursorPos的计算方法是窗口坐标, 并且起始坐标为窗口坐上角(0, 0), 不知道是不是DX平台的特点.
填满窗口看看原始的输入法备选框在哪:
已经超出界面范围了, 现在添加IME_InputFollower组件, 来计算一下位置让备选框出现在输入框的左下角:
public override Vector2 compositionCursorPos { get { return base.compositionCursorPos; } set { #if UNITY_STANDALONE var size = new Vector2(Screen.width, Screen.height); Vector3[] coners = new Vector3[4]; (inputField.transform as RectTransform).GetWorldCorners(coners); Vector2 leftBottom = coners[0]; var compositionCursorPos = new Vector2(leftBottom.x, size.y - leftBottom.y); base.compositionCursorPos = compositionCursorPos; #else base.compositionCursorPos = value; #endif } }
证明确实可行, 这样这个逻辑应该就是可以在全部平台中跑了, 只要添加compositionCursorPos的set逻辑就行了, 而平台的差异只要在计算坐标中注意即可(不过除了Windows也没其他需要的平台了).