Avalonia icon indicating copy to clipboard operation
Avalonia copied to clipboard

How to inject a VirtualKeyboard

Open djonasdev opened this issue 2 years ago • 8 comments

I have made simple VirtualKeyboard.

In my case I would like to as soon as the TextBox receives the InputFocus, transfer the Text of the TextBox to the VirtualKeyboard and open it in a separate Window (RaspberryPie currently only supports a single window). This is finally closed with Return and transferred to the TextBox.

I would like to avoid having to create my own CustomTextbox that the VirtualKeyboard opens automatically.

keyboard

Now I would like to combine it with avaloniaui. As you already mentioned here https://github.com/AvaloniaUI/Avalonia/issues/3415 it should be possible. But actually I'm a little bit stuck and don't know how to go on.

The events TextInputOptionsQuery and TextInputMethodClientRequested are not called.

var boxes = this.GetLogicalDescendants().OfType<TextBox>().ToList();
foreach (var box in boxes)
{
	box.TextInputOptionsQuery += (o, eventArgs) =>
	{
		// is not called
	};
	box.TextInputMethodClientRequested += (o, eventArgs) =>
	{
		// is not called                        
	};
}

As soon as the VirtualKeyboard works, I will share my code so that it may even be included in the repo or to improve it.

djonasdev avatar Oct 21 '21 11:10 djonasdev

@kekekeks can you take a look?

maxkatz6 avatar Oct 22 '21 00:10 maxkatz6

Right now InputMethodManager only looks for input method implementation in the visual root: https://github.com/AvaloniaUI/Avalonia/blob/43c04c2266348ded8b6698f8b497bedccb56fbf9/src/Avalonia.Input/TextInput/InputMethodManager.cs#L79-L81

We probably need to change it to traverse the visual tree to find a visual that provides input method capabilities and use that. It will require to handle some events to account for cases when said visual suddenly gets removed from the visual tree, that's why the initial implementation only uses the root.

For now you can implement your own Window base class and implement ITextInputMethodRoot there. It will enable input method interactions.

kekekeks avatar Oct 22 '21 12:10 kekekeks

keyboard2

For those who also want to implement a virtual keyboard, here is the source code.

Improvements are always welcome


grafik

Layout/KeyboardLayout.cs
public abstract class KeyboardLayout : UserControl
{
    string LayoutName { get; }
}
Layout/VirtualKeyboardLayoutDE.axaml
public partial class VirtualKeyboardLayoutDE : KeyboardLayout
{
    public VirtualKeyboardLayoutDE()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        AvaloniaXamlLoader.Load(this);
    }

    public string LayoutName => "de-DE";
}
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:keyboard="clr-namespace:HMI.Infrastructure.Keyboard"
             xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
             mc:Ignorable="d" d:DesignWidth="800"
             x:Class="HMI.Infrastructure.Keyboard.Layout.VirtualKeyboardLayoutDE">
  <UserControl.Resources>
    <keyboard:VirtualKeyWidthMultiplayer x:Key="VirtualKeyWidthMultiplayer"/>
  </UserControl.Resources>
  <UserControl.Styles>
    <Style Selector="keyboard|VirtualKey">
      <Setter Property="Width" Value="45"/>
      <Setter Property="Height" Value="45"/>
    </Style>
  </UserControl.Styles>
  <StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey NormalKey="^" ShiftKey="°" AltCtrlKey="" Name="VirtualKey1"/>
      <keyboard:VirtualKey NormalKey="1" ShiftKey="!" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="2" ShiftKey="&quot;" AltCtrlKey="²" />
      <keyboard:VirtualKey NormalKey="3" ShiftKey="§" AltCtrlKey="³" />
      <keyboard:VirtualKey NormalKey="4" ShiftKey="$" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="5" ShiftKey="%" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="6" ShiftKey="&amp;" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="7" ShiftKey="/" AltCtrlKey="{}{" />
      <keyboard:VirtualKey NormalKey="8" ShiftKey="(" AltCtrlKey="[" />
      <keyboard:VirtualKey NormalKey="9" ShiftKey=")" AltCtrlKey="]" />
      <keyboard:VirtualKey NormalKey="0" ShiftKey="=" AltCtrlKey="}" />
      <keyboard:VirtualKey NormalKey="ß" ShiftKey="?" AltCtrlKey="\" />
      <keyboard:VirtualKey NormalKey="´" ShiftKey="`" AltCtrlKey=""/>
      <keyboard:VirtualKey SpecialKey="Back" SpecialIcon="KeyboardBackspace" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=20}"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="Tab" SpecialIcon="KeyboardTab" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}"/>
      <keyboard:VirtualKey NormalKey="q" ShiftKey="Q" AltCtrlKey="@" />
      <keyboard:VirtualKey NormalKey="w" ShiftKey="W" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="e" ShiftKey="E" AltCtrlKey="€" />
      <keyboard:VirtualKey NormalKey="r" ShiftKey="R" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="t" ShiftKey="T" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="z" ShiftKey="Z" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="u" ShiftKey="U" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="i" ShiftKey="I" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="o" ShiftKey="O" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="p" ShiftKey="P" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="ü" ShiftKey="Ü" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="+" ShiftKey="*" AltCtrlKey="~"/>
      <keyboard:VirtualKey SpecialKey="Enter" SpecialIcon="KeyboardReturn" Margin="0,0,0,-45" Height="90" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}">
      </keyboard:VirtualKey>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="CapsLock" SpecialIcon="KeyboardCapslock" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=18}"/>
      <keyboard:VirtualKey NormalKey="a" ShiftKey="A" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="s" ShiftKey="S" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="d" ShiftKey="D" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="f" ShiftKey="F" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="g" ShiftKey="G" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="h" ShiftKey="H" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="j" ShiftKey="J" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="k" ShiftKey="K" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="l" ShiftKey="L" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="ö" ShiftKey="Ö" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="ä" ShiftKey="Ä" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="#" ShiftKey="'" AltCtrlKey="" />
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="LeftShift" SpecialIcon="AppleKeyboardShift" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}"/>
      <keyboard:VirtualKey NormalKey="&lt;" ShiftKey="&gt;" AltCtrlKey="|" />
      <keyboard:VirtualKey NormalKey="y" ShiftKey="Y" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="x" ShiftKey="X" AltCtrlKey="³" />
      <keyboard:VirtualKey NormalKey="c" ShiftKey="C" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="v" ShiftKey="V" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="b" ShiftKey="B" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="n" ShiftKey="N" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="m" ShiftKey="M" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="," ShiftKey=";" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="." ShiftKey=":" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="-" ShiftKey="_" AltCtrlKey="" />
      <keyboard:VirtualKey SpecialKey="RightShift" SpecialIcon="AppleKeyboardShift" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=25}">
      </keyboard:VirtualKey>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="Home" SpecialIcon="KeyboardTabReverse" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}"/>
      <keyboard:VirtualKey SpecialKey="Left" SpecialIcon="ChevronLeft" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}"/>
      <keyboard:VirtualKey SpecialKey="Right" SpecialIcon="ChevronRight" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}"/>
      <keyboard:VirtualKey SpecialKey="End" SpecialIcon="KeyboardTab" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}"/>
      <keyboard:VirtualKey NormalKey=" " Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=57}"/>
      <keyboard:VirtualKey SpecialKey="RightAlt" SpecialIcon="AppleKeyboardControl" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}"/>
      <keyboard:VirtualKey SpecialKey="Clear" SpecialIcon="DeleteForever" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}"/>
      <keyboard:VirtualKey SpecialKey="Help" SpecialIcon="Translate" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}"/>
    </StackPanel>
  </StackPanel>
