In addition to Weibo, there is also WeChat
Please pay attention
WeChat public account
Shulou
2025-01-29 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >
Share
Shulou(Shulou.com)06/01 Report--
This article will explain in detail how WPF implements the input method to follow the cursor in a custom text box. The editor thinks it is very practical, so I share it with you as a reference. I hope you can get something after reading this article.
The results of this paper are as follows
Realize
The method of this article refers to the logic of the WPF official warehouse. You can see how the official TextBox control gets the focus of the input method and modifies the input box coordinates of the input method when the input cursor changes in the WPF warehouse file wpf\ src\ Microsoft.DotNet.Wpf\ src\ PresentationFramework\ System\ Windows\ Documents\ ImmComposition.cs.
First of all, learn about the input method. In Windows programming development, there are three sets of input method framework, of which the second is the most used. The second set is docked with IMM. The so-called IMM is Input Method Manager, which is the input method manager.
Another related acronym IME is the abbreviation of Input Method Editor or Input Method Engine, meaning input method editor or input method engine
The application can dock the input method through IMM. The API focus of the win32 used is as follows
ImmGetContext gets the input method context for all subsequent function calls
ImmAssociateContext associates the input method and the corresponding window to let the input method know which window to enter
ImmSetCompositionWindow is used to set the coordinates of the window of the input method, which is also the most important function in this article.
Next, this article will show you how to encapsulate the IME input method call step by step, and all the source code will be given at the end of this article.
This part of the logic of the input method can be encapsulated into a class, so that the upper layer can not pay attention to the detailed logic. For example, the example code is put in the IMESupporter type
In order to facilitate the access of the text box, we define another interface. To set the text box, you need to implement some methods to provide parameters to the IMESupporter before you can access it.
/ indicates that the control supports the input method / interface IIMETextEditor {/ to get the currently used font name / string GetFontFamilyName () / get the font size in the same unit as the FontSize of WPF /; / get the point in the upper-left corner of the input box, which is used to set the upper-left corner of the input method. This point is relative to the coordinates of the element in which it is located. For most controls, it should be zero zero point / Point GetTextEditorLeftTop (); / get the point in the upper-left corner of the input of the cursor. This point is relative to the element coordinate / Point GetCaretLeftTop ();}
For input methods such as Microsoft Pinyin, it is supported to set the text size and font of the input method. Therefore, the text box is required to provide GetFontFamilyName and GetFontSize methods
And GetCaretLeftTop is naturally used to let the input method follow. In order for the text box to be more customized, the GetTextEditorLeftTop method is also needed, and the return value of this method should be 0 and 0 for most custom text box controls.
In the IMESupporter type constructor, expect to pass in a text box control, which can solve the initialization value and listening pot
Internal class IMESupporter where T: UIElement, IIMETextEditor {/ / ReSharper disable InconsistentNaming public IMESupporter (T editor) {Editor = editor; / / ignore Code}}
To constrain the incoming text box control to inherit both the UIElement and IIMETextEditor interfaces, generics are used
When the text box control Editor gets focus, you will need to call up the input method for input. When the Editor loses focus, you should tell the input method that it is not currently being typed.
Public IMESupporter (T editor) {Editor = editor; Editor.GotKeyboardFocus + = Editor_GotKeyboardFocus; Editor.LostKeyboardFocus + = Editor_LostKeyboardFocus;} private T Editor {get;}
According to the convention of WPF, for custom controls that support input methods, you need to set IsInputMethodSuspendedProperty additional properties, as shown in the following code
InputMethod.SetIsInputMethodSuspended (editor, true)
The logic you need to implement in Editor_GotKeyboardFocus is to adjust the input method and set the coordinates of the initial input box. As mentioned above, you need to get the input method context before you begin. You can get the default IME class window handle before getting the input method context. The default IME class window handle is obtained first so that when multiple processes embed the window, the input box of the Microsoft Pinyin input method follows the input cursor instead of in the upper left corner
_ defaultImeWnd = IMENative.ImmGetDefaultIMEWnd (IntPtr.Zero)
The _ defaultImeWnd above is a field, and the following fields and properties are defined in IMESupporter
Private T Editor {get;} private IntPtr _ defaultImeWnd;private IntPtr _ currentContext;private IntPtr _ previousContext;private HwndSource? _ hwndSource;private bool _ isUpdatingCompositionWindow
One detail here is that ImmGetDefaultIMEWnd may return a null value of 0x00. When will a null value be returned? If you open a Win32Dialog window, such as OpenFileDialog or SaveFileDialog, and then close, maybe ImmGetDefaultIMEWnd will return a null value at this time
To get the null value, you need to re-bind the input method and tell the input method's current window to get the input focus. You can use the following code to fix this problem by modifying the value of the additional attribute and changing the logic of the call to the WPF framework by the additional property.
If (_ defaultImeWnd = = IntPtr.Zero) {/ / if you get an empty default IME window, you may need to refresh the window that you put into another process as a nested window. Otherwise, Microsoft Pinyin input method will RefreshInputMethodEditors () on the upper left corner of the screen. / / ignore the code} / refresh the ITfThreadMgr status of IME, which is used to repair the shutdown after opening Win32Dialog. The input method cannot input Chinese / because after opening Win32Dialog, ITfThreadMgr will lose focus. Therefore, you need to use this method to refresh, call the EnableOrDisableInputMethod method of InputMethod through the IsInputMethodEnabledProperty property of InputMethod, where the TextServicesContext.DispatcherCurrent.SetFocusOnDefaultTextStore method is called, which calls the code of SetFocusOnDim (DefaultTextStore.Current.DocumentManager), sets DefaultTextStore.Current.DocumentManager to the focus of ITfThreadMgr, and rebinds the IME input method / but even so, you still can't get the initial value. You still need to reopen and close the WPF window to get / / [Can we public the `DefaultTextStore. Current.DocumentManager`property to create custom TextEditor with IME Issue # 6139 dotnet/wpf] (https://github.com/dotnet/wpf/issues/6139) private void RefreshInputMethodEditors () {if (InputMethod.GetIsInputMethodEnabled (Editor)) {InputMethod.SetIsInputMethodEnabled (Editor, false) } if (InputMethod.GetIsInputMethodSuspended (Editor)) {InputMethod.SetIsInputMethodSuspended (Editor, false);} InputMethod.SetIsInputMethodEnabled (Editor, true); InputMethod.SetIsInputMethodSuspended (Editor, true);}
In addition to passing the IntPtr.Zero to the ImmGetDefaultIMEWnd, you can also pass in the HwndSource where the current Editor is located, where the HwndSource is equivalent to or most of the time the window where the Editor is located.
_ hwndSource = (HwndSource) (PresentationSource.FromVisual (Editor)?? Throw new ArgumentNullException (nameof (Editor)); if (_ defaultImeWnd = = IntPtr.Zero) {/ / if you get an empty default IME window, it may be a window placed in another process as a nested window / / you need to refresh it if you can't get it. Otherwise, Microsoft Pinyin input method will RefreshInputMethodEditors () on the upper left corner of the screen; / / try to get _ defaultImeWnd = IMENative.ImmGetDefaultIMEWnd (_ hwndSource.Handle) through _ hwndSource, the window where the text is located; / / ignore the code}
If you continue not to get it, you can try to use GetForegroundWindow to get it. What you get with GetForegroundWindow may not be correct, but access to this branch is better than no input method at all.
_ defaultImeWnd = IMENative.ImmGetDefaultIMEWnd (_ hwndSource.Handle) If (_ defaultImeWnd = = IntPtr.Zero) {/ / if it is still not available, then use the currently active window, most of the current windows are right when you are ready for input / / enter here, and restore the input method as much as possible Although the GetForegroundWindow is expected to be wrong / / it is better than no input method _ defaultImeWnd = IMENative.ImmGetDefaultIMEWnd (Win32.User32.GetForegroundWindow ()). }
Next, get the input method context through _ defaultImeWnd, as shown in the following code
/ / using DefaultIMEWnd can better solve the problem of Microsoft Pinyin input method to the upper left corner of the screen _ currentContext = IMENative.ImmGetContext (_ defaultImeWnd)
If you can't get it from _ defaultImeWnd, use _ hwndSource.Handle to get it
_ currentContext = IMENative.ImmGetContext (_ defaultImeWnd); if (_ currentContext = = IntPtr.Zero) {_ currentContext = IMENative.ImmGetContext (_ hwndSource.Handle);}
After getting the context, associate the input method context with the current window. For an input method that only implements the second set of input method framework, the application program calls ImmAssociateContext association, and the input method can be adjusted to input in the associated window.
/ / A pair of Win32 input methods that use the second set of input method framework can use ImmAssociateContext association / / but for the input method that implements the third set of input method framework of TSF, the SetFocus method of ITfThreadMgr needs to be called when the third input method framework is docked in the application program. WPF just docked with _ previousContext = IMENative.ImmAssociateContext (_ hwndSource.Handle, _ currentContext)
During the input process, the input method will communicate with the current window through Windows messages, such as obtaining the coordinates and input text required by the input box. So I need to add a Hook message to tell the input method coordinates. However, there is no need to deal with the logic of the input text, because the logic of the input text and so on is already handled in WPF
_ previousContext = IMENative.ImmAssociateContext (_ hwndSource.Handle, _ currentContext); _ hwndSource.AddHook (WndProc)
About the functional logic of WndProc, let's put it in the back.
In the WPF framework, the third set of input methods is supported, so you need to call ITfThreadMgr, the COM component, to associate focus, as shown in the following code
/ / although the document says that passing null is invalid, it seems to help activate the IME input method in the default input context shared with WPF / / what you need to know here is, in the logic of WPF, is it necessary to pass in DefaultTextStore.Current.DocumentManager to meet the expected IMENative.ITfThreadMgr? ThreadMgr = IMENative.GetTextFrameworkThreadManager (); threadMgr?.SetFocus (IntPtr.Zero)
The initialization process also needs to give the input box of the input method an initialization coordinate, which can be set using Win32's ImmSetCompositionWindow. Before setting up, you need to get the coordinates of the input cursor of the text box relative to the window for use by the input method
The following code gets the text box from the text box to implement the acquisition cursor and the upper-left corner of the input box
Var textEditorLeftTop = Editor.GetTextEditorLeftTop (); var caretLeftTop = Editor.GetCaretLeftTop ()
Next, use the following code to convert the coordinates to the
Var hIMC = _ currentContext;HwndSource source = _ hwndSource;var textEditorLeftTop = Editor.GetTextEditorLeftTop (); var caretLeftTop = Editor.GetCaretLeftTop (); var transformToAncestor = Editor.TransformToAncestor (source.RootVisual); var textEditorLeftTopForRootVisual = transformToAncestor.Transform (textEditorLeftTop); var caretLeftTopForRootVisual = transformToAncestor.Transform (caretLeftTop)
For surface devices, more processing is needed
/ / to solve the incorrect cursor position of the input method on surface / / the phenomenon is that the cursor position on surface needs to be multiplied by 2 to be correct. There is no such problem on ordinary computers / / and this problem has nothing to do with DPI. At present, we can effectively judge caretLeftTopForRootVisual = new Point (caretLeftTopForRootVisual.X / SystemParameters.CaretWidth, caretLeftTopForRootVisual.Y / SystemParameters.CaretWidth) with CaretWidth.
The acquired coordinates are passed to the ImmSetCompositionWindow method
/ / const int CFS_DEFAULT = 0x0000; / / const int CFS_RECT = 0x0001; const int CFS_POINT = 0x0002; / / const int CFS_FORCE_POSITION = 0x0020; / / const int CFS_EXCLUDE = 0x0080; / / const int CFS_CANDIDATEPOS = 0x0040; var form = new IMENative.CompositionForm (); form.dwStyle = CFS_POINT Form.ptCurrentPos.x = (int) Math.Max (caretLeftTopForRootVisual.X, textEditorLeftTopForRootVisual.X); form.ptCurrentPos.y = (int) Math.Max (caretLeftTopForRootVisual.Y, textEditorLeftTopForRootVisual.Y); / / if (_ isSoftwarePinYinOverWin7) / / {/ / form.ptCurrentPos.y + = (int) characterBounds.Height; / /} IMENative.ImmSetCompositionWindow (hIMC, ref form)
The logic of the _ isSoftwarePinYinOverWin7 in the above note is to judge that on systems with a system version greater than Win7, such as Win10 systems, Microsoft Pinyin input method is used. In several versions, Microsoft Pinyin input method needs to be modified by Y coordinates, plus the line height of the input. But in some Win10 versions, the problem has been fixed through patches
The above completes the initialization logic of the input method.
Next, you need to deal with Windows messages. For example, when you receive a WM_INPUTLANGCHANGE message, you need to retrieve the input method context.
Private IntPtr WndProc (IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {switch (msg) {/ / ignore code case IMENative.WM_INPUTLANGCHANGE: if (_ hwndSource! = null) {CreateContext () } / / ignore the code break;} return IntPtr.Zero;}
The above CreateContext method to get the input method context is the logic to get _ currentContext
After receiving the WM_IME_COMPOSITION message, you need to update the coordinates of the input box of the input method.
Private IntPtr WndProc (IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {switch (msg) {/ / ignore code case IMENative.WM_IME_COMPOSITION: UpdateCompositionWindow (); break / / ignore code} return IntPtr.Zero;}
The above UpdateCompositionWindow method is the method that calls the ImmSetCompositionWindow method to set the coordinates
All code for this IMESupporter type can be obtained from the following
Next is to dock the IMESupporter and the specific text box.
First inherit the IIMETextEditor interface on the custom text box TextEditor control. In order to facilitate debugging, I first write the test logic, and the input cursor obtained is the point of the last mouse click and the fixed font size.
Public partial class TextEditor: FrameworkElement, IIMETextEditor {/ / ignore code protected override void OnRender (DrawingContext drawingContext) {drawingContext.DrawRectangle (Brushes.Black,null,new Rect (MouseDownPoint,new Size (3); base.OnRender (drawingContext) } protected override HitTestResult HitTestCore (PointHitTestParameters hitTestParameters) {/ / Let the control receive click return new PointHitTestResult (this, hitTestParameters.HitPoint);} protected override void OnMouseDown (MouseButtonEventArgs e) {MouseDownPoint = e.GetPosition (this); Focus (); InvalidateVisual () } private Point MouseDownPoint {get; set;} string IIMETextEditor.GetFontFamilyName () {return "Microsoft Yahei";} int IIMETextEditor.GetFontSize () {return 30 } Point IIMETextEditor.GetTextEditorLeftTop () {/ / relative to the coordinates of the current input box return new Point (0,0);} Point IIMETextEditor.GetCaretLeftTop () {return MouseDownPoint;}}
In the OnMouseDown method, you need to call Focus to get the focus and update the simulated cursor. The simulated cursor is in the OnRender method and is simulated by drawing a rectangle without flickering.
To enable the control to receive keyboard messages, you need to set the FocusableProperty property. In order to receive the Tab key instead of being cut to other controls, you need to set the IsTabStopProperty and TabNavigationProperty additional properties of KeyboardNavigation. Because this works on all custom text box TextEditor controls, you can change the default value in the static constructor of TextEditor as follows
Static TextEditor () {/ / is used to receive Tab keys instead of being switched focus KeyboardNavigation.IsTabStopProperty.OverrideMetadata (typeof (TextEditor), new FrameworkPropertyMetadata (true)); KeyboardNavigation.TabNavigationProperty.OverrideMetadata (typeof (TextEditor), new FrameworkPropertyMetadata (KeyboardNavigationMode.None)) / / used to get focus logic FocusableProperty.OverrideMetadata (typeof (TextEditor), new FrameworkPropertyMetadata (true));}
After completing the configuration of the TextEditor control, you can dock the IMESupporter class by creating it.
Public TextEditor () {/ / ignore code _ imeSupporter = new IMESupporter (this);} private readonly IMESupporter _ imeSupporter
In this way, the function of the text box to let the input method follow the input is completed.
Code
All the code for this article is on github and gitee. Welcome to visit
You can get the source code of this article by first creating an empty folder, then using the command line cd command to enter the empty folder and entering the following code on the command line to get the code for this article.
Git initgit remote add origin https://gitee.com/lindexi/lindexi_gd.gitgit pull origin b3a1fffece8284d0b84407aa13d949de6a2f1536
The above is the source of gitee. If gitee cannot access it, please replace it with the source of github.
Git remote remove origingit remote add origin https://github.com/lindexi/lindexi_gd.git
After getting the code, open the LightTextEditorPlus.sln file
On "WPF how to achieve input method to follow the cursor in the custom text box" this article is shared here, I hope the above content can be of some help to you, so that you can learn more knowledge, if you think the article is good, please share it out for more people to see.
Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.
Views: 0
*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.
Continue with the installation of the previous hadoop.First, install zookooper1. Decompress zookoope
"Every 5-10 years, there's a rare product, a really special, very unusual product that's the most un
© 2024 shulou.com SLNews company. All rights reserved.