Building Lync IM Conversation Windows: Lync 2010 API Event Handlers (Part 4 of 5)

Summary:   This article is the fourth in a series of five articles that describe how to build a Microsoft Lync 2010 IM conversation window that features a spelling checker, and then add the spelling checker to the conversation window. This article describes the Microsoft Lync 2010 API event handlers that are used in the application.

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

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

Contents

  • Introduction

  • Lync 2010 API Events

  • Lync 2010 API Callback Methods

  • Conclusion

  • Additional Resources

This article is the fourth in a five-part series of articles about how to build a Lync 2010 IM conversation window.

Introduction

Building Lync IM Conversation Windows: Helper Methods (Part 3 of 5) describes simple application helper methods that show how to sign in to Microsoft Lync 2010, start conversations, and add contacts to conversations. Part 4 describes the methods that handle the Lync 2010 API events that are related to IM conversations. The examples appearing in part 4 show the events that are raised and also describe a way to handle them. You may handle these events in a different way. However, to build a responsive and useful conversation window, you must handle the events according to the specified order.

Each article in this series uses code from a WPF sample application that includes a conversation window, a network credential window, and the MSDNArticleIM.ClientModel and MSDNArticleIM.BingSpellChecker helper class. These classes are part of the MSDNArticleIM namespace.

Figure 1 shows the WPF conversation window that is built to work with the Microsoft Word Repository Client application. The WPF conversation window is implemented by the MSDNArticleIM.ConversationWindow class. For more information about the Word Repository Client application, see Building a Lync IM Conversation Window: Introduction (Part 1 of 5).

Figure 1. WPF conversation window

Lync_IM_Article_ConversationWindow

Lync 2010 API Events

The events in this section are raised by Lync 2010 API after the conversation window logic registers for events on the Lync 2010 API class instances that are used in IM conversations.

LyncClient Events

Client events are registered when the conversation Form loads. For information about the conversation window control event handlers, see Building a Lync IM Conversation Window: Window Control Event Handlers (Part 2 of 5).

Your application must respond to changes in client state such as unexpected network connectivity issues. A user can sign out from Lync 2010 if the client is configured to run in the default, non UI-Suppressed mode. This kind of custom application state is non-deterministic, and the application should provide a visual queue that shows that the user is not signed in to Lync 2010. If defensive code is not added around each Lync 2010 API call, the Microsoft.Lync.Model.NotSignedInException instance must be handled with each Lync 2010 API call.

        /// <summary>
        /// Handles LyncClient state changes for SignedIn and SignedOut
        /// </summary>
        /// <param name="sender">object. The LyncClient</param>
        /// <param name="e">ClientStateChangedEventArgs. The event state.</param>
        void _LyncClient_StateChanged(object sender, ClientStateChangedEventArgs e)
        {
            //
            LyncClient client = (LyncClient)sender;

            switch (e.NewState)
            { 
                case ClientState.SigningOut:
                    //************************************
                    //You must unregister for these events in the SigningOut state. Otherwise, you continue
                    //to receive events while signing out. If your event handlers for these events call
                    //Lync 2010 API methods under this condition, the NotSignedInException is raised.
                    //************************************
                    _remoteContact.ContactInformationChanged -= _remoteContact_ContactInformationChanged;
                    _LyncClient.ConversationManager.ConversationAdded -= ConversationsManager_ConversationAdded;
                    if ((InstantMessageModality)(_Conversation.Modalities[ModalityTypes.InstantMessage]) != null)
                    {
                        ((InstantMessageModality)_Conversation.Modalities[ModalityTypes.InstantMessage]).InstantMessageReceived -= myInstantMessageModality_MessageReceived;
                        ((InstantMessageModality)_Conversation.Modalities[ModalityTypes.InstantMessage]).IsTypingChanged -= myInstantMessageModality_ComposingChanged;
                    }
                    if (_RemoteIMModality != null)
                    {
                        _RemoteIMModality.InstantMessageReceived -= myInstantMessageModality_MessageReceived;
                        _RemoteIMModality.IsTypingChanged -= myInstantMessageModality_ComposingChanged;
                    }

                    //*********************************************************************
                    //Disable the Send Message button so user cannot invoke BeginSendMessage on
                    //signed out Lync 2010 client.
                    //*********************************************************************
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.DisableButton, Send_Button, null });

                    break;
                case ClientState.SignedOut:
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Sign-in State Change: Signed out" });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.EnableButton, SignIn_Button, null });
                    break;
                case ClientState.ShuttingDown:
                    MessageBox.Show("Lync is shutting down. This application will shut down", "Lync Critical Exception");
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] {FormActions.CloseForm, this,null });

                    break;
                case ClientState.SignedIn:
                    //*****************************************************************
                    //Register for the ConversationAdded event on LyncCient.ConversationManager. 
                    //Raised when a new conversation is added to the collection of conversations
                    //on the conversation manager.
                    //*****************************************************************
                    _ClientModel._LyncClient.ConversationManager.ConversationAdded += ConversationsManager_ConversationAdded;

                    if (_Conversation == null)
                    {
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Sign-in State Change: Signed in."});
                    }
                    else
                    {
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Sign-in State Change: Signed in. Conversation State: " + _Conversation.State.ToString() });
                        if (_Conversation.State == ConversationState.Active)
                        {
                            this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.CadetBlue });
                        }
                    }
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.DisableButton, SignIn_Button, null });

                    break;
            }
        }f



