Archive

Posts Tagged ‘InputLanguage’

Switching the Keyboard to Different Languages

November 21, 2009 1 comment

Handling different languages with different keyboard layouts within the same document has only recently become a common feature in text processing applications. However, since I frequently have to work with data in different languages, I wouldn’t like to miss this ability.

In WPF, the class responsible for managing different keyboard languages is called InputLanguageManager. It exposes an attached property, InputLanguage, that accepts a culture identifier and switches the keyboard to the defined layout for this language, if one is installed. Unfortunately, this only works when the control receives focus. So, if you are typing something in a TextBox and change the InputLanguage while you are typing, e.g. through a keyboard shortcut, this change doesn’t become active unless the keyboard focus leaves the TextBox and returns again.

Also, in principle you can define different languages for paragraphs in a RichTextBox by setting the InputLanguageManager.InputLanguage on the Paragraph elements in the RichTextBoxes FlowDocument, but the keyboard doesn’t automatically change to the appropriate layout when the selection enters a section in a different language. This feature is extremely useful, so I decided to create a Sticky Property which allows to change the keyboard language effective immediately, and a Sticky Behaviour that sets the current input language according to what is set in this attached property on the FrameworkContentElements inside a RichTextBox’s FlowDocument.

Changing the Keyboard Language at Runtime

The way the KeyboardLanguage property is implemented is a typical example for the Sticky Property design pattern. A Sticky Property is an attached property that has an OnPropertyChanged handler which does some useful work, so that the property has an effect aside from storing data with the object it is set on. In the case of the KeyboardLanguage property, this work would be looking up the available keyboard languages, checking whether one of them fits the value, and activating this keyboard layout.

Public Shared ReadOnly KeyboardLanguageProperty As DependencyProperty = DependencyProperty.RegisterAttached("KeyboardLanguage", GetType(String), GetType(Localization), New FrameworkPropertyMetadata(Nothing, System.Windows.FrameworkPropertyMetadataOptions.Inherits, AddressOf OnKeyboardLanguageChanged))
Public Shared Function GetKeyboardLanguage(ByVal d As DependencyObject) As String
    Return d.GetValue(KeyboardLanguageProperty)
End Function
Public Shared Sub SetKeyboardLanguage(ByVal d As DependencyObject, ByVal value As String)
    d.SetValue(KeyboardLanguageProperty, value)
End Sub
Public Shared Sub OnKeyboardLanguageChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    If TypeOf d Is FrameworkElement Then
        Dim l As System.Globalization.CultureInfo = FindKeyboardLanguage(e.NewValue)
        If String.IsNullOrEmpty(e.NewValue) Or l Is System.Globalization.CultureInfo.InvariantCulture Then
            d.ClearValue(InputLanguageManager.InputLanguageProperty)
            d.ClearValue(InputLanguageManager.RestoreInputLanguageProperty)
        Else
            d.SetValue(InputLanguageManager.InputLanguageProperty, l)
            d.SetValue(InputLanguageManager.RestoreInputLanguageProperty, True)
            InputLanguageManager.Current.CurrentInputLanguage = l
        End If
    End If
End Sub

Public Shared Function FindKeyboardLanguage(ByVal tag As String) As System.Globalization.CultureInfo
    Dim result As System.Globalization.CultureInfo = System.Globalization.CultureInfo.InvariantCulture
    If Not String.IsNullOrEmpty(tag) Then
        For Each c As System.Globalization.CultureInfo In InputLanguageManager.Current.AvailableInputLanguages
            If c.IetfLanguageTag.ToUpper.StartsWith(tag.ToUpper) Then
                result = c
            End If
            If c.IetfLanguageTag.ToUpper = tag.ToUpper Then
                Exit For
            End If
        Next
    End If
    Return result
End Function

 

This gives the added flexibility of supporting language-only IETF tags, so that you don’t have to know exactly which flavour of the language the target system supports.

Keeping Track of Different Languages in a FlowDocument

This is done through a sticky (or attached) behaviour. A Sticky Behaviour is an attached property that registers event handlers on the control it is attached to. In this case, the behaviour will be specialized for RichTextBoxes, because their ability to edit FlowDocuments is the most convenient way to edit structured documents in WPF.

The Sticky Behaviour is activated by setting the TracksKeyboardLanguage property to true. Whenever the selection changes, this will cause the RichTextBox to check whether there are KeyboardLanguage attributes on the parent element of the current selection, and to activate the appropriate keyboard layout:

Public Shared ReadOnly TracksKeyboardLanguageProperty As DependencyProperty = DependencyProperty.RegisterAttached("TracksKeyboardLanguage", GetType(Boolean), GetType(Localization), New PropertyMetadata(AddressOf OnTracksKeyboardLanguageChanged))
Public Shared Function GetTracksKeyboardLanguage(ByVal d As DependencyObject) As Boolean
    Return d.GetValue(TracksKeyboardLanguageProperty)
End Function
Public Shared Sub SetTracksKeyboardLanguage(ByVal d As DependencyObject, ByVal value As Boolean)
    d.SetValue(TracksKeyboardLanguageProperty, value)
End Sub
Private Shared Sub OnTracksKeyboardLanguageChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
    If TypeOf d Is RichTextBox Then
        Dim b As RichTextBox = d
        If CBool(e.OldValue) Then
            RemoveHandler b.SelectionChanged, AddressOf OnSelectionChanged
        End If
        If CBool(e.NewValue) Then
            AddHandler b.SelectionChanged, AddressOf OnSelectionChanged
        End If
    End If
End Sub

Private Shared Sub OnSelectionChanged(ByVal sender As Object, ByVal e As RoutedEventArgs)
    Dim b As RichTextBox = sender
    Dim f As FrameworkContentElement = TryCast(b.Selection.Start.Parent, FrameworkContentElement)
    If f IsNot Nothing Then
        Dim tag As String = GetKeyboardLanguage(f)
        If Not String.IsNullOrEmpty(tag) Then
            If Not InputLanguageManager.Current.CurrentInputLanguage.IetfLanguageTag.ToUpper.StartsWith(tag.ToUpper) Then
                InputLanguageManager.Current.CurrentInputLanguage = FindKeyboardLanguage(tag)
            End If
        End If
    End If
    e.Handled = False
End Sub

 

It is always wise to make these event handlers static, so that they don’t create any references which could keep the control from being freed. Setting e.Handled to false makes sure that the RichTextBox still can accept other handlers for the SelectionChanged event.

Downloading the Example

The WPFGluePublished solution on the Downloads page contains a project called KeyboardLanguageExample which demonstrates the use of the KeyboardLanguage and TracksKeyboardLanguage properties. This example assumes that you have German, Arabic and English installed as Input languages on your computer. If you don’t have any additional keyboards installed, you can do so using the Regional and Language Options control panel in Windows.