diff --git a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs index 1c61334888..c7fca04ea9 100644 --- a/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs +++ b/src/Avalonia.Base/Input/TextInput/InputMethodManager.cs @@ -22,11 +22,18 @@ namespace Avalonia.Input.TextInput set { if(_client == value) + { return; + } + if (_client != null) { _client.CursorRectangleChanged -= OnCursorRectangleChanged; _client.TextViewVisualChanged -= OnTextViewVisualChanged; + + _client = null; + + _im?.Reset(); } _client = value; @@ -35,8 +42,6 @@ namespace Avalonia.Input.TextInput { _client.CursorRectangleChanged += OnCursorRectangleChanged; _client.TextViewVisualChanged += OnTextViewVisualChanged; - - _im?.Reset(); if (_focusedElement is StyledElement target) { @@ -50,6 +55,7 @@ namespace Avalonia.Input.TextInput _transformTracker.SetVisual(_client?.TextViewVisual); _im?.SetClient(_client); + UpdateCursorRect(); } else diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs index aabf361844..05074cc82e 100644 --- a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs +++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs @@ -1,6 +1,8 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Text; +using Avalonia.Input; +using Avalonia.Input.Raw; using Avalonia.Input.TextInput; using Avalonia.Threading; @@ -22,6 +24,8 @@ namespace Avalonia.Win32.Input private ushort _langId; private const int CaretMargin = 1; + private bool _ignoreComposition; + public ITextInputMethodClient? Client { get; private set; } [MemberNotNullWhen(true, nameof(Client))] @@ -121,19 +125,35 @@ namespace Avalonia.Win32.Input { var himc = ImmGetContext(Hwnd); - if (IsComposing) + if (himc != IntPtr.Zero) { + _ignoreComposition = true; + + if (_parent != null) + { + _parent._ignoreWmChar = true; + } + ImmNotifyIME(himc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0); - + + ImmReleaseContext(Hwnd, himc); + IsComposing = false; - } - ImmReleaseContext(Hwnd, himc); + Composition = null; + } }); } public void SetClient(ITextInputMethodClient? client) { + if(Client != null) + { + Composition = null; + + Client.SetPreeditText(null); + } + Client = client; Dispatcher.UIThread.Post(() => @@ -297,6 +317,82 @@ namespace Avalonia.Win32.Input return ImmGetCompositionString(himc, flag); } + public void HandleCompositionStart() + { + Composition = null; + + if (IsActive) + { + Client.SetPreeditText(null); + } + + IsComposing = true; + } + + public void HandleCompositionEnd() + { + //Cleanup composition state. + IsComposing = false; + + Composition = null; + + if (IsActive) + { + Client.SetPreeditText(null); + } + } + + public void HandleComposition(IntPtr wParam, IntPtr lParam, uint timestamp) + { + if (_ignoreComposition) + { + _ignoreComposition = false; + + return; + } + + var flags = (GCS)ToInt32(lParam); + + if ((flags & GCS.GCS_RESULTSTR) != 0) + { + var resultString = GetCompositionString(GCS.GCS_RESULTSTR); + + if (_parent != null && !string.IsNullOrEmpty(resultString)) + { + Composition = null; + + if (IsActive) + { + Client.SetPreeditText(null); + } + + var e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _parent.Owner, resultString); + + if (_parent.Input != null) + { + _parent.Input(e); + + _parent._ignoreWmChar = true; + } + } + } + + if ((flags & GCS.GCS_COMPSTR) != 0) + { + var compositionString = GetCompositionString(GCS.GCS_COMPSTR); + + CompositionChanged(compositionString); + } + } + + private static int ToInt32(IntPtr ptr) + { + if (IntPtr.Size == 4) + return ptr.ToInt32(); + + return (int)(ptr.ToInt64() & 0xffffffff); + } + ~Imm32InputMethod() { _caretManager.TryDestroy(); diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index b256a9433d..9a7f4b62e3 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs @@ -87,7 +87,7 @@ namespace Avalonia.Win32 { // The first and foremost thing to do - notify the TopLevel Closed?.Invoke(); - + if (UiaCoreTypesApi.IsNetComInteropAvailable) { UiaCoreProviderApi.UiaReturnRawElementProvider(_hwnd, IntPtr.Zero, IntPtr.Zero, null); @@ -98,7 +98,7 @@ namespace Avalonia.Win32 { Imm32InputMethod.Current.ClearLanguageAndWindow(); } - + // Cleanup render targets (_gl as IDisposable)?.Dispose(); @@ -724,26 +724,7 @@ namespace Avalonia.Win32 } case WindowsMessage.WM_IME_COMPOSITION: { - var flags = (GCS)ToInt32(lParam); - - if ((flags & GCS.GCS_COMPSTR) != 0) - { - var currentComposition = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_COMPSTR); - - Imm32InputMethod.Current.CompositionChanged(currentComposition); - } - - if ((flags & GCS.GCS_RESULTSTR) != 0) - { - var result = Imm32InputMethod.Current.GetCompositionString(GCS.GCS_RESULTSTR); - - if (!string.IsNullOrEmpty(result)) - { - Imm32InputMethod.Current.Composition = result; - - _ignoreWmChar = true; - } - } + Imm32InputMethod.Current.HandleComposition(wParam, lParam, timestamp); break; } @@ -757,35 +738,16 @@ namespace Avalonia.Win32 case WindowsMessage.WM_IME_NOTIFY: break; case WindowsMessage.WM_IME_STARTCOMPOSITION: - Imm32InputMethod.Current.Composition = null; - - if (Imm32InputMethod.Current.IsActive) { - Imm32InputMethod.Current.Client.SetPreeditText(null); - } + Imm32InputMethod.Current.HandleCompositionStart(); - Imm32InputMethod.Current.IsComposing = true; - return IntPtr.Zero; + return IntPtr.Zero; + } case WindowsMessage.WM_IME_ENDCOMPOSITION: { - var currentComposition = Imm32InputMethod.Current.Composition; - - //In case composition has not been comitted yet we need to do that here. - if (!string.IsNullOrEmpty(currentComposition)) - { - e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, Owner, currentComposition); - } - - //Cleanup composition state. - Imm32InputMethod.Current.IsComposing = false; - Imm32InputMethod.Current.Composition = null; + Imm32InputMethod.Current.HandleCompositionEnd(); - if (Imm32InputMethod.Current.IsActive) - { - Imm32InputMethod.Current.Client.SetPreeditText(null); - } - - break; + return IntPtr.Zero; } case WindowsMessage.WM_GETOBJECT: if ((long)lParam == uiaRootObjectId && UiaCoreTypesApi.IsNetComInteropAvailable && _owner is Control control) @@ -830,7 +792,7 @@ namespace Avalonia.Win32 return IntPtr.Zero; } } - + return DefWindowProc(hWnd, msg, wParam, lParam); } diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 4260f90e9f..057cdb2db0 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/src/Windows/Avalonia.Win32/WindowImpl.cs @@ -97,7 +97,7 @@ namespace Avalonia.Win32 private bool _shown; private bool _hiddenWindowIsParent; private uint _langid; - private bool _ignoreWmChar; + internal bool _ignoreWmChar; private WindowTransparencyLevel _transparencyLevel; private const int MaxPointerHistorySize = 512; @@ -186,7 +186,7 @@ namespace Avalonia.Win32 s_instances.Add(this); } - private IInputRoot Owner + internal IInputRoot Owner => _owner ?? throw new InvalidOperationException($"{nameof(SetInputRoot)} must have been called"); public Action? Activated { get; set; }