Contact Events

When the application logic gets a contact instance in a ConversationManager.ConversationAdded event handler, the conversation window registers for contact events. The application can also register for contact events at any time before adding a contact to a conversation.

The contact that a user intends to invite to a conversation may not be available at the time that a conversation is created. Register for the Contact.ContactInformationChanged event on the contact to be notified that the contact’s availability changed. As soon as a contact is available, you can add the contact to a conversation.

The process that is used to register contacts includes the following tasks.

To register contacts

  1. Register for the ConversationAdded event.

  2. Add a new conversation.

  3. In the ConversationAdded event, register for information changes that are associated with the desired contact, including availability.

  4. Get the availability of a contact.

  5. If the contact is available, add it to the new conversation.

    Tip

    If the contact is not available at this point, add the contact to the conversation later when the contact is available. Use the Contact.ContactInformationChanged event handler to catch the change in availability.

The following example handles the Contact.ContactInformationChanged event by checking if the changed contact information type is set to Microsoft.Lync.Model.ContactInformationType.Availability. The example then calls the MSDNArticleIM.AddContactToConversation helper method, changes the background color of the conversation form, and enables or disables the Send Message button. For more information about MSDNArticleIM.AddContactToConversation, see Building a Lync IM Conversation Window: Window Control Event Handlers (Part 2 of 5)

Tip

If network connectivity is lost during a conversation, the ContactInformationChanged event is raised for each participant except the local participant. The availability value for each remote contact is None.

        /// <summary>
        /// Handles the event that is raised when contact information is updated for a contact
        /// </summary>
        /// <param name="sender">object. The contact with information that changed.</param>
        /// <param name="e">ContactInformationChangedEventArgs. The event state. Provides collection of change contact information types.</param>
        void _remoteContact_ContactInformationChanged(object sender, ContactInformationChangedEventArgs e)
        {

            //**************************************
            //This event handler performs any of 4 actions based on contact availability:
            // 1: Sets the background color of the Form that is based on contact availability.
            // 2: Sets the conversation window title to current conversation state and contact availability.
            // 3: Enables or disables SendMessage_Button.
            // 4: Adds the _remoteContact to the conversation if it is available to join the conversation. 
            //**************************************

            //**************************************
            //Has the contact availability changed?
            //**************************************
            if (e.ChangedContactInformation.Contains(ContactInformationType.Availability))
            {
                ContactAvailability currentAvailability = (ContactAvailability)((Contact)sender).GetContactInformation(ContactInformationType.Availability);
                switch (currentAvailability)
                {
                    case ContactAvailability.DoNotDisturb:
                        //**************************************
                        //Contact is not available.
                        //**************************************

                        //**************************************
                        //Update the conversation window title text with the current conversation state and contact availability.
                        //**************************************
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation is " + _Conversation.State.ToString() + ". Contact is " + currentAvailability.ToString() });
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.AntiqueWhite });

                        //**************************************
                        //Disable the Send Message button appearing in conversation Form.
                        //**************************************
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.DisableButton, Send_Button, null });
                        break;
                    case ContactAvailability.Offline:
                        //**************************************
                        //Contact is not available.
                        //**************************************

                        //**************************************
                        //Change Form color to white, indicating that the remote participant cannot accept IM text.
                        //**************************************
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.AntiqueWhite });
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation is " + _Conversation.State.ToString() + ". Contact is " + currentAvailability.ToString() });

                        //**************************************
                        //Disable the Send Message button in conversation window Form.
                        //**************************************
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.DisableButton, Send_Button, null });
                        break;
                    default:
                        //**************************************
                        //If the contact is now available to join a conversation...
                        //**************************************

                        //**************************************
                        //Call helper method to add the contact to the conversation.
                        //**************************************
                        AddContactToConversation(_Conversation, (Contact)sender);

                        //**************************************
                        //Change the conversation window Form color to blue if
                        //conversation state is active and contact is available.
                        //**************************************
                        if (_Conversation.State == ConversationState.Active)
                        {
                            this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.CadetBlue });
                        }
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation is " + _Conversation.State.ToString() + ". Contact is " + currentAvailability.ToString() });

                        //**************************************
                        //Enable Send Message button in conversation window Form.
                        //**************************************
                        this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.EnableButton, Send_Button, null });
                        break;
                }
            }
        }

