Building Lync IM Conversation Windows: Helper Methods (Part 3 of 5)

Summary:   This article is the third 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 IM application helper methods that are used to update the application UI and call Microsoft Lync 2010 API methods.

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

  • TakeFormAction Method

  • SignUserIn Method

  • StartConversation Method

  • AddContactToConversation Method

  • canReceiveFormattedIM Method

  • Conclusion

  • Additional Resources

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

Introduction

Building a Lync IM Conversation Window: Window Control Event Handlers (Part 2 of 5) describes how to develop a Lync 2010 conversation window with Microsoft Lync 2010 API, and Microsoft Windows Presentation Foundation (WPF) or Microsoft Windows Forms. Part 3 describes the conversation window application code that is used to update window control properties on the UI thread, shows how to sign a user in to Lync 2010, start a conversation, and then add a contact to a conversation. Building Lync IM Conversation Windows: Lync 2010 API Event Handlers (Part 4 of 5) describes the methods in the conversation form that are used to handle events that are raised by Lync 2010 in response to state changes. For example, a state change can include the addition of a new conversation to a collection of active conversations, or an IM that is received from a remote conversation participant.

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

TakeFormAction Method

This method is invoked from several of the application methods that handle the Lync 2010 API events on the Lync 2010 platform thread.

.NET Framework 3.5, the target framework for the application, runs in debug mode throughout design time. This configuration raises cross-thread exceptions if you update control properties on the conversation window by using event handlers and callback methods that are invoked on the Lync 2010 platform thread. To avoid these exceptions, declare a delegate that takes an enumeration of custom form actions, an object that represents the control properties that are updated, and an object that represents the new control property value.

The following example updates controls on the Microsoft Windows Form UI. TakeFormAction is invoked by using the FormActionDelegate delegate that is declared in Building a Lync IM Conversation Window: Window Control Event Handlers (Part 2 of 5).

        /// <summary>
        /// Updates a control property on the UI thread.
        /// </summary>
        /// <param name="actionToTake">FormActions. Enumerates the action to take on the Form UI control.</param>
        /// <param name="actionObject">object. The Form UI control whose property is updated.</param>
        /// <param name="actionData">object. The new property value of the control to be updated.</param>
        private void TakeFormAction(FormActions actionToTake, object actionObject, object actionData)
        {
            try
            {
                System.Windows.Window thisForm;
                switch (actionToTake)
                {
                    case FormActions.CloseForm: //Close conversation window.
                        thisForm = (System.Windows.Window)actionObject;
                        thisForm.Close();
                        break;
                    case FormActions.DisplayDefaultCursor: //Display the default cursor
                        this.Cursor = Cursors.Arrow;
                        break;
                    case FormActions.DisplayWaitCursor:    //Display the wait cursor
                        this.Cursor = Cursors.Wait;
                        break;
                    case FormActions.EnableButton:         //Enable the specified button.
                        ((Button)actionObject).IsEnabled = true;
                        break;
                    case FormActions.DisableButton:       //Disable the specified button.
                        ((Button)actionObject).IsEnabled = false;
                        break;
                    case FormActions.UpdateLabel:        //Update the text property of the specified label.
                        Label labelToUpdate = (Label)actionObject;
                        labelToUpdate.Content = (string)actionData;
                        break;
                    case FormActions.UpdateWindowTitle:
                        thisForm = (System.Windows.Window)actionObject;
                        thisForm.Title = (string)actionData;
                        break;
                    case FormActions.ClearText:          //Clear the text property of the specified text box.
                        System.Windows.Controls.TextBox textBoxToUpdate = (System.Windows.Controls.TextBox)actionObject;
                        textBoxToUpdate.Text = string.Empty;
                        break;
                    case FormActions.StartTimer:
                        _DispatcherTimer_DisplayComposing.Start();
                        break;
                    case FormActions.StopTimer:
                        _DispatcherTimer_DisplayComposing.Stop();
                        break;
                    case FormActions.DisplayTypingStatus:
                        ActivityText_Label.Content = "";
                        _DispatcherTimer_DisplayComposing.Start();
                        break;
                    case FormActions.SetListContents:   //Clear and update the contents of the web browser conversation history.

                        //***************************************************
                        //Cast action data to array of object.
                        //***************************************************
                        object[] actionDataArray = (object[])actionData;

                        //***************************************************
                        //Get the arraylist of conversation history out of the action data array.
                        //***************************************************
                        ArrayList historyArray = (ArrayList)actionDataArray[1];

                        //***************************************************
                        //Get the name of the message sender out of first element of action data. 
                        //***************************************************
                        string sender = (string)actionDataArray[0];


                        //***************************************************
                        //Get the newest message text out of the conversation history array. 
                        //***************************************************
                        string receivedMessage = historyArray[historyArray.Count - 1].ToString();

                        //***************************************************
                        //Cast the object of action to the control type to be updated. 
                        //***************************************************
                        WebBrowser webBrowserToUpdate = (WebBrowser)actionObject;

                        //***************************************************
                        //Concatenate the previous history to the new message. 
                        //***************************************************

                        _ConversationHistoryHtml.Insert(0,  sender + receivedMessage );
                        webBrowserToUpdate.NavigateToString(_ConversationHistoryHtml.ToString());
                        
                        //***************************************************
                        //Update the history array to replace the newest message with
                        //the message sender name + the current message text.
                        //***************************************************
                        string recMessage = sender + ": " + receivedMessage;
                        historyArray.RemoveAt(historyArray.Count - 1);
                        historyArray.Add(recMessage);

                        break;
                    case FormActions.SetFormBackColor:   //Set the background color of the Form.
                        System.Windows.Window formToUpdate = (System.Windows.Window)actionObject;
                        formToUpdate.Background = (Brush)actionData;
                        break;

                }
            }
            catch (InvalidCastException ex)
            {
                MessageBox.Show("Invalid cast exception: " + ex.Message, "ConversationForm.TakeFormAction");
            }
        }

