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.

66 thoughts on “Silverlight 2 (beta1): International (Non-US) TextBox

  1. Hi Chris,

    Thanks for your reply.

    What you suggested worked. However it is not working again since the modifications you suggested.

    I have created a project with two textboxes, one original and one of the yours.

    In mac, the original textbox works with accents! That’s very strange! Why it doesn’t work on windows as well?

    Well, it seems that make the textbox keydown e.Handled = false on ctrl + c or ctrl + v isnt enough to make it work on make.

    Maybe i should need to pass anything more that i don’t know.

    Or maybe i could just override everything when i’m on mac, which i don’t know how to do.

    Any tips?

    Nuno

  2. >Or maybe i could just override everything when i’m on mac, which i don’t know how to do.

    I don’t think that silverlight allows you to determine on which platform you are running. Also this is one of the goals silverlight has, to be platform independent.

    Wait for the final version…

  3. Well of course you can use javascript to detect the “safari” browser and then use the normal textbox instead. As my textbox is derived from “TextBox” there is no problem in implementing this switch.

    I think you can access javascript natively by using something that ends with “Browser.Windows.evaluate()”…

    regards
    chris

  4. Hi Chris,
    I have found a way of getting user agent string. This way i can get platform.

    Now i need you help to make the hack for the switch.

    This is what i have done:

    void ExtendedTextBox_KeyDown(object sender, KeyEventArgs e)
    {

    try
    {

    if (Page.platform == Platform.Windows)
    {
    e.Handled = true;
    String Value = Keyboard.HandleKeyDown(sender, e);

    ….

    }
    else
    {
    e.Handled = false;
    }
    }
    catch
    {
    }
    }

    It works but something is wrong because, now, the text is written this way. For example, if i write “this” the result will be

    “siht”

    Now i need to put all togheter in order to have it working on mac, windows and with ctrl-c and ctrl-v working.

    Which HandleKeyDown should i mess with? In which class?

    Its almost there! :)

    Thanks for your time and patience!

    Best regards,

    Nuno

  5. If the platform is Mac you should skip the whole constructor… So that the TextBox is just a normal TextBox…

  6. That way doesnt work in mac. Nothing.

    This is what i have done in the constructor

    if (Page.platform == Platform.Windows)
    {
    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();
    }
    }

    Any thing missing?

    Thx,

    Nuno

  7. >In mac, the original textbox works with accents!

    So I don’t see the problem…

    If you don’t execute the constructor in mac, my textbox is simply a usual textbox, so it should work!

  8. Chris,

    Sorry, but i don’t know any other way of using your textbox without manually instanciate a variable of InternationTextBox type, initialize it with the constructor and manually add it to the UI like this:

    emailTextBox = new InternationalTextBox();
    emailTextBox.Keyboard = new Portuguese_Keyboard();
    emailTextBox.AcceptsReturn = true;
    emailTextBox.Width = 150;
    emailTextBox.Height = 110;
    emailTextBox.Margin = new Thickness(5, 5, 0, 5);
    emailTextBox.Padding = new Thickness(0, 0, 0, 0);
    emailTextBox.HorizontalAlignment = HorizontalAlignment.Left;
    emailTextBox.VerticalAlignment = VerticalAlignment.Bottom;
    emailTextBox.BorderThickness = new Thickness(0);
    emailTextBox.FontSize = 10;

    LayoutRoot.Children.Add(emailTextBox);

    I have already tried to instanciate it in xaml without any success. That would be wonderfull for me as i could have much less code.

    I tried like this, but silverlight explodes and nothing appears:

    xmlns:sda=”clr-namespace:SDA.SL.UI”

    So, what i understood from your tip was to avoid everything in the InternationTextBox constructor while in mac.

    Can you please be more specific? As i’m not understanding you.

    Thank you,

    Best regards,

    Nuno

  9. if(IsMac)
    {
    emailTextBox = new TextBox();
    }
    else
    {
    emailTextBox = new InternationalTextBox();
    }

    You can make initialization short by using constructors with more parameters like that:

    emailTextBox = new InternationalTextBox(true, 150, 110, …);

    For XAML you could try to derive a component from “UserControl” and provide the above switch in the constructor, maintaining a “private TextBox Surface;” member which actuallly represents the content of your user control. Then just export all the member you need by prototyping them like:

    Color BackgroundColor
    {
    get{return Surface.BackgroundColor;}
    set[Surface.BackgroundColor = value;}
    }

  10. Hi Chris,

    I have done this:

    public InternationalTextBox()
    {
    if (System.Environment.OSVersion.Platform != PlatformID.MacOSX)
    {
    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();
    }
    }
    }

    Basicly its the same thing i tried the first time. It’s working. maybe there was some gap in my testing.

    I prefer this way because it will not implicate breaking changes in the rest of the code.

    Thank you for your patient and help.

    Best regards,

    Nuno

  11. I am happy that it works now…

    Well you changed a significiant part! Now you check whether the OS is NOT mac whereas you previously checked whether the OS is windows.

    So I assume that your OS determination provided wrong results…

    regards
    chris

  12. Hi,

    I detect a bug in InternationalTextBox class:

    void ExtendedTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
    if (m_SelectionIndex < 0)
    SelectionStart = 0;
    else
    SelectionStart = m_SelectionIndex;
    }

    On TextChanged methos we have to control if m_SelectionIndex is less than zero.
    Sometimes fail it, if don’t control.

    For Example. Write in TextBox “Hi, my name is Jose” and with the mouse put the cursor in comma and Back(remove) “Hi,”, just then press DELETE and fail.

    Regards!!!

  13. Hi,

    with this code I achieve that works ctrol+c ctrol+v and ctrol+x:

    void ExtendedTextBox_KeyDown(object sender, KeyEventArgs e)
    {
    e.Handled = true;

    String Value = Keyboard.HandleKeyDown(sender, e);

    //if (AcceptTabs && (e.Key == Key.Tab))
    //{
    // Value = “\t”; // a plain tab is not displayed correctly, so we use multiple spaces…

    // Focus();
    //}

    //Si es una tecla especial (Por aqui tb pasan las de los acentos)
    if (Value.Length == 0)
    {
    //Switch es la forma más eficiente de hacer esto
    switch (e.Key)
    {
    case Key.Delete:
    {
    e.Handled = false;
    break;
    }
    //Inicio
    case Key.Home:
    {
    e.Handled = false;
    break;
    }
    case Key.PageDown:
    {
    e.Handled = false;
    break;
    }
    case Key.PageUp:
    {
    e.Handled = false;
    break;
    }
    case Key.End:
    {
    e.Handled = false;
    break;
    }
    case Key.Left:
    {
    e.Handled = false;
    break;
    }
    case Key.Right:
    {
    e.Handled = false;
    break;
    }
    case Key.Down:
    {
    e.Handled = false;
    break;
    }
    case Key.Up:
    {
    e.Handled = false;
    break;
    }
    //Hay que poner esto para que se lancen los eventos del intro
    case Key.Enter:
    {
    e.Handled = false;
    break;
    }
    case Key.Back:
    {
    e.Handled = false;
    break;
    }
    case Key.Insert:
    {
    e.Handled = false;
    break;
    }
    case Key.Escape:
    {
    e.Handled = false;
    break;
    }
    //Necesario
    case Key.Space:
    {
    e.Handled = false;
    break;
    }
    //Necesario para que funcione el TAB
    case Key.Tab:
    {
    e.Handled = false;
    break;
    }
    //Para Ctrl+C Ctrol+V Ctrol+X
    //case Key.Ctrl:
    // {
    // e.Handled = false;
    // break;
    // }
    case Key.C:
    {
    e.Handled = false;
    break;
    }
    case Key.X:
    {
    e.Handled = false;
    break;
    }
    case Key.V:
    {
    e.Handled = false;
    break;
    }

    }

    if ((e.Key == Key.Back) && (m_SelectionLength < 1))
    m_SelectionIndex–;

    if (IsPassword)
    {
    // handle selection removal of more than one character per time…
    if (Text.Length < m_Protected.Length)
    {
    m_Protected = m_Protected.Substring(0, m_SelectionIndex) +
    m_Protected.Substring(m_SelectionIndex + Math.Max(1, m_SelectionLength));

    m_SelectionIndex = Math.Min(m_SelectionIndex, Text.Length);
    m_SelectionLength = 0;
    }
    }

    return;
    }

    UpdateText(Value);
    }

    Practically, i have do the next:

    if (Value == “”) and (Key == V) is because the key “V” don’t produce values and is because any other key is pressed.
    In this case, e.Handled = false.
    And copy, cut and paste perfectly.

    Only there are any bug, that the cursor don’t go to the final of the pasted text, it stay at start of the text pasted.

    Any suggest Chris??

    Thanks

  14. Hi, All
    I am using this international text box in my app…….
    – I am not able to use delete key when if i select with shift key pressed and then delete key presses not deleting ……
    – Not working on mac and if i want that text box to be used for password …..

    Will any one suggest me ……. its urgent……

    Thanx in advance……..

  15. Consequently, the combination of wireless functionality and
    genuine-time charging tends to make this headset a best remedy for long duration gaming sessions.

Leave a Reply

Your email address will not be published. Required fields are marked *