</UserControl>

Layout/VirtualKeyboardLayoutUS.axaml
public partial class VirtualKeyboardLayoutUS : KeyboardLayout
{
    public VirtualKeyboardLayoutUS()
    {
        InitializeComponent();
    }

    private void InitializeComponent()
    {
        AvaloniaXamlLoader.Load(this);
    }

    public string LayoutName => "en-US";
}
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:keyboard="clr-namespace:HMI.Infrastructure.Keyboard"
             xmlns:avalonia="clr-namespace:Material.Icons.Avalonia;assembly=Material.Icons.Avalonia"
             mc:Ignorable="d" d:DesignWidth="800"
             x:Class="HMI.Infrastructure.Keyboard.Layout.VirtualKeyboardLayoutUS">
  <UserControl.Resources>
    <keyboard:VirtualKeyWidthMultiplayer x:Key="VirtualKeyWidthMultiplayer" />
  </UserControl.Resources>
  <UserControl.Styles>
    <Style Selector="keyboard|VirtualKey">
      <Setter Property="Width" Value="45" />
      <Setter Property="Height" Value="45" />
    </Style>
  </UserControl.Styles>
  <StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey NormalKey="`" ShiftKey="~" AltCtrlKey="" Name="VirtualKey1" />
      <keyboard:VirtualKey NormalKey="1" ShiftKey="!" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="2" ShiftKey="@" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="3" ShiftKey="#" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="4" ShiftKey="$" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="5" ShiftKey="%" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="6" ShiftKey="^" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="7" ShiftKey="&amp;" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="8" ShiftKey="*" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="9" ShiftKey="(" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="0" ShiftKey=")" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="-" ShiftKey="_" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="=" ShiftKey="+" AltCtrlKey="" />
      <keyboard:VirtualKey SpecialKey="Back" SpecialIcon="KeyboardBackspace" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=20}" />
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="Tab" SpecialIcon="KeyboardTab"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}" />
      <keyboard:VirtualKey NormalKey="q" ShiftKey="Q" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="w" ShiftKey="W" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="e" ShiftKey="E" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="r" ShiftKey="R" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="t" ShiftKey="T" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="y" ShiftKey="Y" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="u" ShiftKey="U" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="i" ShiftKey="I" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="o" ShiftKey="O" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="p" ShiftKey="P" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="[" ShiftKey="{}{" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="]" ShiftKey="}" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="\" ShiftKey="|" AltCtrlKey=""
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}" />
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="CapsLock" SpecialIcon="KeyboardCapslock"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=18}" />
      <keyboard:VirtualKey NormalKey="a" ShiftKey="A" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="s" ShiftKey="S" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="d" ShiftKey="D" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="f" ShiftKey="F" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="g" ShiftKey="G" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="h" ShiftKey="H" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="j" ShiftKey="J" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="k" ShiftKey="K" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="l" ShiftKey="L" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey=";" ShiftKey=":" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="'" ShiftKey="&quot;" AltCtrlKey="" />
      <keyboard:VirtualKey SpecialKey="Enter" SpecialIcon="KeyboardReturn" Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=22}"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="LeftShift" SpecialIcon="AppleKeyboardShift"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=25}" />
      <keyboard:VirtualKey NormalKey="z" ShiftKey="Z" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="x" ShiftKey="X" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="c" ShiftKey="C" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="v" ShiftKey="V" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="b" ShiftKey="B" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="n" ShiftKey="N" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="m" ShiftKey="M" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="," ShiftKey="&lt;" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="." ShiftKey=">" AltCtrlKey="" />
      <keyboard:VirtualKey NormalKey="/" ShiftKey="?" AltCtrlKey="" />
      <keyboard:VirtualKey SpecialKey="RightShift" SpecialIcon="AppleKeyboardShift"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=25}" />
    </StackPanel>
    <StackPanel Orientation="Horizontal">
      <keyboard:VirtualKey SpecialKey="Home" SpecialIcon="KeyboardTabReverse"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}" />
      <keyboard:VirtualKey SpecialKey="Left" SpecialIcon="ChevronLeft"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}" />
      <keyboard:VirtualKey SpecialKey="Right" SpecialIcon="ChevronRight"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}" />
      <keyboard:VirtualKey SpecialKey="End" SpecialIcon="KeyboardTab"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=12}" />
      <keyboard:VirtualKey NormalKey=" "
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=71}" />
      <keyboard:VirtualKey SpecialKey="Clear" SpecialIcon="DeleteForever"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}" />
      <keyboard:VirtualKey SpecialKey="Help" SpecialIcon="Translate"
                           Width="{Binding #VirtualKey1.Bounds.Width, Converter={StaticResource VirtualKeyWidthMultiplayer}, ConverterParameter=15}" />
    </StackPanel>
  </StackPanel>