Conversation Object Events

The application logic registers for the conversation object events in the ConversationManager.ConversationAdded event handler.

In Building Lync IM Conversation Windows: Helper Methods (Part 3 of 5), the ConversationManager.ConversationAdded event is registered on the conversation manager. ConversationAdded obtains a contact based on a SIP URI, registers for contact events, and then creates a new conversation. After these tasks are completed, start a new conversation. The next step adds the contact to the new conversation.

The following example handles the conversation added event. First, events on the new conversation are registered. Second, contacts are added to the new conversation. When the contact is added to the conversation, the Conversation.ParticipantAdded event is raised.

The availability of the contact is checked in the MSDNArticleIM.AddContactToConversation helper method. If the contact is not available then the helper method returns false. For more information about MSDNArticleIM.AddContactToConversation, see Building a Lync IM Conversation Window: Window Control Event Handlers (Part 2 of 5).

        /// <summary>
        /// Handles ConversationAdded state change event that is raised on ConversationsManager
        /// </summary>
        /// <param name="source">ConversationsManager: The source of the event.</param>
        /// <param name="data">ConversationsManagerEventArgs: The event data. The incoming conversation is obtained here.</param>
        void ConversationsManager_ConversationAdded(Object source, ConversationManagerEventArgs data)
        {
            if (_targetUri == null || _targetUri.Length == 0)
            {
                //***********************************************
                //User has not specified the SIP address of a remote user.
                //***********************************************
                return;
            }

            //***********************************************
            //Register for conversation state changed events.
            //***********************************************
            data.Conversation.ParticipantAdded += Conversation_ParticipantAdded;
            data.Conversation.StateChanged += Conversation_StateChangedEvent;
            try
            {

                //***********************************************
                //Get a contact instance by using the SIP address of a 
                //remote user. _targetUri is a class field that is initiated 
                //with the contents of the SIP address text entry box
                //on the conversation Form. 
                //***********************************************
                _remoteContact = _ClientModel._LyncClient.ContactManager.GetContactByUri(_targetUri);

                //***********************************************
                //Register for contact information changed events to catch changes in contact availability before and during a conversation.
                //***********************************************
                _remoteContact.ContactInformationChanged += new EventHandler<ContactInformationChangedEventArgs>(_remoteContact_ContactInformationChanged);

                //***********************************************
                //Set conversation window title to current state of conversation.
                //***********************************************
                this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation is " + data.Conversation.State.ToString() });

                //**************************************
                //To add the contact to the conversation, call the helper method.
                //**************************************
                if (AddContactToConversation(data.Conversation, _remoteContact) == false)
                {
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateLabel, ActivityText_Label, "ConversationAdded Event: Participant not added" });
                }
            }
            catch (ItemNotFoundException)
            {
                data.Conversation.End();
            }
            catch (ItemAlreadyExistException) { }

        }

Conversation State Changed Event