SignUserIn Method

The following example helper method signs a user in to Lync 2010. If Lync 2010 is in UI Suppression mode, the example collects the user’s credentials by using the helper MSDNArticleIM.CredentialForm WPF window. For more information about the CredentialForm window and the ClientModel helper class, see Building Lync IM Conversation Windows: Helper Classes (Part 5 of 5).

        /// <summary>
        /// Signs a user in to Lync 2010
        /// </summary>
        private void SignUserIn()
        {
            //********************************************************
            //If the Lync client UI is not suppressed then you do 
            //not collect user sign-in credentials. Instead, the
            //Microsoft Windows network credentials are used.
            //********************************************************
            if (_ClientModel._LyncClient.InSuppressedMode == false && _ClientModel._LyncClient.State != ClientState.SignedIn)
            {
                //********************************************************
                //Call a helper method from the ClientModel helper class. UI
                //thread is blocked until user is signed in.
                //********************************************************
                _ClientModel.SignIn(null, null, null);

                return;
            }

            //********************************************************
            //If Lync 2010 is in UI Suppression mode and not signed in, collect user credentials and sign in.
            //********************************************************
            if (_ClientModel._LyncClient.InSuppressedMode == true && _ClientModel._LyncClient.State != ClientState.SignedIn)
            {
                _CredentialForm = new CredentialForm();
                _CredentialForm.ShowDialog();
                if (_CredentialForm.DialogResult == true)
                {
                    //********************************************************
                    //Client model handles the state check and asynchronous code that is necessary before attempting to
                    //sign a user in.
                    //********************************************************
                    _ClientModel.SignIn(
                        _CredentialForm.userSIP,
                        _CredentialForm.userDomain,
                        _CredentialForm.userPassword);
                }
            }
            this.Cursor = Cursors.Arrow;
        }

StartConversation Method

StartConversation is called after the conversation Form is loaded.

A Lync conversation can be a one-to-one conversation or a one-to-many conversation. A one-to-many conversation is also known as a conference. A conference requires additional host server resources. To access a hosted server conference, a specialized URL that resolves to the hosting server resource is required.

If a conference is initiated remotely, it must be joined locally by using a URL. Both kinds of conversation are encapsulated by the Microsoft.Lync.Model.Conversation.Conversation class, but class instances are obtained through a different process. The following example takes a string parameter that can be a SIP address or a conference URL. If the parameter is a conference URL, the example obtains a conversation by calling into the ConversationManagerJoinConference(string) method. Otherwise, the example calls the ConversationManager.AddConversation method. AddConversation returns a conversation instance synchronously. However, you should wait until the ConversationManager.ConversationAdded event is raised before you try additional operations on the new conversation. The conversation window registers for the ConversationAdded event in the Initialized event for the conversation window.

Tip

