Hosting Multi-Language IM Conversations in Lync 2010: Code Listing (Part 3 of 3)

Summary:   This is the final article in a series of three articles that describe how to build a Microsoft Lync 2010 Conversation Window Extension application that translates IM text between two languages. The ConversationTranslator application described in this article uses the Microsoft Translation web service.

Applies to:   Microsoft Lync 2010 SDK | Microsoft Lync 2010 | Microsoft Lync Server 2010

Published:   June 2011 | Provided by:   John Austin, Microsoft | About the Author

Contents

Part 2 discusses the logic that is used by the Microsoft Lync 2010 ConversationTranslator application and describes how to obtain translation languages, send messages, and translate received messages. Part 3 lists the C# code that implements the logic that is discussed in Part 2.

The examples in the following sections are included with the Microsoft Lync 2010 SDK ConversationTranslator sample project. The only sample code that is reproduced in this article is code that demonstrates the concepts that are described in this article. Some of the examples may refer to methods that are in the Lync SDK sample project but are not reproduced in this article. To compile the example code, install the Lync SDK and then open the installed sample in Microsoft Visual Studio 2010 development system.

The two translation language controls (comboBoxMyLanguage, comboBoxTargetLanguage) are bound to the ObservableCollection<Language>Languages property of MainViewModel.

The following example declares two ComboBox controls and binds them to the MainViewModel.Languages property. The ItemSource attribute binds the collection of languages returned from the translation service. The combo boxes bind to the same collection of languages.

        <!-- Me: FromLanguage   Them: TargetLanguage -->
        <Grid Grid.Row="0" Height="Auto" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="grid1" VerticalAlignment="Stretch" Width="Auto" Background="#FF8A9AB1">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition />
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <!-- Combos are bond to a single list of languages, and the properties SourceLanguage and TargetLanguage -->
            <TextBlock Grid.Column="0" Text="Me:" HorizontalAlignment="Right" Margin="5,0,0,0" VerticalAlignment="Center" Width="Auto" />
            <ComboBox Grid.Column="1" ItemsSource="{Binding Languages}" SelectedItem="{Binding SourceLanguage, Mode=TwoWay}" DisplayMemberPath="Name" Height="23" HorizontalAlignment="Left" Margin="5,0,0,0" Name="comboBoxMyLanguage" VerticalAlignment="Center" Width="138" />
            <TextBlock Grid.Column="2" Text="Them:"  HorizontalAlignment="Right" Margin="0,0,0,0" VerticalAlignment="Center" Width="Auto"/>
            <ComboBox Grid.Column="3" ItemsSource="{Binding Languages}"  SelectedItem="{Binding TargetLanguage, Mode=TwoWay}" DisplayMemberPath="Name" Height="23" HorizontalAlignment="Left" Margin="5,0,5,0" Name="comboBoxTargetLanguage" VerticalAlignment="Center" Width="138" />
        </Grid>


Figure 1 shows the language combo boxes.

Figure 1. Bing Translator language boxes

Bing Translator language boxes.

The following example declares a data bound list, associated item container style, and the data template.

