diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml
index 233b309caf..0a5ccdcfff 100644
--- a/samples/ControlCatalog/Pages/TextBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml
@@ -18,7 +18,7 @@
Custom context flyout
-
+
@@ -47,6 +47,19 @@
SelectionStart="5" SelectionEnd="22"
SelectionBrush="Green" SelectionForegroundBrush="Yellow"/>
+
+
+
+
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 00fc6002d1..32428bea53 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -533,7 +533,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get("PART_TextPresenter");
- _imClient.SetPresenter(_presenter);
+ _imClient.SetPresenter(_presenter, this);
if (IsFocused)
{
_presenter?.ShowCaret();
diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
index c5a729afae..334db2cafd 100644
--- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
+++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
@@ -1,5 +1,6 @@
using System;
using Avalonia.Controls.Presenters;
+using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.VisualTree;
@@ -7,9 +8,26 @@ namespace Avalonia.Controls
{
internal class TextBoxTextInputMethodClient : ITextInputMethodClient
{
+ private InputElement _parent;
private TextPresenter _presenter;
private IDisposable _subscription;
- public Rect CursorRectangle => _presenter?.GetCursorRectangle() ?? default;
+ public Rect CursorRectangle
+ {
+ get
+ {
+ if (_parent == null || _presenter == null)
+ {
+ return default;
+ }
+ var transform = _presenter.TransformToVisual(_parent);
+ if (transform == null)
+ {
+ return default;
+ }
+ return _presenter.GetCursorRectangle().TransformToAABB(transform.Value);
+ }
+ }
+
public event EventHandler CursorRectangleChanged;
public IVisual TextViewVisual => _presenter;
public event EventHandler TextViewVisualChanged;
@@ -23,9 +41,11 @@ namespace Avalonia.Controls
public string TextAfterCursor => null;
private void OnCaretIndexChanged(int index) => CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
-
- public void SetPresenter(TextPresenter presenter)
+
+
+ public void SetPresenter(TextPresenter presenter, InputElement parent)
{
+ _parent = parent;
_subscription?.Dispose();
_subscription = null;
_presenter = presenter;
diff --git a/src/Avalonia.Input/InputMethod.cs b/src/Avalonia.Input/InputMethod.cs
new file mode 100644
index 0000000000..8098b18c47
--- /dev/null
+++ b/src/Avalonia.Input/InputMethod.cs
@@ -0,0 +1,32 @@
+namespace Avalonia.Input
+{
+ public class InputMethod
+ {
+ ///
+ /// A dependency property that enables alternative text inputs.
+ ///
+ public static readonly AvaloniaProperty IsInputMethodEnabledProperty =
+ AvaloniaProperty.RegisterAttached("IsInputMethodEnabled", true);
+
+ ///
+ /// Setter for IsInputMethodEnabled AvaloniaProperty
+ ///
+ public static void SetIsInputMethodEnabled(InputElement target, bool value)
+ {
+ target.SetValue(IsInputMethodEnabledProperty, value);
+ }
+
+ ///
+ /// Getter for IsInputMethodEnabled AvaloniaProperty
+ ///
+ public static bool GetIsInputMethodEnabled(InputElement target)
+ {
+ return target.GetValue(IsInputMethodEnabledProperty);
+ }
+
+ private InputMethod()
+ {
+
+ }
+ }
+}
diff --git a/src/Avalonia.Input/TextInput/InputMethodManager.cs b/src/Avalonia.Input/TextInput/InputMethodManager.cs
index 207ba6096e..dafd397348 100644
--- a/src/Avalonia.Input/TextInput/InputMethodManager.cs
+++ b/src/Avalonia.Input/TextInput/InputMethodManager.cs
@@ -8,9 +8,14 @@ namespace Avalonia.Input.TextInput
private ITextInputMethodImpl? _im;
private IInputElement? _focusedElement;
private ITextInputMethodClient? _client;
+ private IDisposable? _subscribeDisposable;
private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper();
- public TextInputMethodManager() => _transformTracker.MatrixChanged += UpdateCursorRect;
+ public TextInputMethodManager()
+ {
+ _transformTracker.MatrixChanged += UpdateCursorRect;
+ InputMethod.IsInputMethodEnabledProperty.Changed.Subscribe(OnIsInputMethodEnabledChanged);
+ }
private ITextInputMethodClient? Client
{
@@ -40,6 +45,7 @@ namespace Avalonia.Input.TextInput
_im?.SetOptions(optionsQuery);
_transformTracker?.SetVisual(_client?.TextViewVisual);
UpdateCursorRect();
+
_im?.SetActive(true);
}
else
@@ -50,6 +56,14 @@ namespace Avalonia.Input.TextInput
}
}
+ private void OnIsInputMethodEnabledChanged(AvaloniaPropertyChangedEventArgs obj)
+ {
+ if (ReferenceEquals(obj.Sender, _focusedElement))
+ {
+ TryFindAndApplyClient();
+ }
+ }
+
private void OnTextViewVisualChanged(object sender, EventArgs e)
=> _transformTracker.SetVisual(_client?.TextViewVisual);
@@ -57,6 +71,7 @@ namespace Avalonia.Input.TextInput
{
if (_im == null || _client == null || _focusedElement?.VisualRoot == null)
return;
+
var transform = _focusedElement.TransformToVisual(_focusedElement.VisualRoot);
if (transform == null)
_im.SetCursorRect(default);
@@ -75,17 +90,23 @@ namespace Avalonia.Input.TextInput
if(_focusedElement == element)
return;
_focusedElement = element;
-
+
var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
- if(_im != inputMethod)
+ if (_im != inputMethod)
_im?.SetActive(false);
_im = inputMethod;
-
- if (_focusedElement == null || _im == null)
+
+ TryFindAndApplyClient();
+ }
+
+ private void TryFindAndApplyClient()
+ {
+ if (_focusedElement is not InputElement focused ||
+ _im == null ||
+ !InputMethod.GetIsInputMethodEnabled(focused))
{
Client = null;
- _im?.SetActive(false);
return;
}
@@ -93,7 +114,7 @@ namespace Avalonia.Input.TextInput
{
RoutedEvent = InputElement.TextInputMethodClientRequestedEvent
};
-
+
_focusedElement.RaiseEvent(clientQuery);
Client = clientQuery.Client;
}
diff --git a/src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs b/src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs
new file mode 100644
index 0000000000..38605efa22
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs
@@ -0,0 +1,38 @@
+using System;
+using static Avalonia.Win32.Interop.UnmanagedMethods;
+
+namespace Avalonia.Win32.Input
+{
+ internal struct Imm32CaretManager
+ {
+ private bool _isCaretCreated;
+
+ public void TryCreate(int _langId, IntPtr hwnd)
+ {
+ if (!_isCaretCreated)
+ {
+ if (_langId == LANG_ZH || _langId == LANG_JA)
+ {
+ _isCaretCreated = CreateCaret(hwnd, IntPtr.Zero, 2, 10);
+ }
+ }
+ }
+
+ public void TryMove(int x, int y)
+ {
+ if (_isCaretCreated)
+ {
+ SetCaretPos(x, y);
+ }
+ }
+
+ public void TryDestroy()
+ {
+ if (_isCaretCreated)
+ {
+ DestroyCaret();
+ _isCaretCreated = false;
+ }
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
new file mode 100644
index 0000000000..71e33554f1
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
@@ -0,0 +1,231 @@
+using System;
+using Avalonia.Input.TextInput;
+using Avalonia.Threading;
+
+using static Avalonia.Win32.Interop.UnmanagedMethods;
+
+namespace Avalonia.Win32.Input
+{
+ ///
+ /// A Windows input method editor based on Windows Input Method Manager (IMM32).
+ ///
+ class Imm32InputMethod : ITextInputMethodImpl
+ {
+ public IntPtr HWND { get; private set; }
+ private IntPtr _defaultImc;
+ private WindowImpl _parent;
+ private bool _active;
+ private bool _showCompositionWindow;
+ private Imm32CaretManager _caretManager = new();
+ private bool _showCandidateList;
+ private ushort _langId;
+ private const int _caretMargin = 1;
+
+ public void SetLanguageAndWindow(WindowImpl parent, IntPtr hwnd, IntPtr HKL)
+ {
+ if (HWND != hwnd)
+ {
+ _defaultImc = IntPtr.Zero;
+ }
+ HWND = hwnd;
+ _parent = parent;
+ _active = false;
+ _langId = PRIMARYLANGID(LGID(HKL));
+ _showCompositionWindow = true;
+ _showCandidateList = true;
+
+ IsComposing = false;
+ }
+
+ //Dependant on CurrentThread. When Avalonia will support Multiple Dispatchers -
+ //every Dispatcher should have their own InputMethod.
+ public static Imm32InputMethod Current { get; } = new Imm32InputMethod();
+
+ private IntPtr DefaultImc
+ {
+ get
+ {
+ if (_defaultImc == IntPtr.Zero &&
+ HWND != IntPtr.Zero)
+ {
+ _defaultImc = ImmGetContext(HWND);
+ ImmReleaseContext(HWND, _defaultImc);
+ }
+
+ if (_defaultImc == IntPtr.Zero)
+ {
+ _defaultImc = ImmCreateContext();
+ }
+
+ return _defaultImc;
+ }
+ }
+
+ public void Reset()
+ {
+ if (IsComposing)
+ {
+ Dispatcher.UIThread.Post(() =>
+ {
+ ImmNotifyIME(DefaultImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
+ ImmReleaseContext(HWND, DefaultImc);
+ IsComposing = false;
+ });
+ }
+ }
+
+ public void SetActive(bool active)
+ {
+ _active = active;
+ Dispatcher.UIThread.Post(() =>
+ {
+ if (active)
+ {
+ if (DefaultImc != IntPtr.Zero)
+ {
+ _caretManager.TryCreate(_langId, HWND);
+ // Load the default IME context.
+ // NOTE(hbono)
+ // IMM ignores this call if the IME context is loaded. Therefore, we do
+ // not have to check whether or not the IME context is loaded.
+ ImmAssociateContext(HWND, _defaultImc);
+ }
+ }
+ else
+ {
+ // A renderer process have moved its input focus to a password input
+ // when there is an ongoing composition, e.g. a user has clicked a
+ // mouse button and selected a password input while composing a text.
+ // For this case, we have to complete the ongoing composition and
+ // clean up the resources attached to this object BEFORE DISABLING THE IME.
+ if (IsComposing)
+ {
+ ImmNotifyIME(DefaultImc, NI_COMPOSITIONSTR, CPS_COMPLETE, 0);
+ ImmReleaseContext(HWND, DefaultImc);
+ IsComposing = false;
+ }
+ ImmAssociateContext(HWND, IntPtr.Zero);
+ _caretManager.TryDestroy();
+ }
+ });
+ }
+
+ public void SetCursorRect(Rect rect)
+ {
+ var focused = GetActiveWindow() == HWND;
+ if (!focused)
+ {
+ return;
+ }
+ Dispatcher.UIThread.Post(() =>
+ {
+ IntPtr himc = DefaultImc;
+ if (himc == IntPtr.Zero)
+ {
+ return;
+ }
+
+ MoveImeWindow(rect, himc);
+ ImmReleaseContext(HWND, himc);
+ });
+ }
+
+ // see: https://chromium.googlesource.com/experimental/chromium/src/+/bf09a5036ccfb77d2277247c66dc55daf41df3fe/chrome/browser/ime_input.cc
+ // see: https://engine.chinmaygarde.com/window__win32_8cc_source.html
+ private void MoveImeWindow(Rect rect, IntPtr himc)
+ {
+ var p1 = rect.TopLeft;
+ var p2 = rect.BottomRight;
+ var s = _parent?.DesktopScaling ?? 1;
+ var (x1, y1, x2, y2) = ((int) (p1.X * s), (int) (p1.Y * s), (int) (p2.X * s), (int) (p2.Y * s));
+
+ if (!_showCompositionWindow &&
+ _langId == LANG_ZH)
+ {
+ // 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 CANDIDATEFORM
+ {
+ dwIndex = 0,
+ dwStyle = CFS_CANDIDATEPOS,
+ ptCurrentPos = new POINT {X = x2, Y = y2}
+ };
+ ImmSetCandidateWindow(himc, ref candidateForm);
+ }
+
+ _caretManager.TryMove(x2, y2);
+
+ if (_showCompositionWindow)
+ {
+ ConfigureCompositionWindow(x1, y1, himc, y2 - y1);
+ // Don't need to set the position of candidate window.
+ return;
+ }
+
+ if (_langId == LANG_KO)
+ {
+ // 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.
+ y2 += _caretMargin;
+ }
+
+ // Need to return here since some Chinese IMEs would stuck if set
+ // candidate window position with CFS_EXCLUDE style.
+ if (_langId == LANG_ZH)
+ {
+ 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 CANDIDATEFORM
+ {
+ dwIndex = 0,
+ dwStyle = CFS_EXCLUDE,
+ ptCurrentPos = new POINT {X = x1, Y = y1},
+ rcArea = new RECT {left = x1, top = y1, right = x2, bottom = y2 + _caretMargin}
+ };
+ ImmSetCandidateWindow(himc, ref excludeRectangle);
+ }
+
+ private static void ConfigureCompositionWindow(int x1, int y1, IntPtr himc, int height)
+ {
+ var compForm = new COMPOSITIONFORM
+ {
+ dwStyle = CFS_POINT,
+ ptCurrentPos = new POINT {X = x1, Y = y1},
+ };
+ ImmSetCompositionWindow(himc, ref compForm);
+
+ var logFont = new LOGFONT()
+ {
+ lfHeight = height,
+ lfQuality = 5 //CLEARTYPE_QUALITY
+ };
+ ImmSetCompositionFont(himc, ref logFont);
+ }
+
+ public void SetOptions(TextInputOptionsQueryEventArgs options)
+ {
+ // we're skipping this. not usable on windows
+ }
+
+ public bool IsComposing { get; set; }
+
+ ~Imm32InputMethod()
+ {
+ _caretManager.TryDestroy();
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 3b2b99fb0c..c74c5fbc01 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -1551,6 +1551,112 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
internal static extern int SetWindowCompositionAttribute(IntPtr hwnd, ref WindowCompositionAttributeData data);
+ [DllImport("imm32.dll", SetLastError = true)]
+ public static extern IntPtr ImmGetContext(IntPtr hWnd);
+ [DllImport("imm32.dll", SetLastError = true)]
+ public static extern IntPtr ImmAssociateContext(IntPtr hWnd, IntPtr hIMC);
+ [DllImport("imm32.dll", SetLastError = true)]
+ public static extern IntPtr ImmCreateContext();
+ [DllImport("imm32.dll")]
+ public static extern bool ImmReleaseContext(IntPtr hWnd, IntPtr hIMC);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmSetOpenStatus(IntPtr hIMC, bool flag);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmSetActiveContext(IntPtr hIMC, bool flag);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmSetStatusWindowPos(IntPtr hIMC, ref POINT lpptPos);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmIsIME(IntPtr HKL);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmSetCandidateWindow(IntPtr hIMC, ref CANDIDATEFORM lpCandidate);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmSetCompositionWindow(IntPtr hIMC, ref COMPOSITIONFORM lpComp);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmSetCompositionFont(IntPtr hIMC, ref LOGFONT lf);
+ [DllImport("imm32.dll")]
+ public static extern bool ImmNotifyIME(IntPtr hIMC, int dwAction, int dwIndex, int dwValue);
+ [DllImport("user32.dll")]
+ public static extern bool CreateCaret(IntPtr hwnd, IntPtr hBitmap, int nWidth, int nHeight);
+ [DllImport("user32.dll")]
+ public static extern bool SetCaretPos(int X, int Y);
+ [DllImport("user32.dll")]
+ public static extern bool DestroyCaret();
+ [DllImport("user32.dll")]
+ public static extern IntPtr GetKeyboardLayout(int idThread);
+ [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
+ public static extern int LCIDToLocaleName(uint Locale, StringBuilder lpName, int cchName, int dwFlags);
+
+ public static uint MAKELCID(uint lgid, uint srtid)
+ {
+ return (((uint)(ushort)srtid) << 16) |
+ ((ushort)lgid);
+ }
+
+ public static ushort PRIMARYLANGID(uint lgid)
+ {
+ return (ushort)(lgid & 0x3ff);
+ }
+
+ public static uint LGID(IntPtr HKL)
+ {
+ return (uint)(HKL.ToInt32() & 0xffff);
+ }
+
+ public const int SORT_DEFAULT = 0;
+ public const int LANG_ZH = 0x0004;
+ public const int LANG_JA = 0x0011;
+ public const int LANG_KO = 0x0012;
+
+ public const int CFS_FORCE_POSITION = 0x0020;
+ public const int CFS_CANDIDATEPOS = 0x0040;
+ public const int CFS_EXCLUDE = 0x0080;
+ public const int CFS_POINT = 0x0002;
+ public const int CFS_RECT = 0x0001;
+ public const uint ISC_SHOWUICOMPOSITIONWINDOW = 0x80000000;
+
+ public const int NI_COMPOSITIONSTR = 21;
+ public const int CPS_COMPLETE = 1;
+ public const int CPS_CONVERT = 2;
+ public const int CPS_REVERT = 3;
+ public const int CPS_CANCEL = 4;
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct CANDIDATEFORM
+ {
+ public int dwIndex;
+ public int dwStyle;
+ public POINT ptCurrentPos;
+ public RECT rcArea;
+ }
+
+ [StructLayout(LayoutKind.Sequential)]
+ internal struct COMPOSITIONFORM
+ {
+ public int dwStyle;
+ public POINT ptCurrentPos;
+ public RECT rcArea;
+ }
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
+ public struct LOGFONT
+ {
+ public int lfHeight;
+ public int lfWidth;
+ public int lfEscapement;
+ public int lfOrientation;
+ public int lfWeight;
+ public byte lfItalic;
+ public byte lfUnderline;
+ public byte lfStrikeOut;
+ public byte lfCharSet;
+ public byte lfOutPrecision;
+ public byte lfClipPrecision;
+ public byte lfQuality;
+ public byte lfPitchAndFamily;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
+ public string lfFaceName;
+ }
+
[StructLayout(LayoutKind.Sequential)]
internal struct WindowCompositionAttributeData
{
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index eaf6b47f42..89d5009da5 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -1,6 +1,7 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
+using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Remote;
using Avalonia.Input;
@@ -34,6 +35,7 @@ namespace Avalonia.Win32
case WindowActivate.WA_CLICKACTIVE:
{
Activated?.Invoke();
+ UpdateInputMethod(GetKeyboardLayout(0));
break;
}
@@ -472,6 +474,35 @@ namespace Avalonia.Win32
case WindowsMessage.WM_KILLFOCUS:
LostFocus?.Invoke();
break;
+
+ case WindowsMessage.WM_INPUTLANGCHANGE:
+ {
+ UpdateInputMethod(lParam);
+ // call DefWindowProc to pass to all children
+ break;
+ }
+ case WindowsMessage.WM_IME_SETCONTEXT:
+ {
+ // TODO if we implement preedit, disable the composition window:
+ // lParam = new IntPtr((int)(((uint)lParam.ToInt64()) & ~ISC_SHOWUICOMPOSITIONWINDOW));
+ UpdateInputMethod(GetKeyboardLayout(0));
+ break;
+ }
+ case WindowsMessage.WM_IME_CHAR:
+ case WindowsMessage.WM_IME_COMPOSITION:
+ case WindowsMessage.WM_IME_COMPOSITIONFULL:
+ case WindowsMessage.WM_IME_CONTROL:
+ case WindowsMessage.WM_IME_KEYDOWN:
+ case WindowsMessage.WM_IME_KEYUP:
+ case WindowsMessage.WM_IME_NOTIFY:
+ case WindowsMessage.WM_IME_SELECT:
+ break;
+ case WindowsMessage.WM_IME_STARTCOMPOSITION:
+ Imm32InputMethod.Current.IsComposing = true;
+ break;
+ case WindowsMessage.WM_IME_ENDCOMPOSITION:
+ Imm32InputMethod.Current.IsComposing = false;
+ break;
}
#if USE_MANAGED_DRAG
@@ -500,6 +531,20 @@ namespace Avalonia.Win32
}
}
+ private void UpdateInputMethod(IntPtr hkl)
+ {
+ // note: for non-ime language, also create it so that emoji panel tracks cursor
+ var langid = LGID(hkl);
+ if (langid == _langid && Imm32InputMethod.Current.HWND == Hwnd)
+ {
+ return;
+ }
+ _langid = langid;
+
+ Imm32InputMethod.Current.SetLanguageAndWindow(this, Hwnd, hkl);
+
+ }
+
private static int ToInt32(IntPtr ptr)
{
if (IntPtr.Size == 4)
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 0a031cd5bf..4c3165eaf9 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -6,6 +6,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
+using Avalonia.Input.TextInput;
using Avalonia.OpenGL;
using Avalonia.OpenGL.Angle;
using Avalonia.OpenGL.Egl;
@@ -25,7 +26,8 @@ namespace Avalonia.Win32
/// Window implementation for Win32 platform.
///
public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
- ITopLevelImplWithNativeControlHost
+ ITopLevelImplWithNativeControlHost,
+ ITopLevelImplWithTextInputMethod
{
private static readonly List s_instances = new List();
@@ -87,6 +89,7 @@ namespace Avalonia.Win32
private bool _isCloseRequested;
private bool _shown;
private bool _hiddenWindowIsParent;
+ private uint _langid;
public WindowImpl()
{
@@ -122,7 +125,7 @@ namespace Avalonia.Win32
CreateWindow();
_framebuffer = new FramebufferManager(_hwnd);
-
+ UpdateInputMethod(GetKeyboardLayout(0));
if (glPlatform != null)
{
if (_isUsingComposition)
@@ -1353,5 +1356,7 @@ namespace Avalonia.Win32
public void Dispose() => _owner._resizeReason = _restore;
}
+
+ public ITextInputMethodImpl TextInputMethod => Imm32InputMethod.Current;
}
}