</UserControl>
VirtualKey.axaml
public class VirtualKey : TemplatedControl
    {
        public static readonly StyledProperty<ICommand> ButtonCommandProperty = AvaloniaProperty.Register<VirtualKey, ICommand>(nameof(ButtonCommand));

        public ICommand ButtonCommand
        {
            get { return GetValue(ButtonCommandProperty); }
            set { SetValue(ButtonCommandProperty, value); }
        }

        public static readonly StyledProperty<string> NormalKeyProperty =  AvaloniaProperty.Register<VirtualKey, string>(nameof(NormalKey));

        public string NormalKey
        {
            get { return GetValue(NormalKeyProperty); }
            set { SetValue(NormalKeyProperty, value); }
        }

        public static readonly StyledProperty<string> ShiftKeyProperty =  AvaloniaProperty.Register<VirtualKey, string>(nameof(ShiftKey));

        public string ShiftKey
        {
            get { return GetValue(ShiftKeyProperty); }
            set { SetValue(ShiftKeyProperty, value); }
        }
        public static readonly StyledProperty<string> AltCtrlKeyProperty =  AvaloniaProperty.Register<VirtualKey, string>(nameof(AltCtrlKey));

        public string AltCtrlKey
        {
            get { return GetValue(AltCtrlKeyProperty); }
            set { SetValue(AltCtrlKeyProperty, value); }
        }

        public static readonly StyledProperty<object> CaptionProperty = AvaloniaProperty.Register<VirtualKey, object>(nameof(Caption));

        public object Caption
        {
            get { return GetValue(CaptionProperty); }
            set { SetValue(CaptionProperty, value); }
        }

        public static readonly StyledProperty<Key> SpecialKeyProperty =  AvaloniaProperty.Register<VirtualKey, Key>(nameof(SpecialKey));

        public Key SpecialKey
        {
            get { return GetValue(SpecialKeyProperty); }
            set { SetValue(SpecialKeyProperty, value); }
        }

        public static readonly StyledProperty<MaterialIconKind> SpecialIconProperty =  AvaloniaProperty.Register<VirtualKey, MaterialIconKind>(nameof(SpecialIcon));

        public MaterialIconKind SpecialIcon
        {
            get { return GetValue(SpecialIconProperty); }
            set { SetValue(SpecialIconProperty, value); }
        }

        public VirtualKeyboardLayoutDE VirtualKeyboardLayout { get; set; }

        private ToggleButton _toggleButton;

        public VirtualKey()
        {
            DataContext = this;

            Initialized += (sender, args) =>
            {
                VirtualKeyboard keyboard = null;
                if (!Design.IsDesignMode)
                {
                    keyboard = this.GetVisualAncestors().OfType<VirtualKeyboard>().First();

                    keyboard.KeyboardStateStream.Subscribe(state =>
                    {
                        if (!string.IsNullOrEmpty(NormalKey))
                        {
                            switch (state)
                            {
                                case VirtualKeyboardState.Default:
                                    Caption = NormalKey;
                                    break;
                                case VirtualKeyboardState.Shift:
                                case VirtualKeyboardState.Capslock:
                                    Caption = ShiftKey;
                                    break;
                                case VirtualKeyboardState.AltCtrl:
                                    Caption = AltCtrlKey;
                                    break;
                                default:
                                    throw new ArgumentOutOfRangeException(nameof(state), state, null);
                            }
                        }
                    });

                    ButtonCommand = new RelayCommand(() =>
                    {
                        if (string.IsNullOrEmpty(NormalKey))
                        {
                            keyboard.ProcessKey(SpecialKey);
                        }
                        else
                        {
                            if(Caption is string s && !string.IsNullOrEmpty(s))
                                keyboard.ProcessText(s);
                        }
                    });
                }

                if (SpecialKey == Key.LeftShift || SpecialKey == Key.RightShift || SpecialKey == Key.CapsLock || SpecialKey == Key.RightAlt)
                {
                    _toggleButton = new ToggleButton
                    {
                        BorderThickness = new Thickness(1),
                        BorderBrush = new SolidColorBrush(Color.Parse("Black")),
                        [!ToggleButton.WidthProperty] = new Binding("Width"),
                        [!ToggleButton.HeightProperty] = new Binding("Height"),
                        [!ToggleButton.ContentProperty] = new Binding("Caption"),
                        [!ToggleButton.CommandProperty] = new Binding("ButtonCommand"),
                    };
                    Template = new FuncControlTemplate((control, scope) => _toggleButton);

                    if (keyboard != null)
                    {
                        keyboard.KeyboardStateStream.Subscribe(state =>
                        {
                            switch (state)
                            {
                                case VirtualKeyboardState.Default:
                                    _toggleButton.IsChecked = false;
                                    break;
                                case VirtualKeyboardState.Shift:
                                    if (SpecialKey == Key.LeftShift || SpecialKey == Key.RightShift)
                                        _toggleButton.IsChecked = true;
                                    else
                                    {
                                        _toggleButton.IsChecked = false;
                                    }
                                    break;
                                case VirtualKeyboardState.Capslock:
                                    _toggleButton.IsChecked = SpecialKey == Key.CapsLock;
                                    break;
                                case VirtualKeyboardState.AltCtrl:
                                    _toggleButton.IsChecked = SpecialKey == Key.RightAlt;
                                    break;
                                default:
                                    throw new ArgumentOutOfRangeException(nameof(state), state, null);
                            }
                        });
                    }
                }
                else
                {
                    Template = new FuncControlTemplate((control, scope) =>
                    {
                        return new Button
                        {
                            BorderThickness = new Thickness(1),
                            BorderBrush = new SolidColorBrush(Color.Parse("Black")),
                            [!Button.WidthProperty] = new Binding("Width"),
                            [!Button.HeightProperty] = new Binding("Height"),
                            [!Button.ContentProperty] = new Binding("Caption"),
                            [!Button.CommandProperty] = new Binding("ButtonCommand"),
                        };
                    });
                }

                if (string.IsNullOrEmpty(NormalKey))
                {
                    // special cases
                    switch (SpecialKey)
                    {
                        case Key.Tab:
                        {
                            var stackPanel = new StackPanel();
                            stackPanel.Orientation = Orientation.Vertical;
                            var first = new MaterialIcon();
                            first.Kind = SpecialIcon;
                            var second = new MaterialIcon();
                            second.Kind = SpecialIcon;
                            second.RenderTransform = new RotateTransform(180.0);
                            stackPanel.Children.Add(first);
                            stackPanel.Children.Add(second);
                            Caption = stackPanel;
                            IsEnabled = false;
                        }
                            break;
                        case Key.Space:
                        {
                            Caption = null;
                        }
                            break;
                        default:
                            Caption = new MaterialIcon
                            {
                                Kind = SpecialIcon
                            };
                            break;
                    }
                }
                else
                {
                    Caption = NormalKey;
                }
            };
        }
    }
<Styles xmlns="https://github.com/avaloniaui"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:controls="using:HMI.Infrastructure.Keyboard">
  <Design.PreviewWith>
    <controls:VirtualKey />
  </Design.PreviewWith>
</Styles>
VirtualKeyboard.axaml
public enum VirtualKeyboardState
    {
        Default,
        Shift,
        Capslock,
        AltCtrl
    }
    public class VirtualKeyboard : UserControl
    {
        private static List<Type> Layouts { get; } = new();
        private static Func<Type> DefaultLayout { get; set; }

        public static void AddLayout<TLayout>() where TLayout : KeyboardLayout => Layouts.Add(typeof(TLayout));

        public static void SetDefaultLayout(Func<Type> getDefaultLayout) => DefaultLayout = getDefaultLayout;

        public static async Task<string?> ShowDialog(TextInputOptionsQueryEventArgs options, Window? owner = null)
        {
            var keyboard = new VirtualKeyboard();

            if (options.Source is TextBox textBox)
            {
                keyboard.TextBox.Text = textBox.Text;
                keyboard.TextBox.PasswordChar = textBox.PasswordChar;
            }

            var window = new CoporateWindow();
            window.CoporateContent = keyboard;
            window.Title = "MyFancyKeyboard";
            await window.ShowDialog(owner ?? App.MainWindow);
            if (window.Tag is string s)
            {
                if (options.Source is TextBox tb)
                    tb.Text = s;
                return s;
            }
            return null;
        }

        public TextBox TextBox { get; }
        public TransitioningContentControl TransitioningContentControl { get; }

        public IObservable<VirtualKeyboardState> KeyboardStateStream => _keyboardStateStream;
        private readonly BehaviorSubject<VirtualKeyboardState> _keyboardStateStream;

        private Window _parentWindow;

        public VirtualKeyboard()
        {
            InitializeComponent();
            TextBox = this.Get<TextBox>(nameof(TextBox));
            TransitioningContentControl = this.Get<TransitioningContentControl>(nameof(TransitioningContentControl));

            Initialized += async (sender, args) =>
            {
                TransitioningContentControl.Content = Activator.CreateInstance(DefaultLayout.Invoke());
                _parentWindow = this.GetVisualAncestors().OfType<Window>().First();
                await Task.Delay(TimeSpan.FromMilliseconds(100));
                Dispatcher.UIThread.Post(() =>
                {
                    TextBox.Focus();
                    if(!string.IsNullOrEmpty(TextBox.Text))
                        TextBox.CaretIndex = TextBox.Text.Length;
                });
            };
            KeyDown += (sender, args) =>
            {
                TextBox.Focus();
                if (args.Key == Key.Escape)
                {
                    TextBox.Text = "";
                }
                else if(args.Key == Key.Enter)
                {
                    _parentWindow.Tag = TextBox.Text;
                    _parentWindow.Close();
                }
            };
            _keyboardStateStream = new BehaviorSubject<VirtualKeyboardState>(VirtualKeyboardState.Default);
        }

        public void ProcessText(string text)
        {
            TextBox.Focus();
            InputManager.Instance.ProcessInput(new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, (Window)TextBox.GetVisualRoot(), text ));
            if (_keyboardStateStream.Value == VirtualKeyboardState.Shift)
            {
                _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
            }
        }

        public void ProcessKey(Key key)
        {
            if (key == Key.LeftShift || key == Key.RightShift)
            {
                if (_keyboardStateStream.Value == VirtualKeyboardState.Shift)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                }
                else
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Shift);
                }
            }
            else if (key == Key.RightAlt)
            {
                if (_keyboardStateStream.Value == VirtualKeyboardState.AltCtrl)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                }
                else
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.AltCtrl);
                }
            }
            else if (key == Key.CapsLock)
            {
                if (_keyboardStateStream.Value == VirtualKeyboardState.Capslock)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                }
                else
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Capslock);
                }
            }
            else
            {
                if (key == Key.Clear)
                {
                    TextBox.Text = "";
                    TextBox.Focus();
                }
                else if (key == Key.Enter)
                {
                    _parentWindow.Tag = TextBox.Text;
                    _parentWindow.Close();
                }
                else if (key == Key.Help)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                    if (TransitioningContentControl.Content is KeyboardLayout layout)
                    {
                        var index = Layouts.IndexOf(layout.GetType());
                        if (Layouts.Count - 1 > index)
                        {
                            TransitioningContentControl.Content = Activator.CreateInstance(Layouts[index + 1]);
                        }
                        else
                        {
                            TransitioningContentControl.Content = Activator.CreateInstance(Layouts[0]);
                        }
                    }
                }
                else
                {
                    TextBox.Focus();
                    InputManager.Instance.ProcessInput(new RawKeyEventArgs(KeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, (Window)TextBox.GetVisualRoot(), RawKeyEventType.KeyDown, key, RawInputModifiers.None ));
                    InputManager.Instance.ProcessInput(new RawKeyEventArgs(KeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, (Window)TextBox.GetVisualRoot(), RawKeyEventType.KeyUp, key, RawInputModifiers.None ));
                }
            }
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }
<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:reactiveUi="http://reactiveui.net"
             xmlns:layout="clr-namespace:HMI.Infrastructure.Keyboard.Layout"
             mc:Ignorable="d"
             x:Class="HMI.Infrastructure.Keyboard.VirtualKeyboard">
    <DockPanel Margin="10">
      <TextBox Name="TextBox" DockPanel.Dock="Top"></TextBox>
      <Grid DockPanel.Dock="Bottom">
        <!-- QuickAndDirty resize bugfix -->
        <layout:VirtualKeyboardLayoutDE Opacity="0" IsHitTestVisible="False"/>
        <reactiveUi:TransitioningContentControl Name="TransitioningContentControl" />
      </Grid>
    </DockPanel>