<ListBox Name="listBoxHistory" ScrollViewer.HorizontalScrollBarVisibility="Disabled" IsHitTestVisible="False" VerticalAlignment="Stretch"
 ItemsSource="{Binding MessageHistory}" BorderThickness="0" Padding="0" FontSize="14" >

    <!-- Gets the ListBox content to stretch horizontally -->
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem" >
            <Setter Property="HorizontalContentAlignment" Value="Stretch"></Setter>
        </Style>
    </ListBox.ItemContainerStyle>

    <!-- List template defines each line of the message history, which maps to one MessageLine object -->
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid x:Name="LayoutRoot" HorizontalAlignment="Stretch" Background="{Binding Background}" Margin="0,-7,-5,0">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" /><RowDefinition />
                </Grid.RowDefinitions>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>
                <TextBox Grid.Row="0" Grid.Column="0" Text="{Binding ParticipantName}" Background="{Binding Background}"
                Visibility="{Binding FirstLineVisibility}" HorizontalAlignment="Left" Margin="3,2,0,0"
                VerticalAlignment="Center" BorderThickness="0" Foreground="#666666" VerticalContentAlignment="Bottom" Padding="0"/>

                <TextBox Grid.Row="0" Grid.Column="1" Text="{Binding MessageTime, StringFormat='hh:mm tt'}"
                Background="{Binding Background}" Visibility="{Binding FirstLineVisibility}"
                HorizontalAlignment="Right" Margin="0,2,2,0" VerticalAlignment="Center" BorderThickness="0"
                Foreground="#666666" VerticalContentAlignment="Bottom" Padding="0"/>

                <Grid Name="gridLine" Grid.Row="1" Grid.ColumnSpan="2" Margin="0,0,0,0">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="Auto" />
                        <ColumnDefinition />
                    </Grid.ColumnDefinitions>
                    <TextBox Grid.Column="0" Text="  °" HorizontalAlignment="Left" Background="{Binding Background}" Height="20" Margin="0,0,0,3"
                    VerticalAlignment="Stretch" Width="20" TextWrapping="Wrap" BorderThickness="0" VerticalContentAlignment="Bottom" HorizontalContentAlignment="Right"
                    Foreground="#CACACA" FontWeight="SemiBold"  Padding="-3"/>

                    <TextBox Grid.Column="1" Text="{Binding Message}" Background="{Binding Background}" FontSize="24" Height="20" Margin="0,0,0,3" 
                    HorizontalAlignment="Stretch" VerticalAlignment="Stretch" TextWrapping="Wrap" BorderThickness="0" VerticalContentAlignment="Bottom"
                     HorizontalContentAlignment="Left" Padding="-3" />
                </Grid>
            </Grid>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

Figure 2 shows the rendered conversation history list with a Portuguese translation.

Figure 2. Conversation history list showing translated IM text

Conversation history  showing translated IM text

The following example declares a text box, send button, and cancel button that let a user enter text, translate, send, or cancel sending IM text.

            <TextBox Grid.Column="0" Height="Auto" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="textBoxMessage" VerticalAlignment="Stretch" Width="Auto" KeyUp="textBoxMessage_KeyUp" TextWrapping="Wrap" />
            <Grid Grid.Column="1" Height="Auto" HorizontalAlignment="Stretch" Margin="0,0,0,0" Name="grid3" VerticalAlignment="Stretch" Width="Auto">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <Button Content="Translate" Grid.Row="0" Height="70" HorizontalAlignment="Center" Margin="5,2,5,0" Name="buttonProofSend" VerticalAlignment="Top" Width="70" Click="buttonProofSend_Click" />
                <Button Content="Cancel" Grid.Row="1" Height="23" HorizontalAlignment="Center" Margin="5,0,5,2" Name="buttonCancel" VerticalAlignment="Bottom" Width="70" Click="buttonCancel_Click" />
            </Grid>


Figure 3 shows the text entry box and action buttons that are used to enter new IM text, translate, and send IM.

Figure 3. ConversationTranslator text entry and Translate button

Text box and Translate button

The following procedure shows how ConversationTranslator is initialized:

  1. Get an instance of Microsoft.Lync.Model.Conversation.Conversation that represents the conversation in the conversation window.

  2. Construct and start the TranslationService and ConversationService classes.

  3. Register for events on the two service classes.

Get the Hosting Conversation

The following example from App.xaml.cs runs when a user starts ConversationTranslator.

        private void Application_Startup(object sender, StartupEventArgs e)
        {
            // Creates the main view model.
            viewModel = new MainViewModel();
            MainPage mainPage = new MainPage();
            mainPage.DataContext = viewModel;

            this.RootVisual = mainPage;
        }

The following example from the MainPage.UserControl_Loaded event handler gets the Microsoft.Lync.Model.Conversation.Conversation instance.

 //gets the conversation this translator is associated with
 conversation = (Conversation)LyncClient.GetHostingConversation();
//DEBUG: when running on the web browser, just get the first current
//active Lync conversation
if (conversation == null)
{
    //obtains the first active Lync conversation
    conversation = LyncClient.GetClient().ConversationManager.Conversations[0];

    //cannot run without a conversation
    if (this.conversation == null)
    {
        throw new NotSupportedException("You need one conversation to Debug the ConversationTranslator");
    }
}


Start the Service Classes and Register for Events

The following example constructs ConversationService and TranslationService class instances, registers for events on the instances, and initializes them.

//creates the conversation service component and subscribes to events
conversationService = new ConversationService(conversation);
conversationService.MessageError += new MessageError(conversationService_MessageError);
conversationService.MessageReceived += new MessageReceived(conversationService_MessageReceived);
conversationService.MessageSent += new MessageSent(conversationService_MessageSent);

