Silverlight 2 (beta1): International (Non-US) TextBox

by Christoph Husse

This is a guest post by Christoph Husse. This is a workaround for the issue #7 of Silverlight 2 (beta1) known issues . Thanks a lot, Chirs!
~ Michael Sync

Download ~

License : MIT-License

1. Introduction

… I was writing a simple logon screen and without any prejudges I tried to enter my E-Mail-Address with my German keyboard… Oh damn what’s this? Silverlight didn’t recognized the “@” char. After some googling I found out why. Silverlight does currently support US-keyboards only. Microsoft, how do you explain this? I mean it’s not hard to support all keyboards; instead it is hard to not support them. Windows by default automatically translates all keyboard codes depending on the user selected keyboard. So it’s simply unknown why Microsoft has dropped support for all other keyboards… Even if portability would be an (cheap) explanation, they didn’t care much about Mono till now, why should they do for Moonlight? I think portability is one thing, to exclude billions of people from using Silverlight sites is another one…

With this little story in mind I wrote a little TextBox supporting my German keyboard and the password chars. For your convenience I decided to use an abstract keyboard class, so any other keyboard may be implemented without much effort. The following shows a simple InternationalTextBox in action:


InternationalTextBox MyBox = new InternationalTextBox();

public Page()
{
InitializeComponent();

MyBox.Keyboard = new German_Keyboard();
MyBox.AcceptTabs = true;
MyBox.Width = 200;
MyBox.Height = 30;
MyBox.Margin = new Thickness(100, 150, 0, 0);
MyBox.HorizontalAlignment = HorizontalAlignment.Left;
MyBox.VerticalAlignment = VerticalAlignment.Top;

LayoutRoot.Children.Add(MyBox);

}

1.1 Support for any keyboard

The main feature is that any keyboard may be used with this component. Currently only the US- and German-Keyboard are supported. But if you look at the source code of those two classes “German_Keyboard” and “US_Keyboard” you will find that it is pretty easy to add your custom keyboard.

You may set the keyboard in the “InternationalTextBox.Keyboard” property. A value of “null” will use the default US-keyboard.

I encourage anyone who got a keyboard not already covered, to write a proper keyboard class and publish it. So we all can take the full advantage of support for any common keyboard layout.

I was really happy to discover, that “Ctrl+v/c/z/y” will work well on my “InternationalTextBox” and even for free…

1.2 Keyboard lists

To work with all of those keyboards, you may enumerate them through “TextBoxKeyboard.Keyboards”. Each of its entries will publish a “Description” that may be shown in a combo-box to the user and a “CreateInstance()”. The latter will allow you to get an object for every keyboard the user selects. You can then directly apply this object to the “InternationalTextBox.Keyboard” property without knowing which keyboard class you are really using…

1.3 Support for typing chars with “Ctrl + [Code]”

Even if windows uses “Alt + [Code]” for the same thing, “Alt” is reserved for the browser and can’t be used in this case. The following picture shows the use of “Ctrl + 9787” which renders a smiley icon…

I’m special.

You may use the “Ctrl” shortcut at any position and you may also use it to replace a text selection.

I think the exact rendering depends on the font selected for the TextBox! Different fonts may produce different symbols for the same control code.

1.4 Password chars

I decided to preset the password char to the one used by windows. You may enable or disable the password chars by setting “InternationalTextBox.IsPassword” to either true or false. Existing text will be converted to circles and circles will be converted to text depending on the current state to switch.

password.JPG

You may select a few circles with the mouse or by holding “Shift” while stepping through the chars with the arrow keys. It is supported to replace such a selection with a new char. The internal password string will be updated appropriate. Of course you may not use “Ctrl+c” for passwords and also “Ctrl+v/z/y” won’t work because they are intercepted by the textbox and would operate on circles only… But I think this is not an issue because passwords shouldn’t be in clipboard.

2 How to derive your own keyboard

Just create a class derived from “TextBoxKeyboard”.

At the beginning you should override the “Description” property and set it to the description used by windows for your keyboard…

Then you have to overload the “HandleKeyDown” method. This is the complicated part. The base class will take care over “Shift”, “Alt” and “Ctrl” states and also supports the direct character input using “Ctrl + ”. So your only duty is to translate the key code to a Char depending on the current keyboard state. To support special chars you may use so called key translation tables:


private KeyToChar[] Default = new KeyToChar[]
{
new KeyToChar() { Code = 220, Char='`'},
new KeyToChar() { Code = 219, Char='-'},
new KeyToChar() { Code = 221, Char='='},
new KeyToChar() { Code = 187, Char=']'},

The “Code” is just the native key code you may determine by setting a proper breakpoint and then pressing a proper key. The “Char” is the value you would expect to appear if the key is pressed. There are three tables. The “Default”-Table is active when none of “Shift”, “Ctrl” or “Alt” is pressed. The “Shifted”-Table is active when “IsShiftDown” is true. The “Alted”-Table is active when “IsCtrlDown” and “IsAtlDown” are true. This emulates the “Alt Gr”-key available on German keyboards for example.

If you use code as shown in the two built-in keyboards, you just have to fill those tables to support your keyboard.

Things will start getting weird if you have to do things like me with the German keyboard. For example if you press the “^” key, it is not written to the TextBox. Instead the keyboard “waits” for the next char. If it is a vocal, the resulting char is either one of “â, ê, û, î, ô”. All other inputs will generate “^[+char]”. But as you can see in the German keyboard class, this is also not very hard to handle…

Updated:

3. Remember user’s keyboard selection

If a user is visiting your site for the first time, he has to select a proper keyboard layout. I recommend to select the US-keyboard by default to be consistent with the current silverlight TextBox. Another option would be to “guess” the layout by interpreting the HTTP-Headers, especially the language. In general the user language will directly map to his keyboard layout. But of course, this is not always the case. I don’t know if Silverlight has any option to directly retrieve the user language. If not, then you have to implement this by using a server side script, and that’s definitely out of scope for my TextBox!

In the following I will just show how such a “remembering” of the user selection may work with Silverlight. You have to change the InternationalTextBox’ instance constructor code to the following. I have to admit that this doesn’t look so pretty…


public InternationalTextBox()
{
KeyDown += new KeyEventHandler(ExtendedTextBox_KeyDown);
KeyUp += new KeyEventHandler(ExtendedTextBox_KeyUp);
TextChanged += new TextChangedEventHandler(
ExtendedTextBox_TextChanged);
SelectionChanged += new RoutedEventHandler(
ExtendedTextBox_SelectionChanged);

try
{
IsolatedStorageFile File = IsolatedStorageFile.GetUserStoreForApplication();

IsolatedStorageFileStream Stream = File.OpenFile("InternationalTextBox.keyboard.layout", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

using (Stream)
{
BinaryReader Reader = new BinaryReader(Stream);
String Selection = Reader.ReadString();

for (int i = 0; i < TextBoxKeyboard.Keyboards.Length; i++)
{
if (TextBoxKeyboard.Keyboards[i].Description.CompareTo(Selection) == 0)
{
m_Keyboard = TextBoxKeyboard.Keyboards[i].CreateInstance();

break;
}
}
}
}
catch
{
m_Keyboard = new US_Keyboard();
}
}

And you have to change the InternationalTextBox’ Keyboard property setter to the following:


set
{
if (value == null)
value = new US_Keyboard();

// copy cap locks and so on...
value.Attach(m_Keyboard);

// register user char event...
value.OnUserChar += new UserCharEventHandler(value_OnUserChar);

m_Keyboard = value;

try
{
// keep this selection in "mind"
IsolatedStorageFile File = IsolatedStorageFile.GetUserStoreForApplication();

IsolatedStorageFileStream Stream = File.OpenFile("InternationalTextBox.keyboard.layout", FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);

using (Stream)
{
BinaryWriter Writer = new BinaryWriter(Stream);

Stream.SetLength(0);
Writer.Write((String)m_Keyboard.Description);
}
}
catch
{
}
}

I didn’t implement this directly because it’s just one of many options to realize “remembering”. The problem with this code is that every time you set the “Keyboard” property, the user’s selection will be overwritten. So you shouldn’t set this property directly, but you may use a ComboBox holding all the supported layouts. If the user explicitly changes the selection of this ComboBox you may also change the “Keyboard” property. I recommend implementing this ComboBox in a way the user is used to by Windows. So simply put a button with a keyboard icon beneath your TextBox. If the user clicks the button, a list containing all possible layouts should appear.

Another approach would be to check weather this file exists and contains a valid selection. If this is not the case you may display an exclusive screen only intended for layout setting. This allows you to force the user to select his keyboard layout before he can do anything on your site, maybe in style of the Windows Vista Account Control Screen.