</UserControl>
VirtualKeyboardTextInputMethod.cs
    public class VirtualKeyboardTextInputMethod : ITextInputMethodImpl
    {
        private bool _isOpen;
        private TextInputOptionsQueryEventArgs? _textInputOptions;
        public async void SetActive(bool active)
        {
            if (active && !_isOpen && _textInputOptions != null)
            {
                _isOpen = true;
                await VirtualKeyboard.ShowDialog(_textInputOptions);
                _isOpen = false;
                _textInputOptions = null;
                App.MainWindow.Focus(); // remove focus from the last control (TextBox)
            }
        }

        public void SetCursorRect(Rect rect){}

        public void SetOptions(TextInputOptionsQueryEventArgs? options)
        {
            _textInputOptions = options;
        }

        public void Reset(){}
    }
VirtualKeyWidthMultiplayer.cs
    public class VirtualKeyWidthMultiplayer : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var v = double.Parse(value.ToString());
            var p = double.Parse(parameter.ToString());
            return v * (p / 10.0);
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException();
    }
CoporateWindow

The CoporateWindow is just a normal Window with a default styling. It can easily be replaced by the normal Window.

<Window xmlns="https://github.com/avaloniaui"
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
      xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
      xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
      xmlns:theme="clr-namespace:HMI.Theme"
      mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
      x:Class="HMI.Views.CoporateWindow"
      Title="CoporateWindow"
      Icon="/Assets/logo.ico"
      TransparencyLevelHint="AcrylicBlur"
      ExtendClientAreaToDecorationsHint="True"
      Background="Red"
      SizeToContent="WidthAndHeight"
      WindowStartupLocation="CenterOwner">
<Panel>
  <ExperimentalAcrylicBorder IsHitTestVisible="False">
    <ExperimentalAcrylicBorder.Material>
      <ExperimentalAcrylicMaterial
        BackgroundSource="Digger"
        TintColor="{x:Static theme:HaprotecCorporateDesignTheme.Secondary}"
        TintOpacity="1"
        MaterialOpacity="0.65" />
    </ExperimentalAcrylicBorder.Material>
  </ExperimentalAcrylicBorder>
  <Panel Margin="20,40,20,20">
    <ContentPresenter Content="{Binding Path=CoporateContent}"/>
  </Panel>
</Panel>
</Window>
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;

namespace HMI.Views
{
    public class CoporateWindow : Window
    {
        public static readonly StyledProperty<object> CoporateContentProperty = AvaloniaProperty.Register<CoporateWindow, object>(nameof(CoporateContent));

        public object CoporateContent
        {
            get { return GetValue(CoporateContentProperty); }
            set { SetValue(CoporateContentProperty, value); }
        }

        public CoporateWindow()
        {
            DataContext = this;
            InitializeComponent();
#if DEBUG
            this.AttachDevTools();
#endif
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }
}

Usage

define available layouts and set func which one should be used when opening (based on the language of the current logged in user for example).

VirtualKeyboard.AddLayout<VirtualKeyboardLayoutDE>();
VirtualKeyboard.AddLayout<VirtualKeyboardLayoutUS>();
VirtualKeyboard.SetDefaultLayout(() => typeof(VirtualKeyboardLayoutDE));

Add the VirtualKeyboardTextInputMethod to your MainWindow. Here you can optional check your local settings (of your app) if you need the VirtualKeyboard or not.

   public partial class MainWindow : ReactiveWindow<MainWindowViewModel>, ITextInputMethodRoot
    {
        public MainWindow()
        {
            InitializeComponent();
#if DEBUG
            this.AttachDevTools();

#endif
        }

        private void InitializeComponent() => AvaloniaXamlLoader.Load(this);

        private VirtualKeyboardTextInputMethod virtualKeyboardTextInput = new VirtualKeyboardTextInputMethod();
        ITextInputMethodImpl ITextInputMethodRoot.InputMethod
        {
            get
            {
                return virtualKeyboardTextInput;
            }
        }
    }

djonasdev avatar Nov 02 '21 13:11 djonasdev

Thank you for sharing your code. But there is no code for the class CoporateWindow. Can you please share this class for everyone? Thank you so much!

ichbinvinh avatar Jan 05 '22 15:01 ichbinvinh

@ichbinvinh I added the missing CoporateWindow class. However, it is not absolutely necessary to use this.

djonasdev avatar Jan 10 '22 13:01 djonasdev

@dojo90 I can't see the code for VirtualKey.axaml? I'm a bit new to Avalonia, so it may just be me...

Things work, but the icon buttons (shift, enter etc) does not show icons

elveejay1 avatar Aug 05 '22 07:08 elveejay1

@dojo90 I can't see the code for VirtualKey.axaml? I'm a bit new to Avalonia, so it may just be me...

Things work, but the icon buttons (shift, enter etc) does not show icons

I admit that I posted the sources a bit confusing.

I have now summarized all files in individual spoilers 😉

djonasdev avatar Aug 05 '22 08:08 djonasdev

@dojo90 I can't see the code for VirtualKey.axaml? I'm a bit new to Avalonia, so it may just be me... Things work, but the icon buttons (shift, enter etc) does not show icons

I admit that I posted the sources a bit confusing.

I have now summarized all files in individual spoilers 😉

No problems. I think your work is super cool. Thanks for responding so fast to a very old post

I found out why I didn't see the icons. If someone else runs into the same problem as me, they can check: https://github.com/AvaloniaUtils/Material.Icons.Avalonia It turned out I needed an application style... <Application ...> <Application.Styles> ... <StyleInclude Source="avares://Material.Icons.Avalonia/App.xaml"></StyleInclude> </Application.Styles> </Application>

elveejay1 avatar Aug 05 '22 08:08 elveejay1

@dojo90 did you have a chance to port this implementation to Avalonia 11?

vs-savelich avatar Aug 02 '23 13:08 vs-savelich

@vs-savelich I'm sorry, but I no longer use avalonia. 😐

djonasdev avatar Aug 02 '23 13:08 djonasdev

Hey I found a solution to refactor the code so it works under Avalonia 11.

Instead of implementing the ITextInputMethodRoot Interface on the Mainwindow, we could use GotFocusEvent and filtering it for Textbox controls. It is important to somehow clear the Focus. Currently I use the FocusManager.ClearFocus() but this method will be removed in a 11.x update. Because ProcessInput of InputManager isnt available an there are some limitations and visual bugs. for example the Cursor in the TextBox stays always a the beginning of the Textbox.

I also have a strange bug on Linux ARM. If I open the Virtualkeyboard in a Dialog the VirtualKeyboard will reappear because the focus isn't cleared and as soon the VirtualKeyboard closes, the TextBox regains focus and will open the Virtualkeyboard again.

 InputManager.Instance.ProcessInput(new RawKeyEventArgs(KeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, (Window)TextBox.GetVisualRoot(), RawKeyEventType.KeyDown, key, RawInputModifiers.None))

Window

 public partial class MainWindow : Window
    {
        private VirtualKeyboardTextInputMethod virtualKeyboardTextInput = null;
        public MainWindow()
        {
            InitializeComponent();
            this.SystemDecorations = SystemDecorations.None;
            this.WindowStartupLocation = WindowStartupLocation.CenterScreen;
            virtualKeyboardTextInput = new VirtualKeyboardTextInputMethod((Window)this);

            this.AddHandler < GotFocusEventArgs>(Control.GotFocusEvent, openVirtualKeyboard);
        }

        private void openVirtualKeyboard(object? sender, GotFocusEventArgs e)
        {
            if(e.Source.GetType() == typeof(TextBox))
            {
                FocusManager.ClearFocus();
                virtualKeyboardTextInput.SetActive(true, e);
                
            } 
        }
}

VirtualKeyboardTextInputMethod

public class VirtualKeyboardTextInputMethod 
    {
        private bool _isOpen;
        private TextInputOptions? _textInputOptions;

        private Window root = null;

        public VirtualKeyboardTextInputMethod(Window root)
        {
            this.root = root;
        }
        public VirtualKeyboardTextInputMethod()
        {
          
        }

        public async Task SetActive(GotFocusEventArgs e)
        {
            if (!_isOpen )
            {
                
                _isOpen = true;
                var oskReturn  = await VirtualKeyboard.ShowDialog(_textInputOptions, this.root);
                
                if(e.Source.GetType() == typeof(TextBox))
                {
                    ((TextBox)e.Source).Text = oskReturn;
                   
                }

                _isOpen = false;
                _textInputOptions = null;

                if (this.root != null)
                {
                   root!.Focus();
                }
                else if (App.Current.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
                {
                   desktop.MainWindow.Focus();
                }

                e.Handled = true;
                
            }
        }
    }

VirtualKeyboard

public enum VirtualKeyboardState
    {
        Default,
        Shift,
        Capslock,
        AltCtrl
    }
    public partial class VirtualKeyboard : UserControl
    {
        private static List<Type> Layouts { get; } = new List<Type>();
        private static Func<Type> DefaultLayout { get; set; }