The conversation state changed event is raised when the conversation state changes from inactive to active or when the conversation state changes from active to terminated. Conversations are terminated deterministically. For example, your code must call the Conversation.End method to end a conversation. If the remote participant departs from a conversation by leaving only the local participant, the conversation enters an inactive state but is not terminated. You can set the conversation state to Microsoft.Lync.Model.Conversation.ConversationState.Active again by adding another available contact and then sending a message.

The following example uses the state changed event to set the background color of the conversation Form to the Brushes.AntiqueWhite color when the state of the conversation is Microsoft.Lync.Model.Conversation.ConversationState.Terminated. The state of a conversation is Microsoft.Lync.Model.Conversation.ConversationState.Active when a non-local contact is added to the conversation, the first message is sent, and a participant accepts the conversation invitation. The following example uses the active state change to set the background color of the conversation window to Brushes.CadetBlue.

Tip

A conversation is inactive until a participant accepts the first message sent in the conversation. At this point, the conversation state is Microsoft.Lync.Model.Conversation.ConversationState.Active.

        /// <summary>
        /// Handles the event that is raised when the state of an active conversation changes 
        /// </summary>
        /// <param name="source">Conversation: The active conversation that raised the state change event.</param>
        /// <param name="data">ConversationStateChangedEventArgs: Event data that contains state change data.</param>
        void Conversation_StateChangedEvent(Object source, ConversationStateChangedEventArgs data)
        {
            switch (data.NewState)
            {
                //******************************************************************
                //Conversation ended.
                //******************************************************************
                case ConversationState.Terminated:
                    //*******************************************************************
                    //Update Form status label text with current state of conversation.
                    //*******************************************************************
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.AntiqueWhite });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.DisableButton, EndConversation_Button, null });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation Ended" });
                    break;
                //******************************************************************
                //The first sent IM has been accepted.
                //******************************************************************
                case ConversationState.Active:
                    //*******************************************************************
                    //Update Form status label text with current state of conversation.
                    //*******************************************************************
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.CadetBlue });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation is Active" });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.EnableButton, EndConversation_Button, null });
                    break;
                //******************************************************************
                //Only one participant is in the conversation and no IM is being sent.
                //******************************************************************
                case ConversationState.Inactive:
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.AntiqueWhite });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation is inactive" });
                    break;
                case ConversationState.Invalid:
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetFormBackColor, this, Brushes.AntiqueWhite });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateWindowTitle, this, "Conversation is invalid" });
                    break;
            }
        }

Conversation Participant Added Event

The following example updates the conversation window and enables the conversation window Send Message button. Use the conversation ParticipantAdded event to discover the capabilities of the participant and to register for participant events.

        /// <summary>
        /// ParticipantAdded callback handles ParticipantAdded event that is raised by conversation
        /// </summary>
        /// <param name="source">Conversation Source conversation.</param>
        /// <param name="data">ParticpantCollectionEventArgs Event data</param>
        void Conversation_ParticipantAdded(Object source, ParticipantCollectionChangedEventArgs data)
        {

            if (data.Participant.IsSelf == false)
            {

                //******************************************************************************
                //The wantedInformation dictionary contains the types of contact information
                //that the application uses. Specify the kind (ContactInformationType) of contact
                //information that you want included within updates.
                //
                //You can declare one dictionary for all contacts or a unique instance of the dictionary
                //for each contact you are interested in. With a unique dictionary, you can specify
                //different contact information types for individual contacts.
                //******************************************************************************
                List<ContactInformationType> wantedInformation = new List<ContactInformationType>();
                wantedInformation.Add(ContactInformationType.SourceNetwork);
                wantedInformation.Add(ContactInformationType.Capabilities);
                wantedInformation.Add(ContactInformationType.Availability);



                //***********************************************************
                //The canReceiveFormattedIM helper method that
                //looks at the published capabilities information of a contact
                //to see if that contact is capable of receiving and sending IM.
                //***********************************************************
                _FormatMessageAsText = canReceiveFormattedIM(data.Participant.Contact.GetContactInformation(wantedInformation));

                if (((Conversation)source).Modalities.ContainsKey(ModalityTypes.InstantMessage))
                {

                    data.Participant.ActionAvailabilityChanged += new EventHandler<ParticipantActionAvailabilityChangedEventArgs>(Participant_ActionAvailabilityChanged);
                    _RemoteIMModality = data.Participant.Modalities[ModalityTypes.InstantMessage] as InstantMessageModality;
                    _RemoteIMModality.InstantMessageReceived += myInstantMessageModality_MessageReceived;
                    _RemoteIMModality.IsTypingChanged += myInstantMessageModality_ComposingChanged;
                    _RemoteIMModality.ActionAvailabilityChanged += new EventHandler<ModalityActionAvailabilityChangedEventArgs>(_RemoteIMModality_ActionAvailabilityChanged);

                    _Conversation.Modalities[ModalityTypes.InstantMessage].ActionAvailabilityChanged += _RemoteIMModality_ActionAvailabilityChanged;

                    _RemoteIMModality.ModalityStateChanged += myInstantMessageModality_ModalityStateChanged;
                    _Conversation.Modalities[ModalityTypes.InstantMessage].ModalityStateChanged += myInstantMessageModality_ModalityStateChanged;

                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateLabel, this.ActivityText_Label, "Participant Added" });
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.EnableButton, Send_Button, null });

                }
            }
            else
            {
                _LocalIMModality = data.Participant.Modalities[ModalityTypes.InstantMessage] as InstantMessageModality;
                _LocalIMModality.InstantMessageReceived += myInstantMessageModality_MessageReceived;
            }
        }

