This article is compatible with the latest version of Silverlight for Windows Phone 7.
Last year, just for the Christmas holidays I bought my Windows Phone 7 device. I bought it for two reasons. First, it was Christmas and it was the perfect time to make a present for myself . Second, I wanted Windows Phone 7 because as a .NET developer having experience with Silverlight/WPF, I will be able to develop WP7 applications without spending any significant time for learning the WP7 programming model.
As an end user, I won’t comment the disadvantages of Windows Phone 7. It is normal, though we are talking for a system that is only few months old. However, as a developer I should “congratulate” Microsoft for their “great” politics. I am just wondering how is it possible to release the phone in around 30 countries, but at the same time their marketplace to be available in only 15 of them. I really cannot make it out. The fact that my Bulgarian credit card cannot get verified in the marketplace and I am not able to buy applications (some of the games are really awesome) is not such a pain. But the one thing I am really frustrated from is the fact that I am not allowed to create a developer account in the AppHub. Why? For the same reason, I am from Bulgaria, and the marketplace (AppHub) is not officially available here, and no one knows when it will be. As a result I am not able to even deploy my applications on the phone just for debugging.
Ok, enough lyrical digressions. Let’s get down to the brass tacks. My first task was to create some simple application in order to get a general idea about the development process. I had a few ideas in my mind. Initially, I was thinking of creating a simple game. However, a few friends of mine also bought WP7 devices. We had a discussion about the disadvantages of WP7. And they are really disappointed by the fact, that there isn’t a Bulgarian keyboard for WP7. That’s why I decided to try to create a custom Bulgarian keyboard for WP7. One last thing, I strongly recommend to take a look at the Andrea Boschin’s seriers. He discusses deeply the various aspects of the WP7 programming.
Soft Input Panel (SIP)
Let’s take a quick look at how the user enters information on a Windows Phone that is done via an on-screen keyboard, also known as a Soft Input Panel (SIP). Some devices offer a physical keyboard, too. For devices with a keyboard that slides away, the SIP will be displayed when the keyboard is closed. When the keyboard is opened, the SIP will be minimized.
Here is the moment to share a very interesting tip about the SIP and the Windows Phone emulator. Something I learnt a few days ago. I was wondering if it’s possible to use the keyboard on the host computer to enter text in the emulator, instead of the SIP. Well, it seems it is possible. To do that you have to press the Pause (Break) button. That will toggle between the keyboard on the host computer and the SIP.
There are some important issues you have to consider when working with the SIP. The first thing is that the SIP could not be overridden by both application developers and hardware manufacturers. On a WP7 the SIP experience will always be the same. The other thing is that you no longer get to control the display or hiding of the SIP. The SIP is automatically displayed or hidden when a control that requires text input receives or loses focus. As there are no events raised when the SIP is expanded or collapsed, you have to do some of this management yourself. The SIP is displayed when a TextBox gets focus and is hidden when the TextBox loses focus. Unfortunately, there is no way to know definitively if the SIP is visible as there is no explicit event or property indicating the state of the SIP. Although you could do your own tracking based on the TextBox focus, if the device has a physical keyboard, the SIP will not be displayed, making it impossible to know whether the SIP is being displayed. Also you should keep in mind that on devices that have a physical keyboard, the SIP will not be displayed when the keyboard is expanded. Or in other words it is currently impossible to modify the behavior of the SIP. The most commonly used text entry control is the TextBox (no surprise ). Although you cannot override the SIP, you can specify the InputScope of the TextBox. For more information, you could check out MSDN.
The requirements
Obviously, we don’t have any other options except to create an application that simulates the SIP. An application that simulates the SIP will be totally useless unless we add some additional tasks, such as sending email, writing messages, searching in Bing. Finally, our custom keyboard should look like the snapshot below:
The sentence entered in the emulator, translated in English means “I want to eat” . The keyboard should offer the standard Bulgarian 30 letters, system keys (Shift, Space, Enter, Backspace), additional widely used symbols (see the snapshot below), and various tasks (such as email task, search in Bing and send email).
The email task
Let’s take a look at the different tasks that will be used in the application. Windows Phone 7 supports traditional email services such as POP3, IMAP, and SMTP, as well as synchronizing with Exchange Server. The e-mail tasks allow your application to compose emails and interact with users’ contacts to save and retrieve email addresses. For example, sending an email from your application is possible using the EmailComposeTask class. You can generate a pre-populated email by setting the To, Subject , and Body properties.
public static void SendEmail( string body )
{
EmailComposeTask emailComposeTask = new EmailComposeTask
{
Body = body
};
emailComposeTask.Show();
}
Search in Bing task
The SearchTask class provides you with a way to invoke a Bing Web Search for a particular search string you supply through the SearchQuery property.
public static void BingSearch( string searchQuery )
{
SearchTask searchTask = new SearchTask
{
SearchQuery = searchQuery
};
searchTask.Show();
}
Phone and SMS tasks
Through the PhoneNumberChooserTask and SmsComposeTask classes you can request that the user selects phone numbers and send test messages. To allow user to select a phone number from the contact list on the phone, you just need to create a new instance of the PhoneNumberChooserTask class and invoke its Show method. Additionally you need to attach to the Completed event. Invoking the Show method will open the selector application.
When a contact is selected, the selector application closes and the Completed event is raised. The selected phone number is wrapped in the PhoneNumber property of the passed event arguments.
public static void SendSms( string body )
{
PhoneNumberChooserTask phoneChooseTask = new PhoneNumberChooserTask();
EventHandler<PhoneNumberResult> temp = ( o, result ) =>
{
if ( result.TaskResult == TaskResult.Cancel )
{
return;
}
SmsComposeTask smsComposeTask = new SmsComposeTask
{
To = result.PhoneNumber,
Body = body
};
smsComposeTask.Show();
};
phoneChooseTask.Completed -= temp;
phoneChooseTask.Completed += temp;
phoneChooseTask.Show();
}
Designing the keyboard
Let’s take a quick look at the structure of the application. Initially, I was planning to create the application as simple as possible. However, I later decided to put some efforts into the design. I wanted the created keyboard to be reusable, easily configurable and extensible. Well, you can’t achieve this without introducing several interfaces and abstract classes . The diagram below shows the heart of the application – the abstract Keyboard class. It exposes two protected abstract methods – GenerateContext and GenerateSecondaryContext.
The idea here is that the keys you see (the Bulgarian letters) are not predefined. Instead, each concrete keyboard implementation provides its own keyboard context. Or in other words each derived type determines what will be shown on the screen. The only predefined keys are the system keys (space, backspace, enter). However, there are properties controlling their visibility, so you can hide them. Here is how the Bulgarian keyboard implementation looks like.
public class BulgarianKeyboard : Keyboard.Controls.Keyboard
{
protected override KeyboardContext GenerateKeyboardContext()
{
KeyboardContext keyboardContext = new KeyboardContext();
keyboardContext.Rows = 3;
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "я" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "в" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "е" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "р" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "т" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "ъ" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "у" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "и" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "о" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "п" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "а" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "с" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "д" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "ф" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "г" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "х" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "й" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "к" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "л" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "ч" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "з" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "ь" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "ц" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "ж" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "б" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "н" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "м" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "ш" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "щ" ) );
return keyboardContext;
}
protected override KeyboardContext GenerateSecondaryKeyboardContext()
{
return new DefaultSecondaryKeyboardContext();
}
}
The idea of the Expression class is to allow you to translate the letter or to allow you to modify what will be shown on the screen. For example, when you press the “Q” key, instead of showing “Q” on the screen you may want to show its ASCII code or something else. That is the target of the Expression class. As you may guess, the DefaultExpression class just returns the original symbol.
public sealed class DefaultExpression : Expression
{
public override string Interpret( string symbol )
{
return symbol;
}
}
The output screen control is also abstracted; it is not just a simple TextBlock.
We have the IOutputControl interface providing methods for modifying the displayed text. So if you like you can replace the default screen control with your own implementation. It is enough just to implement the IOutputControl interface. Finally, the last key member is the KeyboardScreenControl. This is the glue between the Keyboard and the IOutputControl.
So let’s put everything in practice.
<Grid x:Name="LayoutRoot">
<Grid.Background>
<ImageBrush ImageSource="/WP7.BulgarianKeyboard.Client;component/Background.jpg" />
</Grid.Background>
<keyboardControls:KeyboardScreenControl x:Name="Keyboard">
<keyboardControls:KeyboardScreenControl.Keyboard>
<bgKeyboard:BulgarianKeyboard />
</keyboardControls:KeyboardScreenControl.Keyboard>
</keyboardControls:KeyboardScreenControl>
</Grid>
And our custom keyboard application is ready.
Creating your own keyboard
You may note that there is a fourth button that I haven’t mentioned so far.
Here in Bulgaria we have the common practice to combine Bulgarian letters with English when writing emails, sms, forums etc. So I wanted to provide better experience to my compatriots. I wanted to create an EN keyboard as well, and give them the possibility to switch between the keyboard runtime. As I already mentioned it is enough to implement the abstract Keyboard class.
public class LatinKeyboard : Keyboard.Controls.Keyboard
{
protected override KeyboardContext GenerateKeyboardContext()
{
KeyboardContext keyboardContext = new KeyboardContext();
keyboardContext.Rows = 3;
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "q" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "w" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "e" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "r" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "t" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "y" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "u" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "i" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "o" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 0, "p" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "a" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "s" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "d" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "f" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "g" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "h" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "j" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "k" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 1, "l" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "z" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "x" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "c" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "v" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "b" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "n" ) );
keyboardContext.KeyboardMapping.Add( new KeyMapping( 2, "m" ) );
return keyboardContext;
}
protected override KeyboardContext GenerateSecondaryKeyboardContext()
{
return new DefaultSecondaryKeyboardContext();
}
}
Then runtime, when the click event of the button is raised:
private void ChangeKeyboardButtonClick( object sender, EventArgs e )
{
ApplicationBarIconButton changeKeyboardButton = sender as ApplicationBarIconButton;
if(changeKeyboardButton == null)
{
return;
}
if ( ReferenceEquals( bgKeyboard, this.Keyboard.Keyboard ) )
{
this.Keyboard.Keyboard = this.latinKeyboard;
changeKeyboardButton.Text = "bg";
}
else
{
this.Keyboard.Keyboard = this.bgKeyboard;
changeKeyboardButton.Text = "en";
}
}
What’s next
So that’s it. You could try to create your own keyboard. Or probably you may want to provide your own screen implementation. For me, the only option is to wait for Microsoft to expand their marketplace or maybe to find some workaround for creating an account in the AppHub. Meanwhile, I’ll continue to develop for WP7 in my free time. If you have any comments, feedback, suggestions or whatever it may be, do not hesitate to share with us.
Download Source Code
About the author
An MCPD and MCTS, Pencho Popadiyn is one of SilverlightShow team members with most Silverlight experience. He is actively involved in the development of complex modules for SilverlightShow, and a great deal of his experience is shared through SilverlightShow articles (check out Pencho's posts). He is also programming WP7 in his free time.
Pencho is a keen chess player, enjoys relaxation through jogging or nice music.