Like I mentioned in this post a while back, I came across a dictionary web service called Aonaware that serves up word definitions from various dictionaries and is really easy to use. The services page on their website, http://services.aonaware.com/DictService/DictService.asmx, lists all the operations that are supported by the dictionary service. Here they are,
Word Dictionary Web Service
The following operations are supported. For a formal definition, please review the Service Description.
Define
Define given word, returning definitions from all dictionaries
DefineInDict
Define given word, returning definitions from specified dictionary
DictionaryInfo
Show information about the specified dictionary
DictionaryList
Returns a list of available dictionaries
DictionaryListExtended
Returns a list of advanced dictionaries (e.g. translating dictionaries)
Match
Look for matching words in all dictionaries using the given strategy
MatchInDict
Look for matching words in the specified dictionary using the given strategy
ServerInfo
Show remote server information
StrategyList
Return list of all available strategies on the server
Follow the links above to get more information on each API.
In this post we will be building a simple windows phone 7 client which uses this service to get word definitions for words entered by the user. The application will also allow the user to select a dictionary from all the available ones and look up the word definition in that dictionary. So of all the apis above we will be using only two, DictionaryList() to get a list of all supported dictionaries and DefineInDict() to get the word definition from a particular dictionary.
Before we get started, a note to you all; I would have liked to implement this application using concepts from data binding, item templates, data templates etc. I have a basic understanding of what they are but, being a beginner, I am not very comfortable with those topics yet so I didn’t use them. I thought I’ll get this version out of the way and maybe in the next version I could give those a try.
A somewhat scary mock-up of the what the final application will look like,
Select Dictionary is a list picker control from the silverlight toolkit (you need to download and install the toolkit if you haven’t already). Below it is a textbox where the user can enter words to look up and a button beside it to fetch the word definition when clicked. Finally we have a textblock which occupies the remaining area and displays the word definition from the selected dictionary.
Create a silverlight application for windows phone 7, AonawareDictionaryClient, and add references to the silverlight toolkit and the web service. From the solution explorer right on References and select Microsoft.Phone.Controls.Toolkit from under the .NET tab,
Next, add a reference to the web service. Again right click on References and this time select Add Service Reference In the resulting dialog paste the service url in the Address field and press go, (url –> http://services.aonaware.com/DictService/DictService.asmx)
once the service is discovered, provide a name for the NameSpace, in this case I’ve called it AonawareDictionaryService. Press OK. You can now use the classes and functions that are generated in the AonawareDictionaryClient.AonawareDictionaryService namespace.
Let’s get the UI done now. In MainPage.xaml add a namespace declaration to use the toolkit controls,
xmlns:toolkit="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit"
the content of LayoutRoot is changed as follows, (sorry, no syntax highlighting in this post)
<StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,5,0,5">
<TextBlock x:Name="ApplicationTitle" Text="AONAWARE DICTIONARY CLIENT" Style="{StaticResource PhoneTextNormalStyle}"/>
<!--<TextBlock x:Name="PageTitle" Text="page name" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>-->
</StackPanel>
<!--ContentPanel - place additional content here-->
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<toolkit:ListPicker Grid.Column="1" x:Name="listPickerDictionaryList"
Header="Select Dictionary :">
</toolkit:ListPicker>
<Grid Grid.Row="1" Margin="0,5,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox x:Name="txtboxInputWord" Grid.Column="0" GotFocus="OnTextboxInputWordGotFocus" />
<Button x:Name="btnGo" Grid.Column="1" Click="OnButtonGoClick" >
<Button.Content>
<Image Source="/images/button-go.png"/>
</Button.Content>
</Button>
</Grid>
<ScrollViewer Grid.Row="2" x:Name="scrollViewer">
<TextBlock Margin="12,5,12,5" x:Name="txtBlockWordMeaning" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" TextWrapping="Wrap"
FontSize="26" />
</ScrollViewer>
</Grid>
I have commented out the PageTitle as it occupies too much valuable space, and the ContentPanel is changed to contain three rows. First row contains the list picker control, second row contains the textbox and the button, and the third row contains a textblock within a scroll viewer.
The designer will now be showing the final ui,
Now go to MainPage.xaml.cs, and add the following namespace declarations,
using Microsoft.Phone.Controls;
using AonawareDictionaryClient.AonawareDictionaryService;
using System.IO.IsolatedStorage;
A class called DictServiceSoapClient would have been created for you in the background when you added a reference to the web service. This class functions as a wrapper to the services exported by the web service. All the web service functions that we saw at the start can be access through this class, or more precisely through an object of this class.
Create a data member of type DictServiceSoapClient in the Mainpage class, and a function which initializes it,
DictServiceSoapClient DictSvcClient = null;
private DictServiceSoapClient GetDictServiceSoapClient()
{
if (null == DictSvcClient)
{
DictSvcClient = new DictServiceSoapClient();
}
return DictSvcClient;
}
We have two major tasks remaining. First, when the application loads we need to populate the list picker with all the supported dictionaries and second, when the user enters a word and clicks on the arrow button we need to fetch the word’s meaning.
Populating the List Picker
In the OnNavigatingTo event of the MainPage, we call the DictionaryList() api. This can also be done in the OnLoading event handler of the MainPage; not sure if one has an advantage over the other. Here’s the code for OnNavigatedTo,
protected override void OnNavigatedTo(System.Windows.Navigation.NavigationEventArgs e)
{
DictServiceSoapClient client = GetDictServiceSoapClient();
client.DictionaryListCompleted += new EventHandler<DictionaryListCompletedEventArgs>(OnGetDictionaryListCompleted);
client.DictionaryListAsync();
base.OnNavigatedTo(e);
}
Windows Phone 7 supports only async calls to web services. When we added a reference to the dictionary service, asynchronous versions of all the functions were generated automatically. So in the above function we register a handler to the DictionaryListCompleted event which will occur when the call to DictionaryList() gets a response from the server. Then we call the DictionaryListAsynch() function which is the async version of the DictionaryList() api. The result of this api will be sent to the handler OnGetDictionaryListCompleted(),
void OnGetDictionaryListCompleted(object sender, DictionaryListCompletedEventArgs e)
{
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
Dictionary[] listOfDictionaries;
if (e.Error == null)
{
listOfDictionaries = e.Result;
PopulateListPicker(listOfDictionaries, settings);
}
else if (settings.Contains("SavedDictionaryList"))
{
listOfDictionaries = settings["SavedDictionaryList"] as Dictionary[];
PopulateListPicker(listOfDictionaries, settings);
}
else
{
MessageBoxResult res = MessageBox.Show("An error occured while retrieving dictionary list, do you want to try again?", "Error", MessageBoxButton.OKCancel);
if (MessageBoxResult.OK == res)
{
GetDictServiceSoapClient().DictionaryListAsync();
}
}
settings.Save();
}
I have used IsolatedStorageSettings to store a few things; the entire dictionary list and the dictionary that is selected when the user exits the application, so that the next time when the user starts the application the current dictionary is set to the last selected value. First we check if the api returned any error, if the error object is null e.Result will contain the list (actually array) of Dictionary type objects. If there was an error, we check the isolated storage settings to see if there is a dictionary list stored from a previous instance of the application and if so, we populate the list picker based on this saved list. Note that in this case there are chances that the dictionary list might be out of date if there have been changes on the server. Finally, if none of these cases are true, we display an error message to the user and try to fetch the list again.
PopulateListPicker() is passed the array of Dictionary objects and the settings object as well,
void PopulateListPicker(Dictionary[] listOfDictionaries, IsolatedStorageSettings settings)
{
listPickerDictionaryList.Items.Clear();
foreach (Dictionary dictionary in listOfDictionaries)
{
listPickerDictionaryList.Items.Add(dictionary.Name);
}
settings["SavedDictionaryList"] = listOfDictionaries;
string savedDictionaryName;
if (settings.Contains("SavedDictionary"))
{
savedDictionaryName = settings["SavedDictionary"] as string;
}
else
{
savedDictionaryName = "WordNet (r) 2.0"; //default dictionary, wordnet
}
foreach (string dictName in listPickerDictionaryList.Items)
{
if (dictName == savedDictionaryName)
{
listPickerDictionaryList.SelectedItem = dictName;
break;
}
}
settings["SavedDictionary"] = listPickerDictionaryList.SelectedItem as string;
}
We first clear all the items from the list picker, add the dictionary names from the array and then create a key in the settings called SavedDictionaryList and store the dictionary list in it. We then check if there is saved dictionary available from a previous instance, if there is, we set it as the selected item in the list picker. And if not, we set “WordNet ® 2.0” as the default dictionary. Before returning, we save the selected dictionary in the “SavedDictionary” key of the isolated storage settings.
Fetching word definitions
Getting this part done is very similar to the above code. We get the input word from the textbox, call into DefineInDictAsync() to fetch the definition and when DefineInDictAsync completes, we get the result and display it in the textblock. Here is the handler for the button click,
private void OnButtonGoClick(object sender, RoutedEventArgs e)
{
txtBlockWordMeaning.Text = "Please wait..";
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
if (txtboxInputWord.Text.Trim().Length <= 0)
{
MessageBox.Show("Please enter a word in the textbox and press 'Go'");
}
else
{
Dictionary[] listOfDictionaries = settings["SavedDictionaryList"] as Dictionary[];
string selectedDictionary = listPickerDictionaryList.SelectedItem.ToString();
string dictId = "wn"; //default dictionary is wordnet (wn is the dict id)
foreach (Dictionary dict in listOfDictionaries)
{
if (dict.Name == selectedDictionary)
{
dictId = dict.Id;
break;
}
}
DictServiceSoapClient client = GetDictServiceSoapClient();
client.DefineInDictCompleted += new EventHandler<DefineInDictCompletedEventArgs>(OnDefineInDictCompleted);
client.DefineInDictAsync(dictId, txtboxInputWord.Text.Trim());
}
}
We validate the input and then select the dictionary id based on the currently selected dictionary. We need the dictionary id because the api DefineInDict() expects the dictionary identifier and not the dictionary name. We could very well have stored the dictionary id in isolated storage settings too. Again, same as before, we register a event handler for the DefineInDictCompleted event and call the DefineInDictAsync() method passing in the dictionary id and the input word.
void OnDefineInDictCompleted(object sender, DefineInDictCompletedEventArgs e)
{
WordDefinition wd = e.Result;
scrollViewer.ScrollToVerticalOffset(0.0f);
if (wd.Definitions.Length == 0)
{
txtBlockWordMeaning.Text = String.Format("No definitions were found for '{0}' in '{1}'", txtboxInputWord.Text.Trim(), listPickerDictionaryList.SelectedItem.ToString().Trim());
}
else
{
foreach (Definition def in wd.Definitions)
{
string str = def.WordDefinition;
str = str.Replace(" ", " "); //some formatting
txtBlockWordMeaning.Text = str;
}
}
}
When the api completes, e.Result will contain a WordDefnition object. This class is also generated in the background while adding the service reference. We check the word definitions within this class to see if any results were returned, if not, we display a message to the user in the textblock. If a definition was found the text on the textblock is set to display the definition of the word.
Adding final touches, we now need to save the current dictionary when the application exits. A small but useful thing is selecting the entire word in the input textbox when the user selects it. This makes sure that if the user has looked up a definition for a really long word, he doesn’t have to press ‘clear’ too many times to enter the next word,
protected override void OnNavigatingFrom(System.Windows.Navigation.NavigatingCancelEventArgs e)
{
IsolatedStorageSettings settings = IsolatedStorageSettings.ApplicationSettings;
settings["SavedDictionary"] = listPickerDictionaryList.SelectedItem as string;
settings.Save();
base.OnNavigatingFrom(e);
}
private void OnTextboxInputWordGotFocus(object sender, RoutedEventArgs e)
{
TextBox txtbox = sender as TextBox;
if (txtbox.Text.Trim().Length > 0)
{
txtbox.SelectionStart = 0;
txtbox.SelectionLength = txtbox.Text.Length;
}
}
OnNavigatingFrom() is called whenever you navigate away from the MainPage, since our application contains only one page that would mean that it is exiting.
I leave you with a short video of the application in action, but before that if you have any suggestions on how to make the code better and improve it please do leave a comment.
Until next time…