IM Modality Events

The application logic registers for IM modality events in the Conversation.ParticipantAdded event handler.

The two Microsoft.Lync.Model.Conversation.InstantMessageModality events in this section are used to catch new messages and updates to the remote user typing status.

ComposingChanged Event

The following example handles the event that is raised when a remote participant signals that they are typing text.

Important

To ensure an application is notified that a remote participant’s typing status changed, register for this event on each remote conversation participant’s Microsoft.Lync.Model.Conversation.InstantMessageModality instead of the conversation InstantMessageModality.

The following example does not handle a scenario where a conversation has two or more remote participants. In that scenario, the application handles each ComposingChanged event from multiple participants within 5 seconds.

        /// <summary>
        /// Handles event is raised when conversation participant is typing text.
        /// </summary>
        /// <param name="source">InstantMessageModality: The IM modality that is owned by the composing participant.</param>
        /// <param name="data">ComposingChangeEventArgs: Event argument data.</param>
        void myInstantMessageModality_ComposingChanged(Object source, IsTypingChangedEventArgs data)
        {
            if (data.IsTyping == true)
            {
                //****************************************************************
                //Get the name of the conversation participant whose typing status changed.
                //****************************************************************
                string ParticipantName = ((InstantMessageModality)source).Participant.Contact.GetContactInformation(ContactInformationType.DisplayName).ToString();

                //****************************************************************
                //Invoke delegate to update the typing status label contents on the conversation Form.
                //****************************************************************
                this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateLabel, ActivityText_Label, ParticipantName + " is typing" });

                //****************************************************************
                //Start the DispatcherTimer clock. It raises a tick event in 5 seconds.
                //****************************************************************
                this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.StartTimer, _DispatcherTimer_DisplayComposing, null });
            }
        }

MessageReceived Event

The following example handles the message received event on an InstantMessageModality instance. The source parameter is the conversation participant that sent the message.

If you want to be notified if the local user sends an IM, register for this event on the local participant’s InstantMessageModality. To be notified when a user receives an IM, register for the InstantMessageModality on each remote participant’s IM modality.

Tip