The default mode for a new conversation is an inactive state. The conversation becomes active after the first message is sent to another participant.

        /// <summary>
        /// Creates a new conversation
        /// </summary>
        /// <param name="remoteSIP">string. User SIP address or conference Url.</param>
        private void StartConversation()
        {
            //*************************************************************
            //Client cannot start or accept conversations if not signed in.
            //*************************************************************
            if (_ClientModel._LyncClient.State != ClientState.SignedIn)
            {
                return;
            }

            //***********************************************************
            //_targetUri is a class string field that is set to the SIP URI 
            //of a remote user after the user completes the SIPEntry_Textbox control entry 
            //on this window.
            //***********************************************************
            if (_targetUri == null)
            {
                return;
            }

            //***********************************************************
            //User may have passed a conference URL to the conversation window. If
            //this is the case, the new conversation must be a conference and the
            //local user must join to participate.
            //***********************************************************
            if (_targetUri.ToUpper().Contains("conf:"))
            {
                try
                {
                    _Conversation = _ClientModel._LyncClient.ConversationManager.JoinConference(_targetUri);
                }
                catch (LyncClientException) { }
            }

            //***********************************************************
            //The new conversation is not a conference. In this case, a new conversation is created
            //and this application adds contacts.
            //***********************************************************
            else if (_targetUri.ToUpper().Contains("@"))
            {
                //***********************************************************
                //Create a new instance of Conversation.
                //***********************************************************
                _Conversation = _ClientModel._LyncClient.ConversationManager.AddConversation();
                this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateLabel, ActivityText_Label, "Conversation added" });
            }
            else
            {
                MessageBox.Show("You have typed an invalid address. No conversation was started");
            }
        }

AddContactToConversation Method

AddContactToConversation is called from the ConversationManager.ConversationAdded event handler.

A valid contact can be added to a conversation with Lync 2010 API. However, a contact should be added if two conditions are fulfilled. First, the contact must be able to capture and display IM text. Second, the contact should not be offline or in a do-not-disturb state. If either condition is not fulfilled for a contact, an exception is raised on the IM modality after the BeginSendMessage operation is completed.

The following example checks contact capabilities and availability before it adds the contact to a conversation.

        /// Adds a participant to a conversation
        /// </summary>
        /// <param name="pConversation">Contact. Contact to be added to a specific conversation</param>
        /// <param name="pGroupName">string. Name of group where the contact is located.</param>
        /// <param name="pContactUri">string. Contact Uri.  Example: SIP:davidp@contoso.com</param>
        public Boolean AddContactToConversation(Conversation pConversation, Contact remoteContact)
        {
            Boolean returnValue = false;


            if (0 == ((ContactCapabilities)remoteContact.GetContactInformation(ContactInformationType.Capabilities) & ContactCapabilities.RenderInstantMessage))
            {
                //***********************************************************
                //Contact is not available for conversation.
                //***********************************************************
                return false;
            }


            //***********************************************************
            //If the contact is not available to join the conversation now, return a Boolean false value to call code.
            //***********************************************************
            if (((ContactAvailability)remoteContact.GetContactInformation(ContactInformationType.Availability)) == ContactAvailability.DoNotDisturb
                || ((ContactAvailability)remoteContact.GetContactInformation(ContactInformationType.Availability)) == ContactAvailability.Offline)
            {
                return false;
            }
            try
            {
                //***********************************************************
                //Verify that a contact can be added to the conversation.
                //***********************************************************
                if (pConversation.CanInvoke(ConversationAction.AddParticipant))
                {

                    //***********************************************************
                    //Add the contact to the conversation.
                    //***********************************************************
                    pConversation.AddParticipant(remoteContact);

                    //***********************************************************
                    //Update conversation window status-label content with operation results
                    //***********************************************************
                    this.Dispatcher.Invoke(_FormActionDelegate, new object[] { FormActions.UpdateLabel, ActivityText_Label, "Participant added: " + remoteContact.Uri });
                    returnValue = true;
                }
            }
            catch (ItemAlreadyExistException) { }
            return returnValue;
        }

canReceiveFormattedIM Method

The following example determines the source network of a contact. To get the object that is passed in the first parameter, call the Contact.GetContactInformation method and then pass the returned value back to this helper method.

        /// <summary>
        /// Determines the source network type of a contact and returns true if contact can 
        /// receive formatted IM text in addition to plain text 
        /// </summary>
        /// <param name="contactInfo">IDictionary{ContactInformationType, object}. A dictionary of contact information published by remote contact.</param>
        /// <returns></returns>
        private static bool canReceiveFormattedIM(IDictionary<ContactInformationType, object> contactInfo)
        {
            //***********************************************************
            //Formatted IM is not supported in federation with public networks 
            //like Microsoft Windows Live Messenger 2011, Yahoo! Messenger, and AOL Instant Messenger.
            //***********************************************************
            if ((SourceNetworkType)contactInfo[ContactInformationType.SourceNetwork] == SourceNetworkType.FederatedPublic)
            {
                return false;
            }
            return true;
        }

Conclusion

Part 3 discussed the application helper methods in the MSDNArticleIM.ConversationWindow class that handle the multithread environment of a Lync 2010 API application. The helper methods handle various Lync 2010 sign-in scenarios, start conversations, join multiparty conferences, add conversation contacts, and determine whether an IM client can display formatted IM text.

Building Lync IM Conversation Windows: Lync 2010 API Event Handlers (Part 4 of 5) describes the Lync 2010 API event handlers that are added to the IM conversation window.

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.