Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / multimedia / GDI+

Touchscreen GDI+ Drawn Keyboard

4.94/5 (25 votes)
16 Feb 2021CPOL5 min read 37.4K   5.3K  
A custom drawn keyboard usercontrol and TextBox with keyboard popup
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.

Image 1

Image 2

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.

C#
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.

C#
... 
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.

C#
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:

C#
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:

C#
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.

C#
SendKeys.Send("a"); //letter 'a' pressed 
SendKeys.Send("~"); //symbol '~' pressed

To send such symbols as '{', '}', '+, '^', '%', '~', '(', ')', it is necessary to use curly braces.

C#
SendKeys.Send("{%}"); //symbol '%' pressed
SendKeys.Send("{+}"); //symbol '+' pressed

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:

C#
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.

C#
SendKeys.Send("^c");    // Ctrl + C
SendKeys.Send("^%s");   // Ctrl + Alt + S
SendKeys.Send("%{F4}"); // Alt + 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.

C#
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)
        {
            // raises KeyUp() event 
            case WM_KEYUP:
                if (KeyUp != null)
                {
                    KeyUp(m.HWnd);
                }
                break;

            // raises MouseDown() event
            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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)