If an IM is received and is formatted in a MIME type that your application does not support, then you get the IM text by reading the MessageSentEventArgs.Text property that returns an unformatted string that contains the message text.

        /// <summary>
        /// Handles the event that is raised if an IM is sent on the conversation
        /// </summary>
        /// <param name="source">InstantMessageModality Modality </param>
        /// <param name="data">SendMessageEventArgs The new message.</param>
        void myInstantMessageModality_MessageReceived(Object source, MessageSentEventArgs data)
        {
            IDictionary<InstantMessageContentType, string> messageFormatProperty = data.Contents;
            string Sender = (string)((InstantMessageModality)source).Participant.Contact.GetContactInformation(ContactInformationType.DisplayName);

            System.Text.StringBuilder sb = new System.Text.StringBuilder();
            string receivedMessage;
            if (data.Contents.TryGetValue(InstantMessageContentType.Html, out receivedMessage))
            {
                //****************************************************************
                //Add newest message to the conversation history list box data source.
                //****************************************************************
                _ConversationHistory.Add(receivedMessage);
            }
            else if (data.Contents.TryGetValue(InstantMessageContentType.PlainText, out receivedMessage))
            {
                //****************************************************************
                //Wrap plain text in DIV tags for consumption by the WebBrowser control.
                //****************************************************************
                receivedMessage = "<DIV style=\"font-size:" +
                    "10pt;font-family:" +
                    "MS Shell Dlg 2;color: #463939;direction: ltr\">" +
                    receivedMessage +
                    "</DIV>";
                _ConversationHistory.Add(receivedMessage);
            }
            else
            {
                //****************************************************************
                //Wrap message text in DIV tags for consumption by the WebBrowser control.
                //****************************************************************
                receivedMessage = "<DIV style=\"font-size:" +
                    "10pt;font-family:" +
                    "MS Shell Dlg 2;color: #463939;direction: ltr\">" +
                    data.Text +
                    "</DIV>";
                _ConversationHistory.Add(receivedMessage);
            }

            //****************************************************************
            //Invoke delegate to update conversation history list box on UI thread.
            //****************************************************************
            object[] actionObjectArray = { Sender, _ConversationHistory };
            this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.SetListContents, History_WebBrowser, actionObjectArray });

        }

Lync 2010 API Callback Methods

The performance of three asynchronous operations that are started by the conversation window can be compromised by network latency. To optimize the performance of the operations, the application code uses callback methods to ensure the UI thread is not blocked while the operations run. In the following examples, the send message and set composing operations are completed by calling End[opName].

Send Message Callback

The following example finishes the send message operation, clears the input message text box, and handles all Lync 2010 client exceptions that are raised by the operation. The application sets the asynchronous state object in the InstantMessageModality.BeginSendMessage method call. The following callback method example gets the InstantMessageModality instance from the asynchronous state and then calls InstantMessageModality.EndSendMessage on that instance. The EndSendMessage instance must be called on the same modality instance that is used to call BeginSendMessage.

        /// <summary>
        /// Asynchronous callback method that is invoked by InstantMessageModality instance after SendMessage operation is completed
        /// </summary>
        /// <param name="_asyncOperation">IAsyncResult The operation result</param>
        /// 
        private void SendMessageCallback(IAsyncResult ar)
        {
            if (ar.IsCompleted == true)
            {
                try
                {
                    ((InstantMessageModality)ar.AsyncState).EndSendMessage(ar);

                    //*****************************************************
                    //Clear the message input text box.
                    //*****************************************************
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.ClearText, Message_TextBox, null });

                }
                catch (LyncClientException ex)
                {
                    MessageBox.Show("Contact cannot receive instant messages. Lync Client Exception: " + ex.Message);
                }
            }
        }

Set Composing Callback

The following example finishes the set-composing operation and handles all exceptions that are raised by the operation.

Important

If the application is coded to send a typing status update every time that the local user types a character, then BeginSetCompising and EndSetComposing should be called by using this asynchronous pattern. If the asynchronous pattern is not used, then your UI thread will block a user from typing another character until the set-composing operation is completed.

        /// <summary>
        /// Called by Platform if set-composing operation is completed
        /// </summary>
        /// <param name="ar"></param>
        private void ComposingCallback(System.IAsyncResult ar)
        {
            try
            {
                ((InstantMessageModality)_Conversation.Modalities[ModalityTypes.InstantMessage]).EndSetComposing(ar);
            }
            catch (LyncClientException) { }
        }

Conclusion

Part 4 discusses the Microsoft Lync 2010 API event handlers that are used to write the application code for the IM conversion window. The Microsoft Windows Form controls that are used to develop the IM conversation window that appears in Figure 1 can also be used to develop a WPF or Microsoft Silverlight application.

Building Lync IM Conversation Windows: Helper Classes (Part 5 of 5) discusses how to add Microsoft Bing spelling checker web service calls to the application.

Additional Resources

For more information, see the following resources:

About the Author

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.