//starts listening to Lync events
conversationService.Start();

//obtains the translation service component and subscribes to events
translationService = new TranslationService();
translationService.LanguagesReceived += new LanguagesReceived(translationService_LanguagesReceived);
translationService.TranslationError += new TranslationError(translationService_TranslationError);
translationService.TranslationReceived += new TranslationReceived(translationService_TranslationReceived);
translationService.Start();

Getting supported languages involves the following tasks:

  1. Query BingTranslation for the set of languages that are supported for translation.

  2. Update the UI with the languages that are specified in step 1.

  3. Update a view model class instance with the collection of returned languages.

Query for Languages

The following example from MainPage gets the languages supported by BingTranslation.

        /// <summary>
        /// Starts the translator, which:
        /// 1. Connects to the translation services.
        /// 2. Updates the UI with the available languages.
        /// </summary>
        public void Start()
        {
            //connects to the translation service
            languageService = new LanguageServiceClient();            

            //requests the available language codes
            languageService.BeginGetLanguagesForTranslate(AppID, this.OnGetLanguagesResponse, null);
        }

        /// <summary>
        /// Called back when the language list arrives.
        /// </summary>
        private void OnGetLanguagesResponse(IAsyncResult result)
        {
            //gets the codes
            ObservableCollection<string> languageCodes = languageService.EndGetLanguagesForTranslate(result);

            //requests the language names in English (and passes the codes as the asynchronous context)
            languageService.BeginGetLanguageNames(AppID, "en", languageCodes, this.OnGetLanguageNamesResponse, languageCodes);
        }


        /// <summary>
        /// Called back when the language name list arrives.
        /// Assembles the array with the Language objects and notifies the UI.
        /// </summary>
        /// <param name="result"></param>
        private void OnGetLanguageNamesResponse(IAsyncResult result)
        {
            //obtains the codes from the asyncronous context
            ObservableCollection<string> languageCodes = (ObservableCollection<string>) result.AsyncState;
            //obtains the names
            ObservableCollection<string> languageNames = languageService.EndGetLanguageNames(result);

            //assembles the language list
            List<Language> languages = new List<Language>();
            for (int i = 0; i < languageCodes.Count; i++)
            {
                languages.Add(new Language(languageCodes[i], languageNames[i]));
            }

            //notifies the UI the languages are ready
            LanguagesReceived(languages);
        }


TipTip

Replace the language code passed in argument 2 of BeginGetLanguageNames with the language code of the user. This process causes BingTranslation to return a collection of language names in the user’s language instead of English.

Updating UI and MainViewModel

The following example from MainPage handles the LanguagesReceived event that is raised by the previous example. The viewModel.Languages collection is bound to the XAML control as shown in an earlier example.

        /// <summary>
        /// Languages received from the translation service.
        /// </summary>
        /// <param name="languages">Set of supported languages.</param>
        private void translationService_LanguagesReceived(IList<Language> languages)
        {
            //need to swtich to the UI thread
            Dispatcher.BeginInvoke(() =>
            {
                foreach (Language language in languages)
                {
                    viewModel.Languages.Add(language);
                }

                //updates the combos, necessary given that the set of languages comes after initialization
                comboBoxMyLanguage.SelectedItem = viewModel.SourceLanguage;
                comboBoxTargetLanguage.SelectedItem = viewModel.TargetLanguage;
            });

        }


TipTip

LanguageService (the kind of BingTranslation) exposes an observable collection of language codes and language names. To simplify ConversationTranslator code, the sample does not handle the SpecializedNotifyCollectionChangedEventHandler() event. Instead, the language list is loaded one time on startup. For information about how to handle ObservableCollection events, see System.Collections.Specialized.

Translating outgoing IM text involves the following tasks:

  1. Create an instance of MessageContext in the model layer to encapsulate message text and other context.

  2. Get a translation of the message text from BingTranslation.

  3. Update the ConversationTranslator IM entry text box by using the proposed translation.

  4. Send the translated text as an IM.

  5. Update the IM history list box and view model layer.

Creating MessageContext

