Touchscreen GDI+ drawn Keyboard

0
101

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 it’s 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 standart 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 if 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 becomming active and steeling 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.

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() { });
}

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

Implementation

The class VirtualKeyBoard has code to create a list of keyboard buttons List<VirtualKbButton>. Each keyboard button is the VirtualKeyBoard.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 tuples with text for buttons. The tuples are separated by rows. Example of the first row:

public List<Tuple<string, string>> FirstRowShort()
 {
     return new List<Tuple<string, string>>
    {
        new Tuple<string, string>("~", "`"),
        new Tuple<string, string>("!", "1"),
        new Tuple<string, string>("@", "2"),
...
    };
 }

By default, the component uses predefined english keyboard layout. You can create custom layouts represented by XML file and apply to the keyboard. In order to get predefined layout , use the method GetDefaultLayout(), which returns XDocument.  Using as basis this XML scheme, you can create  and save your own layout. This approach can help use different languages.

<KeyboardLayout>
 <Row>
 <RowNumber>1</RowNumber>
 <Buttons>
 <Text>
 <UpText>~</UpText>
 <BottomText>`</BottomText>
 </Text>
 <Text>
 <UpText>!</UpText>
 <BottomText>1</BottomText>
 </Text>
...
 </Buttons>
 </Row>
...
</KeyboardLayout>

The created XML document can be applied with LayoutSettings property.

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 keys 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

  • 22 february 2017: Initial post
  • 10 june 2017: Added functions to apply custom XML keyboard layout

LEAVE A REPLY