        public static void AddLayout<TLayout>() where TLayout : KeyboardLayout => Layouts.Add(typeof(TLayout));

        public static void SetDefaultLayout(Func<Type> getDefaultLayout) => DefaultLayout = getDefaultLayout;

        public static async Task<string?> ShowDialog(TextInputOptions options, Window? owner = null)
        {
            var keyboard = new VirtualKeyboard();
            
            var window = new CoporateWindow();
            window.CoporateContent = keyboard;
            window.Title = "Keyboard";

            var mw = ((IClassicDesktopStyleApplicationLifetime)App.Current.ApplicationLifetime).MainWindow;



            await window.ShowDialog(owner ?? mw);
            if (window.Tag is string s)
            {
                //if (options.Source is TextBox tb)
                //    tb.Text = s;
                return s;
            }
            return null;
        }

        public TextBox TextBox_ { get; }
        public Button AcceptButton_ { get; }
        public string targetLayout { get; set; }
        public TransitioningContentControl TransitioningContentControl_ { get; }

        public IObservable<VirtualKeyboardState> KeyboardStateStream => _keyboardStateStream;
        private readonly BehaviorSubject<VirtualKeyboardState> _keyboardStateStream;

        private Window _parentWindow;

        public VirtualKeyboard()
        {
            InitializeComponent();
            TextBox_ = this.Get<TextBox>("TextBox");
            TransitioningContentControl_ = this.Get<Avalonia.Controls.TransitioningContentControl>("TransitioningContentControl");
            AcceptButton_ = this.Get<Button>("AcceptButton");

            AcceptButton_.AddHandler(Button.ClickEvent, acceptClicked);

            Initialized += async (sender, args) =>
            {
                
                if(targetLayout == null)
                {
                    TransitioningContentControl_.Content = Activator.CreateInstance(DefaultLayout.Invoke());
                }
                else
                {
                    var layout = Layouts.FirstOrDefault(x => x.Name.ToLower().Contains(targetLayout.ToLower()));
                    if (layout != null)
                    {
                        TransitioningContentControl_.Content = Activator.CreateInstance(layout);
                    }
                    else
                    {
                        TransitioningContentControl_.Content = Activator.CreateInstance(DefaultLayout.Invoke());
                    }
                }

                _parentWindow = this.GetVisualAncestors().OfType<Window>().First();
                await Task.Delay(TimeSpan.FromMilliseconds(100));
                Dispatcher.UIThread.Post(() =>
                {
                    TextBox_.Focus();
                    if (!string.IsNullOrEmpty(TextBox_.Text))
                        TextBox_.CaretIndex = TextBox_.Text.Length;
                });
            };

            KeyDown += (sender, args) =>
            {
                TextBox_.Focus();
                if (args.Key == Key.Escape)
                {
                    TextBox_.Text = "";
                }
                else if (args.Key == Key.Enter)
                {
                    _parentWindow.Tag = TextBox_.Text;
                    _parentWindow.Close();
                }
            };
            _keyboardStateStream = new BehaviorSubject<VirtualKeyboardState>(VirtualKeyboardState.Default);
        }

        private void acceptClicked(object? sender, RoutedEventArgs e)
        {
            _parentWindow.Tag = TextBox_.Text;
            _parentWindow.Close();
        }

        public void ProcessText(string text)
        {
            TextBox_.Focus();
            TextBox_.Text += text;
            //InputManager.Instance.ProcessInput(new RawTextInputEventArgs(KeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, (Window)TextBox.GetVisualRoot(), text));
            if (_keyboardStateStream.Value == VirtualKeyboardState.Shift)
            {
                _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
            }
        }

        public void Accept()
        {
            _parentWindow.Tag = TextBox_.Text;
            _parentWindow.Close();
        }

        public void ProcessKey(Key key)
        {
            if (key == Key.LeftShift || key == Key.RightShift)
            {
                if (_keyboardStateStream.Value == VirtualKeyboardState.Shift)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                }
                else
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Shift);
                }
            }
            else if (key == Key.RightAlt)
            {
                if (_keyboardStateStream.Value == VirtualKeyboardState.AltCtrl)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                }
                else
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.AltCtrl);
                }
            }
            else if (key == Key.CapsLock)
            {
                if (_keyboardStateStream.Value == VirtualKeyboardState.Capslock)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                }
                else
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Capslock);
                }
            }
            else
            {
                if (key == Key.Clear)
                {
                    TextBox_.Text = "";
                    TextBox_.Focus();
                }
                else if (key == Key.Enter || key == Key.ImeAccept)
                {
                    _parentWindow.Tag = TextBox_.Text;
                    _parentWindow.Close();
                }
                else if (key == Key.Help)
                {
                    _keyboardStateStream.OnNext(VirtualKeyboardState.Default);
                    if (TransitioningContentControl_.Content is KeyboardLayout layout)
                    {
                        var index = Layouts.IndexOf(layout.GetType());
                        if (Layouts.Count - 1 > index)
                        {
                            TransitioningContentControl_.Content = Activator.CreateInstance(Layouts[index + 1]);
                        }
                        else
                        {
                            TransitioningContentControl_.Content = Activator.CreateInstance(Layouts[0]);
                        }
                    }
                }
                else if(key == Key.Back)
                {

                    if(TextBox_.Text != null && TextBox_.Text.Length > 0)
                    {
                        TextBox_.Text = TextBox_.Text.Remove(TextBox_.Text.Length - 1, 1);
                    }
                    
                }
                else
                {
                    TextBox_.Focus();
                    
                    //InputManager.Instance.ProcessInput(new RawKeyEventArgs(KeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, (Window)TextBox.GetVisualRoot(), RawKeyEventType.KeyDown, key, RawInputModifiers.None));
                    //InputManager.Instance.ProcessInput(new RawKeyEventArgs(KeyboardDevice.Instance, (ulong)DateTime.Now.Ticks, (Window)TextBox.GetVisualRoot(), RawKeyEventType.KeyUp, key, RawInputModifiers.None));
                }
            }
        }

        private void InitializeComponent()
        {
            AvaloniaXamlLoader.Load(this);
        }
    }

nullx1337 avatar Aug 10 '23 15:08 nullx1337

Hello @nullx1337 thanks so much, i was searching for this for long, could you please share a working code that can compile with avalonia 11 ? thanks

011010101001 avatar Aug 11 '23 13:08 011010101001

Hello @nullx1337 thanks so much, I tried to fix the keyboard for Avalonia 11 but i still have soma issues. Could you please share a working code that can compile with avalonia 11? thanks

dariobattistella avatar Aug 19 '23 15:08 dariobattistella

What about native On-Screen Keyboard support on Windows? i.e. when a TextBox gets focused, OSK should appear, just like in Android.

mozesa avatar Aug 29 '23 12:08 mozesa

Sure, but the app runs on embed system with linux-arm64, so, in this case i don't have any OSK.

dariobattistella avatar Aug 29 '23 13:08 dariobattistella

Do you need full keyboard or numerical is enough?

mozesa avatar Aug 29 '23 13:08 mozesa

full keyboard is mandatory

dariobattistella avatar Aug 29 '23 14:08 dariobattistella

I spent more than a week trying to get this working on an embedded lunix-arm64 system using @nullx1337 GotFocusEvent suggestion. There is a known bug in Avlonia-ui 11 that has to do with focus and popup dialogs. Every control you click after dismissing the keyboard with trigger the OnFocus event as the TextBox. To work around this I created a keyboard on the main window, instead of using a popup, and set it to visible on receiving a GotFocus event from a textbox. I also set the opacity of the other Grid item to 0.2.:

<Grid>
  <Grid x:Name="MainGrid" RowDefinitions="1*,10*,1*" ColumnDefinitions="1*,1*,1*,1*,1*" Opacity="{Binding MainOpacity}"> 
    <!-- Content -->
  </Grid>
		
  <Border IsVisible="{Binding IsOskVisible}"  Background="#40000000">
    <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
      <cont:VirtualKeyboard x:Name="OSK" Background="LightGray"/>
    </StackPanel>
  </Border>
</Grid>

I use the Community toolkit Mvvm Messenger service to send 'show/hide' messages, to the MainWindow and VirtualKeyboard control and also to pass the source textbox object.

public class OskControlMsg : ValueChangedMessage<bool>
{
  public OskControlMsg(bool value) : base(value)
  {
  }
}

public class MainPageViewModel : ReactiveObject,  IRecipient<OskControlMsg>
{
...
  public void Receive(OskControlMsg message)
  {
    Dispatcher.UIThread.InvokeAsync(() =>
    {
      IsOskVisible = message.Value;
      MainOpacity = (IsOskVisible ? .2 : 1.0);
    });
  }
...
}

public partial class MainPage : ReactiveWindow<MainPageViewModel>
{
  public MainPage()
  {
...
      this.AddHandler<GotFocusEventArgs>(Control.GotFocusEvent, openVirtualKeyboard);
...
  }

  private void openVirtualKeyboard(object? sender, GotFocusEventArgs e)
  {
    if (e.Source.GetType() == typeof(TextBox))
    {
      if(!ViewModel.IsOskVisible)
      {
        WeakReferenceMessenger.Default.Send(new PassObjectMsg(e.Source));
        WeakReferenceMessenger.Default.Send(new OskControlMsg(true));
        e.Handled = true;
      }
      else
      {
        e.Handled = false;
      }
    }
  }
}
public class PassObjectMsg : ValueChangedMessage<object>
{
  public PassObjectMsg(object value) : base(value)
  {
  }
}

public partial class VirtualKeyboard : UserControl, IRecipient<PassObjectMsg>
{
 ...
  public async void Receive(PassObjectMsg message)
  {
    if (message.Value is TextBox textBox)
    {
      TextBox_.Text = textBox.Text;
      sourceObject = textBox;
      await Task.Delay(TimeSpan.FromMilliseconds(100));
      Dispatcher.UIThread.Post(() =>
      {
        TextBox_.Focus();
        if (!string.IsNullOrEmpty(TextBox_.Text))
          TextBox_.CaretIndex = TextBox_.Text.Length;
      });
    }
  }

  public void ProcessKey(Key key)
  {
...
      else if(key == Key.Escape)
      {
        WeakReferenceMessenger.Default.Send(new OskControlMsg(false));
      }
      else if (key == Key.Enter)
      {
        sourceObject.Text = TextBox_.Text;
        WeakReferenceMessenger.Default.Send(new OskControlMsg(false));
      }
'''
  }


}

As you don't need any of the popup stuff or ShowDialog you don't need to reference the window handle anywhere or have to find links back to some other objects. My application is reasonably simple, I insert Panels into the Main Grid instead of opening new windows so the keyboard will always be present. Hopefully mentioning this approach will help someone else with their project.

krobotics avatar Nov 30 '23 18:11 krobotics

@vs-savelich I'm sorry, but I no longer use avalonia. 😐

what do you use now? I am starting out and would like to try other approaches. I am trying to find the fastest way to develop a modern looking UI on Raspberry Pi - I have used Visual Studio for Winforms and WPF for many years, hoping to find a cross platform solution.

c4801725870 avatar Feb 25 '24 23:02 c4801725870