The following example from MainPage creates a new MessageContext instance and sets the MessageContext.OriginalMessage property to the text typed by the user. The example calls Contact.GetContactInformation. This example code is called when a user wants to translate IM text to another user’s language before the IM is sent. The name string returned by this method call is used on the view to identify the sender of a message. Finally, the message is translated by TranslationService.

        /// <summary>
        /// Translates the message asynchronously.
        /// </summary>
        /// <param name="message"></param>
        private void SendLocalMessageForTranslation(string message)
        {
            //assembles a MessageContext object
            messageContext = new MessageContext();
            messageContext.OriginalMessage = message;
            //reads the self participant's name
            try
            {
                messageContext.ParticipantName = 
                    conversation.SelfParticipant.Contact.GetContactInformation(ContactInformationType.DisplayName) as string;
            }
            catch (LyncClientException e)
            {
                ShowError(e);
                return;
            }
            catch (SystemException e)
            {
                if (LyncModelExceptionHelper.IsLyncException(e))
                {
                    // Log the exception thrown by the Lync Model API.
                    ShowError(e);
                    return;
                }
                else
                {
                    // Rethrow the SystemException which did not come from the Lync Model API.
                    throw;
                }
            }
            messageContext.SourceLanguage = viewModel.SourceLanguage.Code;
            messageContext.TargetLanguage = viewModel.TargetLanguage.Code;
            messageContext.Direction = MessageDirection.Outgoing;
            messageContext.MessageTime = DateTime.Now;

            //sends the message for translation
            translationService.Translate(messageContext);
        }


Getting a Message Translation

The following example is taken from TranslationService.cs and is called by MainPage to translate an incoming or outgoing message. If the message direction is incoming, the example detects the language of the sender. Otherwise, the message is translated into the target language specified by the message context.

        /// <summary>
        /// Processes a translation.
        /// </summary>
        /// <param name="context">Context containing the message information.</param>
        public void Translate(MessageContext context)
        {
            //first checks if need to detect the source language
            if (context.Direction == MessageDirection.Incoming && context.IsLanguageDetectionNeeded)
            {
                //detects the incoming message language
                languageService.BeginDetect(AppID, context.OriginalMessage, this.OnDetectResponse, context);
            }
            else
            {
                //translates directly
                DoTranslate(context);
            }
        }
        /// <summary>
        /// Sends the message for translation.
        /// </summary>
        /// <param name="context"></param>
        private void DoTranslate(MessageContext context)
        {
            //calls the translation service
            languageService.BeginTranslate(AppID, context.OriginalMessage, context.SourceLanguage, context.TargetLanguage, this.OnTranslateResponse, context);
        }

For an example that retrieves the translation obtained by calling by BeginTranslate, see Handling LanguageService Events.

The following example is called by LanguageService as a callback when the asynchronous translation operation is complete. The callback updates the MessageContext instance with the translated message and then raises an event, passing the updated MessageContext. The event is handled by MainPage.

        /// <summary>
        /// Called when the translation is received.
        /// </summary>
        /// <param name="result"></param>
        private void OnTranslateResponse(IAsyncResult result)
        {
            //gets context from the asyncronous context
            MessageContext context = (MessageContext)result.AsyncState;

            try
            {
                //reads the response
                context.TranslatedMessage = languageService.EndTranslate(result);

                //notifies the UI
                TranslationReceived(context);
            }
            catch (Exception ex)
            {
                TranslationError(ex, context);
            }
        }


Updating Text Box by Using Translated Message

The following example is taken from MainPage.xaml.cs and handles the event that is raised in the previous example. It updates the ConversationTranslator UI with translated message text. A user can review the translation before they send it.

        /// <summary>
        /// Shows the translation received from the translation service.
        /// The visual representation depends on the message direction.
        /// </summary>
        /// <param name="context"></param>
        private void translationService_TranslationReceived(MessageContext context)
        {
            //incoming message: just show in the history
            if (context.Direction == MessageDirection.Incoming)
            {
                ShowIncomingTranslatedMessage(context);
            }
            else //outgoing message: move to the Send state
            {
                SetUIState(UIState.Send);
                ShowProofMessage(context.TranslatedMessage);
            }
        }
        /// <summary>
        /// Shows a translated outgoing message for proofing
        /// </summary>
        private void ShowProofMessage(string message)
        {
            this.Dispatcher.BeginInvoke(() =>
            {
                textBoxMessage.Text = message;
            });
        }



Sending IM

The following example from MainPage.xaml.cs is called when a user clicks the Send button. If the message has not been translated, the message is translated by using code from an earlier example. Otherwise, the message is sent to other conversation participants.

Tip Tip

