Skip to main content Skip to footer

Multi-Language Spell-checking with C1RichTextBox

In the 2013 v3 release we added spell-checking to the C1RichTextBox control for WinRT XAML. Spell-checking requires a dictionary file to supply the list of words. To enable spell-checking and load a dictionary file from the application resources, the code required looks like this:


// declare new spell checker  
C1SpellChecker spellChecker = new C1SpellChecker();  
// load dictionary  
spellChecker.MainDictionary.Load(typeof(MainPage).GetTypeInfo().Assembly.GetManifestResourceStream("AppName.Resources.C1Spell_en-US.dct"));  
// enable spell checking on C1RichTextBox  
c1RichTextBox1.SpellChecker = spellChecker;  

RichTextBox_SpellChecking

Handling Multiple Dictionaries in One App

Loading a dictionary from embedded resources is fine for one dictionary, but what if you would like to expand the option for multiple languages? ComponentOne provides 22 international dictionaries free for you to download, edit and redistribute. For best practice in Windows Store apps, we can download dictionaries on the fly and store them in the application’s local folder. In this sample I demonstrate a scenario of allowing the user to switch among multiple dictionaries on the fly. The work flow goes like this:

  1. The user changes the dictionary from the application settings.
  2. The application code looks in the app’s local folder to find the dictionary.
  3. If found, the dictionary is loaded from local folder.
  4. If not found, the dictionary is downloaded from the Web and stored in the local folder for future runs.

So if the user prefers French, the dictionary only downloads one time and is stored in the local folder forever (until they uninstall the app). I also use application settings to store the last selected dictionary between runs of the application. This work flow is the most efficient, and therefore a recommended approach to supporting multiple dictionaries with your application. It is efficient because it results in the smallest possible size for your app and it requires the fewest calls to download the dictionary file. Otherwise you would (1) have to ship every dictionary with the app, bloating the size of your app, or (2) your app could be inefficient making multiple trips to download the same file each time, driving up the data cost. Of course, if accessing the internet is an issue for your users then your only option would be to ship all dictionaries with the app as embedded resources. Rather than show all the code necessary I encourage you to download the complete sample. I will only discuss the relevant parts of this topic below. Download RichTextBoxDictionaries sample I’ve declared an instance of C1SpellChecker at the application level so that it only must be changed in one location and will affect my entire application. Inside the OnLaunched event I initialize the spell checker and load the main dictionary from a class named SpellCheckingUtil.


sealed partial class App : Application  
{  
    public static C1SpellChecker SpellChecker;  
...  
    protected override void OnLaunched(LaunchActivatedEventArgs e)  
    {  
        if (SpellChecker == null)  
        {  
            SpellChecker = new C1SpellChecker();  
            SpellCheckingUtil.LoadMainDictionary();  
        }  
...  
    }  
}  

In the code-behind for my page I set the C1RichTextBox.SpellChecker = App.SpellChecker. When the application is launched it will load the dictionary from application settings, to restore what the user had last selected. If this is the very first run we simply provide a default, and in my case I have the default set to English. In fact, for English I decided to ship that dictionary as an embedded resource and only retrieve other languages from the Web. Here is the LoadMainDictionary method from the utility class.


public static async void LoadMainDictionary()  
{  
    var mainDictionary = App.SpellChecker.MainDictionary;  

    // set main dictionary  
    mainDictionary.Enabled = true;  
    string mainDictionaryCode = ApplicationSettings.MainDictionaryCode();  

    // for efficiency do not reload same dictionary  
    if (PreviousDictionaryCode.Equals(mainDictionaryCode))  
        return;  

    PreviousDictionaryCode = mainDictionaryCode;  

    // get dictionary object  
    SpellingDictionary dictionary = SpellCheckingUtil.GetSpellingDictionary(mainDictionaryCode);  

    if (dictionary != null)  
    {  
        App.ShowLoadingIndicator(true);  

        // if english, load from resources  
        if (dictionary.Code.Equals("en-US"))  
        {  
            Assembly asm = typeof(MainPage).GetTypeInfo().Assembly;  
            Stream stream = asm.GetManifestResourceStream("RichTextBoxDictionaries.Resources.C1Spell_en-US.dct");  

            await mainDictionary.LoadAsync(stream);  
        }  
        else  
        {  
            // locate foreign dictionary from local data storage  
            StorageFile file = null;  
            try  
            {  
                file = await ApplicationData.Current.LocalFolder.GetFileAsync(dictionary.FileName);  
            }  
            catch (Exception ex)  
            {  
                //file not found  
            }  

            if (file != null)  
            {  
                //file exists, load from storage  
                await mainDictionary.LoadAsync(await file.OpenStreamForReadAsync());  
            }  
            else  
            {  
                // file does not exist in storage  
                // create the empty file  
                file = await ApplicationData.Current.LocalFolder.CreateFileAsync(dictionary.FileName, CreationCollisionOption.ReplaceExisting);  
                // load file from the Web  
                HttpClient client = new HttpClient();  
                try  
                {  
                    var stream = await client.GetStreamAsync(new Uri(dictionary.Url, UriKind.Absolute));  

                    using (Stream outStream = Task.Run(() => file.OpenStreamForWriteAsync()).Result)  
                    {  
                        stream.CopyTo(outStream);  
                        await outStream.FlushAsync();  
                    }  

                    await mainDictionary.LoadAsync(await file.OpenStreamForReadAsync());  

                }  
                catch (Exception ex)  
                {  
                    file.DeleteAsync(StorageDeleteOption.PermanentDelete);  
                    ShowDialog("There was an error attempting to download the dictionary. Choose a different dictionary or try again later.", "Error", null);  
                }  
            }  
        }  
        App.ShowLoadingIndicator(false);  

    }  

    if (mainDictionary.State != C1.Xaml.SpellChecker.DictionaryState.Loaded)  
    {  
        await ShowDialog("The main dictionary failed to load.", "Warning", null);  
    }  
}  

There are many little things being done in this method for the best experience possible. I store the previously loaded dictionary code, so in case the user keeps selecting the same dictionary we don’t waste time reloading it. The ApplicationData class is used to manage files in the local folder. The work flow steps described above are implemented in this code bit by bit. The only exception is that the English (US) dictionary is handled differently and loaded from resources. After you run the sample you can actually look and see the files that the app has downloaded by looking in the local folder. How do you find the local folder? It’s always somewhere under your AppData directory. In this case I was able to find it by searching for *.dct since these dictionary files are unique enough. Working with File Explorer you can see exactly what’s being stored. RichTextBox_LocalFolder

Conclusion

The sample provided here shows a lot of interesting techniques that are too much for this blog post. It uses application settings, flyouts and local folder storage techniques to bring a multiple dictionary app to life in the most efficient way possible. Download RichTextBoxDictionaries sample

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus