Browse Source

Windows IME (#7007)

* win32 ime wip

* ime window starts tracking the cursor, but coords are wrong

* fix win32 ime cursor coord

* win32-ime lang-specific behaviors

* track language id in WindowImpl

* lowercase dllimport

* create initial ime on window creation

* InputMethodManager: connect to client even if im is absent at the moment

* proposal: IKeyboardDevice.NotifyInputMethodUpdated

* finalizing

* ime: allow client to request active state change

* remove backward incompatible ActiveState.

* InputMethodManager: NotifyInputMethodUpdated: filter the window of current focused element

* [IME] [Windows] ability to enable/disable IME for any InputElement

* [IME] [Windows] Refactor Imm32InputMethod - create a single one for dispatcher. Also change a method of enabling/disabling IME to work like in WPF.

* [IME] [Windows] Fix IME after dialog show not working - active window context is not applied.

* [IME] [Windows] fix intermediate input position

* [IME] [Windows] PreEdit font size is applied

* [IME] [Windows] Make MoveImeWindow code to be exact like in chrome - fix a lot of possible issues. Added comments. Minor Refactoring

* [IME] [Windows] Refactor caret management, improve deactivation, remove comments

* [IME] [Windows] Remove redundant api changes (request from @kekekeks)

* Fix .sln and ApiCompatBesaline.txt redundant changes.

* [Windows] [IME] move IsInputMethodEnabled subscription to InputMethodManager, Move check for IsInputMethodEnabled before TextInputMethodClientRequestedEvent query

* [IME] [Windows] remove redundant SetActive(false) call, because it's called in Client setter

* remove redundant change

Co-authored-by: Yatao Li <yatli@microsoft.com>
Co-authored-by: Max Katz <maxkatz6@outlook.com>
pull/7146/head
Sergey Mikolaytis 4 years ago
committed by GitHub
parent
commit
28a2a2fc8e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  2. 2
      src/Avalonia.Controls/TextBox.cs
  3. 26
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  4. 32
      src/Avalonia.Input/InputMethod.cs
  5. 35
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  6. 38
      src/Windows/Avalonia.Win32/Input/Imm32CaretManager.cs
  7. 231
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  8. 106
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  9. 45
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  10. 9
      src/Windows/Avalonia.Win32/WindowImpl.cs

15
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -18,7 +18,7 @@
<Flyout>
<TextBlock>Custom context flyout</TextBlock>
</Flyout>
</TextBox.ContextFlyout>
</TextBox.ContextFlyout>
</TextBox>
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Numeric Watermark" x:Name="numericWatermark"/>
@ -47,6 +47,19 @@
SelectionStart="5" SelectionEnd="22"
SelectionBrush="Green" SelectionForegroundBrush="Yellow"/>
<TextBox Width="200" Text="Custom caret brush" CaretBrush="DarkOrange"/>
<TextBox Text="IME small font" Width="200"
FontFamily="Comic Sans MS"
FontSize="10"
Foreground="Red"/>
<TextBox Text="IME large font" Width="200"
FontFamily="Comic Sans MS"
FontSize="22"
Foreground="Red"/>
<TextBox Text="IME disabled" Width="200"
FontFamily="Comic Sans MS"
InputMethod.IsInputMethodEnabled="False"
Foreground="Red"/>
</StackPanel>
<StackPanel Orientation="Vertical" Spacing="8">

2
src/Avalonia.Controls/TextBox.cs

@ -533,7 +533,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_imClient.SetPresenter(_presenter);
_imClient.SetPresenter(_presenter, this);
if (IsFocused)
{
_presenter?.ShowCaret();

26
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;

32
src/Avalonia.Input/InputMethod.cs

@ -0,0 +1,32 @@
namespace Avalonia.Input
{
public class InputMethod
{
/// <summary>
/// A dependency property that enables alternative text inputs.
/// </summary>
public static readonly AvaloniaProperty<bool> IsInputMethodEnabledProperty =
AvaloniaProperty.RegisterAttached<InputMethod, InputElement, bool>("IsInputMethodEnabled", true);
/// <summary>
/// Setter for IsInputMethodEnabled AvaloniaProperty
/// </summary>
public static void SetIsInputMethodEnabled(InputElement target, bool value)
{
target.SetValue(IsInputMethodEnabledProperty, value);
}
/// <summary>
/// Getter for IsInputMethodEnabled AvaloniaProperty
/// </summary>
public static bool GetIsInputMethodEnabled(InputElement target)
{
return target.GetValue<bool>(IsInputMethodEnabledProperty);
}
private InputMethod()
{
}
}
}

35
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<bool> 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;
}

38
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;
}
}
}
}

231
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
{
/// <summary>
/// A Windows input method editor based on Windows Input Method Manager (IMM32).
/// </summary>
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();
}
}
}

106
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
{

45
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)

9
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.
/// </summary>
public partial class WindowImpl : IWindowImpl, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo,
ITopLevelImplWithNativeControlHost
ITopLevelImplWithNativeControlHost,
ITopLevelImplWithTextInputMethod
{
private static readonly List<WindowImpl> s_instances = new List<WindowImpl>();
@ -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;
}
}

Loading…
Cancel
Save