The DoSendMessage example updates the MessageContext.TranslatedMessage property by using possible user input from the ConversationTranslator UI.

        /// <summary>
        /// Called when the ProofSend button is clicked or user presses Enter.
        /// </summary>
        /// <param name="message"></param>
        private void ProofOrSend()
        {
            //gets the message and clears the text box
            string message = textBoxMessage.Text;
            textBoxMessage.Text = string.Empty;

            //empty messages are not sent for translation
            if (string.IsNullOrWhiteSpace(message))
            {
                return;
            }

            //depending on the UI state, the message will be sent for translation
            //or into the conversation
            if (state == UIState.Proof)
            {
                //sends the message for translation
                SendLocalMessageForTranslation(message);
            }
            else // Send state
            {
                //sends the message into the conversation
                DoSendMessage(message);

                //switches back to proof state
                SetUIState(UIState.Proof);
            }
        }

        /// <summary>
        /// Sends the message into the conversation.
        /// </summary>
        public void DoSendMessage(string translatedMessage)
        {
            try
            {
                //updates the translated message, in case the user corrected it
                messageContext.TranslatedMessage = translatedMessage;

                //sends the message into the conversation
                conversationService.SendMessage(messageContext);

                //notifies end of composing
                SetComposing(false);
            }
            catch (Exception ex)
            {
                ShowError(ex);
            }
        }


The following example is taken from the ConversationService class. It sends a translated IM to all other participants.

        /// <summary>
        /// Sends a message into the conversation.
        /// </summary>
        public void SendMessage(MessageContext context)
        {
            //sends the message 
            myImModality.BeginSendMessage(context.TranslatedMessage, myImModality_OnMessageSent, context);
        }

        /// <summary>
        /// Called when a message is sent.
        /// </summary>
        public void myImModality_OnMessageSent(IAsyncResult result)
        {
            //gets context from the asyncronous context
            MessageContext context = (MessageContext)result.AsyncState;

            //notifies the UI that the message was actually sent
            MessageSent(context);
        }


You do not have to call the InstantMessageModality.EndSendMessage method. However, the call is necessary if application logic must obtain the results of the operation. The send operation fails when the message recipient goes offline in the interval between BeginSendMessage and the routing of the IM text through the server and to the remote endpoint.

The following example can be added to the callback method from the previous example.


            try
            {
                myImModality.EndSendMessage(result);
            }
            catch (LyncClientException ex)
            {
                System.Windows.MessageBox.Show(ex.Message);
            }

Updating IM History and View Model

The following example from MainPage.xaml.cs handles the event that is raised by ConversationService when a message is sent. The example updates the ConversationTranslator UI with a history of the last sent message.

        /// <summary>
        /// Called when a message is effectively sent into the conversation.
        /// </summary>
        /// <param name="context"></param>
        private void conversationService_MessageSent(MessageContext context)
        {
            //posts the action into the UI thread
            this.Dispatcher.BeginInvoke(() =>
            {
                //writes the sent message into the in-memory history
                WriteMessageToHistory(context.ParticipantName, context.MessageTime, context.TranslatedMessage);
            });
        }
        /// <summary>
        /// Writes a message to the in-memory conversation history.
        /// </summary>
        /// <param name="senderName">The name of the person who sent the message.</param>
        /// <param name="time">The time information.</param>
        /// <param name="message">The message itself.</param>
        private void WriteMessageToHistory(string senderName, DateTime time, string message)
        {
            //adds a line for the received or sent message
            MessageLine line = new MessageLine(senderName, time, message);
            viewModel.MessageHistory.Add(line);

            //manually scrolls down to the last added message
            listBoxHistory.UpdateLayout();
            scrollViewerMessageLog.ScrollToVerticalOffset(listBoxHistory.ActualHeight);
        }



Translating incoming IM text involves the following tasks:

  1. Handle an event that is raised when a message is received.

  2. Create an instance of MessageContext in the model layer to encapsulate message context.

  3. Get a translation of the message text.

  4. Update the UI and view model layer.

Handling Message Received Event

The following example from ConversationService handles the IM received event that is raised by Lync 2010 when an IM is received from another conversation participant.

        /// <summary>
        /// Called by the Lync SDK when a new message is received.
        /// </summary>
        private void remoteImModality_InstantMessageReceived(object sender, MessageSentEventArgs args)
        {
                //casts the modality
                InstantMessageModality modality = (InstantMessageModality)sender;

                //gets the participant name
                string name = (string)modality.Participant.Contact.GetContactInformation(ContactInformationType.DisplayName);

                //reads the message in its plain text format (automatically converted)
                string message = args.Text;

                //notifies the UI about the new message
                MessageReceived(message, name);
        }


