In this article, you will learn about an on-screen keyboard which has many options to operate with customization. I’ve implemented not only a keyboard, but also a TextBox with keyboard's picker.
Introduction
Recently, I’ve had a need for a professional looking virtual keyboard control for my WinForm project. I’ve wanted a control that suits the interface of my application. Online, I found plenty of keyboards, but many of them had considerable shortcomings, such as customization absence, inconvenient settings, slow speed, impossibility to send pressed key to an inactive window. So I decided to make own on-screen keyboard which has many options to operate with customization. Along the way, I’ve implemented not only a keyboard, but also a TextBox
with keyboard's picker.
Background
The VirtualKeyBoard
derives from the System.Windows.Forms.Control
and in order to extend its implementation, you must to be familiar with GDI+ programming. The control works as emulator and lets input text into the currently focused entry field. It allows users to change keyboard design, to manipulate with such properties as colors for different elements, font for buttons, and also you can hide/unhide different buttons areas.
The TextBoxWithKeyboard
is the extended standard System.Windows.Forms.TextBox
control. The component shows implemented virtual keyboard under TextBox
and hides it when the user clicks somewhere away from the keyboard.
Using the Code
To use the VirtualKeyboard
as given here, you simply need to download the given VirtualKeyboard.DLL and include it into your Visual Studio Project. If you want to use TextBoxWithKeyboard
control, you must to attach VirtualKeyboard.dll and TextBoxKeyboard.dll into your application or you can attach TextBoxKeyboard.csproj project to your solution. Also, it will be required to change visual design of popup keyboard, you'll have to modify it manually, the keyboard can be found on the OnScreenKeyboardForm.cs form.
By default, virtual keyboard sends messages to the currently active window, but when you click with the finger (on the touchscreen) or the mouse another application (Notepad, Word, etc.), becomes active window within clicked application. If you want prevent it, you can investigate "OSK.exe" window style through "SPY++". The window has extended styles WS_EX_NOACTIVATE
and WS_EX_TOPMOST
. They prevent the window from becoming active and stealing input focus. These styles quite easily can be used by overriding CreataParams
.
const uint WS_EX_NOACTIVATE = 0x08000000;
const uint WS_EX_TOPMOST = 0x00000008;
protected override CreateParams CreateParams
{
get
{
CreateParams baseParams = base.CreateParams;
baseParams.ExStyle |= (int)(WS_EX_NOACTIVATE | WS_EX_TOPMOST);
return baseParams;
}
}
Such trick doesn't allow to enter field values on another Windows form which was created within your .NET Visual Studio application. There are many ways to transfer data from one Windows form to another. I found two different methods to achieve this.
The first way is the thread pool. Just call Application.Run
from another thread and display the specified form with keyboard.
...
ThreadPool.QueueUserWorkItem(KeyboardLoop);
...
private void KeyboardLoop(object state)
{
Application.Run(new KeyboradExampleForm() { });
}
The second way which I can suggest, to override CreataParams
for form with keyboard.
const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_EX_TOPMOST = 0x00000008;
const int WS_CHILD = 0x40000000;
const int WS_BORDER = 0x00800000;
const int WS_DLGFRAME = 0x00400000;
const int WS_CAPTION = WS_BORDER | WS_DLGFRAME;
const int WS_SYSMENU = 0x00080000;
const int WS_MAXIMIZEBOX = 0x00010000;
const int WS_MINIMIZEBOX = 0x00020000;
private const int WS_THICKFRAME = 0x00040000;
private const int WS_SIZEBOX = WS_THICKFRAME;
protected override CreateParams CreateParams
{
get
{
CreateParams ret = base.CreateParams;
ret.Style = WS_CAPTION |
WS_SIZEBOX | WS_SYSMENU |
WS_MINIMIZEBOX |
WS_MAXIMIZEBOX | WS_CHILD;
ret.ExStyle |= (WS_EX_NOACTIVATE | WS_EX_TOPMOST);
StartPosition = FormStartPosition.CenterScreen;
return ret;
}
}
Properties of the Control
The control VirtualKeyBoard
has the following properties:
FirstRowCustomButtons
: buttons list for the first row SecondRowCustomButtons
: buttons list for the second row ThirdRowCustomButtons
: buttons list for the third row FourthRowCustomButtons
: buttons list for the fourth row FifthRowCustomButtons
: buttons list for the fifth row - properties to apply states of the keyboard (i.e., Shift state, CapsLock state and so on)
- properties to show/hide special buttons of the keyboard (i.e., Ctrl, Tab, Del buttons and so on)
- properties to apply visual properties of the keyboard (color, font and so on)
Each button of the keyboard VirtualKbButton
has its own properties:
TopText
: top text of button BottomText
: bottom text of button Picture
: image of button TopFont
: font for top text of button BottomFont
: font for bottom text of button CanSendCommand
: whether button can send text command Tag
: tag of button ButtonName
: name of button
Additionally, the VirtualKeyBoard
has event ButtonClick
, which occurs when keyboard button is clicked.
Implementation
The class VirtualKeyBoard
has code to create a list of keyboard buttons List. Each keyboard button is the VirtualKbButton.cs class with such properties as rectangle, text for button, name of button. The component uses these properties to paint the keyboard buttons by their coordinates. In order to populate the list of keyboard keys, I used lists of buttons with text. The lists are separated by rows. Example of the first row:
public ButtonsCollection FirstRowButtonsDefault()
{
return new ButtonsCollection
{
new VirtualKbButton("~", "`"),
new VirtualKbButton("!", "1"),
new VirtualKbButton("@", "2"),
new VirtualKbButton("#", "3"),
...
};
}
The user can populate lists of buttons with own properties. Here is the code for button adding:
Font buttonFont = new Font("Tahoma", 8, FontStyle.Bold, GraphicsUnit.Point, 204);
VirtualKbButton btn = new VirtualKbButton();
btn.TopFont = buttonFont;
btn.Tag = "btn_Internet";
btn.TopText = "www";
btn.CanSendCommand = false;
virtualKeyboard1.FifthRowCustomButtons.Add(btn);
With SendKeys
method, the keyboard component simulates key pressed events. By this method, each letter can be handled by whatever control currently has the focus.
SendKeys.Send("a");
SendKeys.Send("~");
To send such symbols as '{', '}', '+, '^', '%', '~', '(', ')', it is necessary to use curly braces.
SendKeys.Send("{%}");
SendKeys.Send("{+}");
As the many of special keys (ENTER, TAB, DELETE, etc.) don't have corresponding alphabetic designations, they have special mnemonic names. Using curly braces, it is possible to emulate these keys:
SendKeys.Send("{ENTER}");
SendKeys.Send("{ESC}");
The exception is made for SHIFT, CTRL, ALT keys. They have special symbolic names: '+' for Shift , '^' for Ctrl, '%' for Alt. Such designations help make different keys combinations.
SendKeys.Send("^c");
SendKeys.Send("^%s");
SendKeys.Send("%{F4}");
At this moment, the VirtualKeyBoard
can handle the following key combinations:
- Ctrl + Alt + Shift + Any Letter or Any Function Command (F1 - F12)
- Ctrl + Alt + Any Letter or Any Function Command
- Ctrl + Any Letter or Any Function Command
- Alt + Any Letter or Any Function Command
The TextBoxWithKeyboard
class uses IMessageFilter interface
for mouse clicks. It checks to see whether a mouse event is raised outside the popup form, which contains VirtualKeyBoard
control.
private static KeyboardFilter kf = null;
...
if (kf == null)
{
kf = new KeyboardFilter();
Application.AddMessageFilter(kf);
}
...
private class KeyboardFilter : IMessageFilter
{
public delegate void LeftButtonDown();
public event LeftButtonDown MouseDown;
public delegate void KeyPressUp(IntPtr target);
public event KeyPressUp KeyUp;
private const int WM_KEYUP = 0x101;
private const int WM_LBUTTONDOWN = 0x201;
bool IMessageFilter.PreFilterMessage(ref Message m)
{
switch (m.Msg)
{
case WM_KEYUP:
if (KeyUp != null)
{
KeyUp(m.HWnd);
}
break;
case WM_LBUTTONDOWN:
if (MouseDown != null)
{
MouseDown();
}
break;
}
return false;
}
}
TextBoxWithKeyboard
can display either numeric or standard keypad, depending on control's properties.
History
- 22nd February, 2017: Initial post
- 10th June, 2017: Added functions to apply custom XML keyboard layout
- 11th June, 2017: Fixed bug, the keyboard had been sending
null
value during literal key pressed - 14th February, 2021: Changed functions to apply custom layout, added possibility to change number of buttons on each row and to show/hide special buttons
- 16th February, 2021: Fixed drawing mistake of the Function buttons row
- 1st December, 2021: Improved OnClick speed