// Set candidate window position. // Borrowed from https://github.com/chromium/chromium/blob/master/ui/base/ime/win/imm32_manager.cc public void SetCandidateWindow(TsfSharp.Rect caretRect) { int x = caretRect.Left; int y = caretRect.Top; if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE) { // Chinese IMEs ignore function calls to ::ImmSetCandidateWindow() // when a user disables TSF (Text Service Framework) and CUAS (Cicero // Unaware Application Support). // On the other hand, when a user enables TSF and CUAS, Chinese IMEs // ignore the position of the current system caret and uses the // parameters given to ::ImmSetCandidateWindow() with its 'dwStyle' // parameter CFS_CANDIDATEPOS. // Therefore, we do not only call ::ImmSetCandidateWindow() but also // set the positions of the temporary system caret. var candidateForm = new NativeMethods.CANDIDATEFORM(); candidateForm.dwStyle = NativeMethods.CFS_CANDIDATEPOS; candidateForm.ptCurrentPos.X = x; candidateForm.ptCurrentPos.Y = y; NativeMethods.ImmSetCandidateWindow(_defaultImc, ref candidateForm); } if (PRIMARYLANGID(_inputLanguageId) == LANG_JAPANESE) { NativeMethods.SetCaretPos(x, caretRect.Bottom); } else { NativeMethods.SetCaretPos(x, y); } // Set composition window position also to ensure move the candidate window position. var compositionForm = new NativeMethods.COMPOSITIONFORM(); compositionForm.dwStyle = NativeMethods.CFS_POINT; compositionForm.ptCurrentPos.X = x; compositionForm.ptCurrentPos.Y = y; NativeMethods.ImmSetCompositionWindow(_defaultImc, ref compositionForm); if (PRIMARYLANGID(_inputLanguageId) == LANG_KOREAN) { // Chinese IMEs and Japanese IMEs require the upper-left corner of // the caret to move the position of their candidate windows. // On the other hand, Korean IMEs require the lower-left corner of the // caret to move their candidate windows. y += kCaretMargin; } // Need to return here since some Chinese IMEs would stuck if set // candidate window position with CFS_EXCLUDE style. if (PRIMARYLANGID(_inputLanguageId) == LANG_CHINESE) { return; } // Japanese IMEs and Korean IMEs also use the rectangle given to // ::ImmSetCandidateWindow() with its 'dwStyle' parameter CFS_EXCLUDE // to move their candidate windows when a user disables TSF and CUAS. // Therefore, we also set this parameter here. var excludeRectangle = new NativeMethods.CANDIDATEFORM(); compositionForm.dwStyle = NativeMethods.CFS_EXCLUDE; compositionForm.ptCurrentPos.X = x; compositionForm.ptCurrentPos.Y = y; compositionForm.rcArea.Left = x; compositionForm.rcArea.Top = y; compositionForm.rcArea.Right = caretRect.Right; compositionForm.rcArea.Bottom = caretRect.Bottom; NativeMethods.ImmSetCandidateWindow(_defaultImc, ref excludeRectangle); }
/// <summary> /// 候補文字ウィンドウの位置を設定 /// </summary> /// <param name="hWnd"></param> private void SetCandidateWindowPos(IntPtr hWnd) { IntPtr hImc = NativeMethods.ImmGetContext(hWnd); if (hImc != null) { NativeMethods.CANDIDATEFORM cndFrm = new NativeMethods.CANDIDATEFORM(); cndFrm.ptCurrentPos.X = caret.GetPos().X; cndFrm.ptCurrentPos.Y = caret.GetPos().Y; cndFrm.dwStyle = NativeMethods.CFS_CANDIDATEPOS;// 候補文字ウィンドウの位置を指定する。 NativeMethods.ImmSetCandidateWindow(hImc, ref cndFrm); NativeMethods.ImmReleaseContext(hWnd, hImc); } }
private void OnWmImeNotify(IntPtr hwnd, IntPtr wParam) { IntPtr himc; // we don't have to do anything if _editor is null. if (!IsInKeyboardFocus) { return; } if ((int)wParam == NativeMethods.IMN_OPENCANDIDATE) { himc = UnsafeNativeMethods.ImmGetContext(new HandleRef(this, hwnd)); if (himc != IntPtr.Zero) { NativeMethods.CANDIDATEFORM candform = new NativeMethods.CANDIDATEFORM(); // // At IMN_OPENCANDIDATE, we need to set the candidate window location to hIMC. // if (IsReadingWindowIme()) { // Level 2 for Chinese legacy IMEs. // We have already set the composition form. The candidate window will follow it. candform.dwIndex = 0; candform.dwStyle = NativeMethods.CFS_DEFAULT; candform.rcArea.left = 0; candform.rcArea.right = 0; candform.rcArea.top = 0; candform.rcArea.bottom = 0; candform.ptCurrentPos = new NativeMethods.POINT(0, 0); } else { ITextView view; ITextPointer startNavigator; ITextPointer endNavigator; ITextPointer caretNavigator; GeneralTransform transform; Point milPointTopLeft; Point milPointBottomRight; Point milPointCaret; Rect rectStart; Rect rectEnd; Rect rectCaret; CompositionTarget compositionTarget; compositionTarget = _source.CompositionTarget; if (_startComposition != null) { startNavigator = _startComposition.CreatePointer(); } else { startNavigator = _editor.Selection.Start.CreatePointer(); } if (_endComposition != null) { endNavigator = _endComposition.CreatePointer(); } else { endNavigator = _editor.Selection.End.CreatePointer(); } if (_startComposition != null) { caretNavigator = _caretOffset > 0 ? _startComposition.CreatePointer(_caretOffset, LogicalDirection.Forward) : _endComposition; } else { caretNavigator = _editor.Selection.End.CreatePointer(); } ITextPointer startPosition = startNavigator.CreatePointer(LogicalDirection.Forward); ITextPointer endPosition = endNavigator.CreatePointer(LogicalDirection.Backward); ITextPointer caretPosition = caretNavigator.CreatePointer(LogicalDirection.Forward); // We need to update the layout before getting rect. It could be dirty. if (!startPosition.ValidateLayout() || !endPosition.ValidateLayout() || !caretPosition.ValidateLayout()) { return; } view = TextEditor.GetTextView(RenderScope); rectStart = view.GetRectangleFromTextPosition(startPosition); rectEnd = view.GetRectangleFromTextPosition(endPosition); rectCaret = view.GetRectangleFromTextPosition(caretPosition); // Take the "extended" union of the first and last char's bounding box. milPointTopLeft = new Point(Math.Min(rectStart.Left, rectEnd.Left), Math.Min(rectStart.Top, rectEnd.Top)); milPointBottomRight = new Point(Math.Max(rectStart.Left, rectEnd.Left), Math.Max(rectStart.Bottom, rectEnd.Bottom)); milPointCaret = new Point(rectCaret.Left, rectCaret.Bottom); // Transform to root visual coordinates. transform = RenderScope.TransformToAncestor(compositionTarget.RootVisual); transform.TryTransform(milPointTopLeft, out milPointTopLeft); transform.TryTransform(milPointBottomRight, out milPointBottomRight); transform.TryTransform(milPointCaret, out milPointCaret); // Transform to device units. milPointTopLeft = compositionTarget.TransformToDevice.Transform(milPointTopLeft); milPointBottomRight = compositionTarget.TransformToDevice.Transform(milPointBottomRight); milPointCaret = compositionTarget.TransformToDevice.Transform(milPointCaret); // Build CANDIDATEFORM. CANDIDATEFORM is window coodidate. candform.dwIndex = 0; candform.dwStyle = NativeMethods.CFS_EXCLUDE; candform.rcArea.left = ConvertToInt32(milPointTopLeft.X); candform.rcArea.right = ConvertToInt32(milPointBottomRight.X); candform.rcArea.top = ConvertToInt32(milPointTopLeft.Y); candform.rcArea.bottom = ConvertToInt32(milPointBottomRight.Y); candform.ptCurrentPos = new NativeMethods.POINT(ConvertToInt32(milPointCaret.X), ConvertToInt32(milPointCaret.Y)); } // Call IMM32 to set new candidate position to hIMC. // ImmSetCandidateWindow fails when // - candform.dwIndex is invalid (over 4). // - himc belongs to other threads. // - fail to lock IMC. // Those cases are ignorable for us. // In addition, it does not set win32 last error and we have no clue to handle error. #pragma warning suppress 6031 UnsafeNativeMethods.ImmSetCandidateWindow(new HandleRef(this, himc), ref candform); UnsafeNativeMethods.ImmReleaseContext(new HandleRef(this, hwnd), new HandleRef(this, himc)); } // We want to pass this message to DefWindowProc. // We don't update "handled". } }
/// <summary> /// WndProc /// </summary> /// <param name="m"></param> protected override void WndProc(ref Message m) { // LogWriter logWriter = new LogWriter(@"C:\log\MyTextbox.txt"); // logWriter.Write(m.ToString()); IntPtr hIMC; switch (m.Msg) { case NativeMethods.WM_CHAR: hIMC = NativeMethods.ImmGetContext(this.Handle); if (NativeMethods.ImmGetOpenStatus(hIMC) == 0) { char chr = Convert.ToChar(m.WParam.ToInt32() & 0xff); Insert(chr.ToString()); // caret.SetPos(caret.GetPos().X + , caret.GetPos().Y); } NativeMethods.ImmReleaseContext(this.Handle, hIMC); Invalidate(); break; case NativeMethods.WM_IME_STARTCOMPOSITION: hIMC = NativeMethods.ImmGetContext(this.Handle); // ImmSetCompositionWindowとImmSetCandidateWindowがしっかり機能しているのかがイマイチ分からない。 // 変換ウィンドウの位置を設定 NativeMethods.COMPOSITIONFORM cf = new NativeMethods.COMPOSITIONFORM(); cf.dwStyle = NativeMethods.CFS_POINT; cf.ptCurrentPos = new Point(100, 0); cf.rcArea = new Rectangle(); NativeMethods.ImmSetCompositionWindow(hIMC, ref cf); // 候補文字ウィンドウの位置調整を行う NativeMethods.CANDIDATEFORM lpCandidate = new NativeMethods.CANDIDATEFORM(); lpCandidate.dwIndex = 0; lpCandidate.dwStyle = NativeMethods.CFS_CANDIDATEPOS; lpCandidate.ptCurrentPos = new Point(10, 50); NativeMethods.ImmSetCandidateWindow(hIMC, ref lpCandidate); NativeMethods.ImmReleaseContext(this.Handle, hIMC); break; case NativeMethods.WM_IME_COMPOSITION: this.ImeComposition(m); break; case NativeMethods.WM_IME_ENDCOMPOSITION: break; case NativeMethods.WM_IME_NOTIFY: switch (m.WParam.ToInt32()) { case NativeMethods.IMN_OPENCANDIDATE: // 候補文字ウィンドウが表示された this.SetCandidateWindowPos(m.HWnd); break; case NativeMethods.IMN_CLOSECANDIDATE: case NativeMethods.IMN_CHANGECANDIDATE: case NativeMethods.IMN_SETOPENSTATUS: // 何もしない。 break; } break; } base.WndProc(ref m); }