Creating MessageContext

The following example from MainPage handles the event that is raised by the previous example. The example event handler creates a new instance of MessageContext to encapsulate the received message and then requests a translation from the TranslationService class.

For an example that lists the Translate method, see Getting a Message Translation.

        /// <summary>
        /// Called when a message is received for the specified participant.
        /// </summary>
        private void conversationService_MessageReceived(string message, string participantName)
        {
            //posts the action into the UI thread
            this.Dispatcher.BeginInvoke(() =>
            {
                //creates a message context object to pass the message data across the layers
                MessageContext context = new MessageContext();
                context.MessageTime = DateTime.Now;
                context.ParticipantName = participantName;
                context.OriginalMessage = message;
                context.TargetLanguage = viewModel.SourceLanguage.Code;
                context.Direction = MessageDirection.Incoming;

                //optimization for two participants (avoids language detection)
                if (conversation.Participants.Count == 2)
                {
                    context.IsLanguageDetectionNeeded = false;
                    context.SourceLanguage = viewModel.TargetLanguage.Code;
                }

                //sends the message for translation
                translationService.Translate(context);
            });
        }


Handling LanguageService Events

The following example from TranslatorService handles the events that are raised when the LanguageService detects the language of an IM and then returns a translation.


        /// <summary>
        /// Called back when the source language was detected.
        /// </summary>
        /// <param name="result"></param>
        private void OnDetectResponse(IAsyncResult result)
        {
            //gets context from the asyncronous context
            MessageContext context = (MessageContext) result.AsyncState;

            try
            {
                //reads the response
                context.SourceLanguage = languageService.EndDetect(result);

                //now calls the translation itself
                DoTranslate(context);
            }
            catch (Exception ex)
            {
                TranslationError(ex, context);
            }
        }


        /// <summary>
        /// Called when the translation is received.
        /// </summary>
        /// <param name="result"></param>
        private void OnTranslateResponse(IAsyncResult result)
        {
            //gets context from the asyncronous context
            MessageContext context = (MessageContext)result.AsyncState;

            try
            {
                //reads the response
                context.TranslatedMessage = languageService.EndTranslate(result);

                //notifies the UI
                TranslationReceived(context);
            }
            catch (Exception ex)
            {
                TranslationError(ex, context);
            }
        }

Updating UI and View Model

The following example is called by a MainPage event handler after the event in the previous example is raised. The code of the event handler appears in Updating Text Box by Using Translated Message.

The Lambda expression in the next example is invoked on the UI thread from the platform thread event handler with a call to BeginInvoke.

        /// <summary>
        /// Adds an incoming translated message to the message log.
        /// </summary>
        private void ShowIncomingTranslatedMessage(MessageContext context)
        {
            //writes a message log in the correct thread
            this.Dispatcher.BeginInvoke(() =>
            {
                WriteMessageToHistory(context.ParticipantName, context.MessageTime, context.TranslatedMessage);
            });
        }

        /// <summary>
        /// Writes a message to the in-memory conversation history.
        /// </summary>
        /// <param name="senderName">The name of the person who sent the message.</param>
        /// <param name="time">The time information.</param>
        /// <param name="message">The message itself.</param>
        private void WriteMessageToHistory(string senderName, DateTime time, string message)
        {
            //adds a line for the received or sent message
            MessageLine line = new MessageLine(senderName, time, message);
            viewModel.MessageHistory.Add(line);

            //manually scrolls down to the last added message
            listBoxHistory.UpdateLayout();
            scrollViewerMessageLog.ScrollToVerticalOffset(listBoxHistory.ActualHeight);
        }


ConversationTranslator is a Lync 2010 Conversation Window Extension application that bridges the language gap between you and a speaker of any one of 34 other languages. After adding several Lync 2010 API types to your code, you can add this translation capability to your application.

For more information about the ConversationTranslator application, see Hosting Multi-Language IM Conversations in Lync 2010: Overview (Part 1 of 3) and Hosting Multi-Language IM Conversations in Lync 2010: Project Details (Part 2 of 3).

John Austin, Microsoft, is a programmer/writer in the Lync client SDK documentation team. He has been writing Microsoft technical documentation for four years. Prior to working for Microsoft, John spent two decades as a software developer.

Show: