Click here to Skip to main content
15,891,184 members
Articles / Programming Languages / C#

WCF / WPF Chat Application

Rate me:
Please Sign up or sign in to vote.
4.93/5 (478 votes)
22 Jul 2011CPOL29 min read 1.9M   61.3K   1.2K  
How to create a peer-to-peer chat application using Windows Communication Foundation
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using System.Windows.Threading; //for Dispatcher



using Common;

namespace WPFChatter
{
    #region Window1 class
    /// <summary>
    /// This class represents a window, which may be used to show a list of 
    /// chatters connected to a chat room. A new chat window may be spawned
    /// by clicking on the list items, which will show a <see cref="ChatControl">
    /// ChatControl</see>
    /// 
    /// It achieves this by using the following WPF techniques
    /// 
    /// Data binding
    /// Templates
    /// Animation
    /// Styles
    /// Routed events
    /// Custom controls
    /// Dependancy properties
    /// WCF, service model interaction
    /// Singleton design pattern
    /// Multithreading
    /// Asynchronous delegates
    /// 
    /// This provides the logic for Window1, whilst the Window1.xaml
    /// provides the WPF UI design.
    /// </summary>
    public partial class Window1 : System.Windows.Window
    {
        #region Instance Fields
        Person currPerson;
        bool chatControlShown = false;
        private Proxy_Singleton ProxySingleton = Proxy_Singleton.GetInstance();
        #endregion
        #region Ctor
        /// <summary>
        /// Wires up the UI Element events
        /// </summary>
        public Window1()
        {
            InitializeComponent();
            this.Loaded += delegate { initialiseGUI(); };
            this.Closed += delegate { Window_Closed(); };
            this.SignInControl.AddButtonClick += new RoutedEventHandler(SignInControl_AddButtonClick);
            this.lstChatters.SelectionChanged += new SelectionChangedEventHandler(lstChatters_SelectionChanged);
            this.ChatControl.CloseClick += new RoutedEventHandler(ChatControl_CloseClick);
        }
        #endregion
        #region Callback methods

        /// <summary>
        /// Calls the showChatWindow() method, passing it the sender
        /// parameter
        /// </summary>
        /// <param name="sender">The <see cref="Common.Person">Originator</see> 
        /// chatter</param>
        /// <param name="message">The message</param>
        private void Receive(Person sender, string message)
        {

            showChatWindow(sender);
        }

        /// <summary>
        /// Calls the showChatWindow() method, passing it the sender
        /// parameter
        /// </summary>
        /// <param name="sender">The <see cref="Common.Person">Originator</see> 
        /// chatter</param>
        /// <param name="message">The message</param>
        private void ReceiveWhisper(Person sender, string message)
        {
            showChatWindow(sender);
        }

        /// <summary>
        /// Adds the person input parameter to the internal list of
        /// chatters
        /// </summary>
        /// <param name="sender">The <see cref="Common.Person">new chatter</see> 
        /// </param>
        private void UserEnter(Person person)
        {
            lstChatters.Items.Add(person);
        }

        /// <summary>
        /// Removes the person input parameter to the internal list of
        /// chatters
        /// </summary>
        /// <param name="sender">The <see cref="Common.Person">chatter leaving</see> 
        /// </param>
        private void UserLeave(Person person)
        {
            lstChatters.Items.Remove(person);
        }
        #endregion
        #region Private Methods
        /// <summary>
        /// Initialise the GUI components
        /// </summary>
        private void initialiseGUI()
        {
            this.SignInControl.Visibility = Visibility.Visible;
            this.MainBorder.Visibility = Visibility.Hidden;
            this.mnuBorder.Visibility = Visibility.Hidden;
            Point windowRelativePoint =
                    this.bottomDocker.TranslatePoint(new Point(0, 0), this.MainBorder);
            double bottomDockerTop = windowRelativePoint.Y;
            this.lstChatters.Height = (double)(this.MainBorder.RenderSize.Height - (bottomDockerTop + 20));
        }

        /// <summary>
        /// Close the chat session by calling the <see cref="Proxy_Singleton">
        /// ExitChatSession()</see> method, and closing the application
        /// </summary>
        private void Window_Closed()
        {
            ProxySingleton.ExitChatSession();
            Application.Current.Shutdown();
        }

        /// <summary>
        /// This event occurs when the <see cref="ChatControl">
        /// ChatControl</see> control window is closed.
        /// The hideChatWindow animation is found from the WPF 
        /// resources and is triggered, which hides the 
        /// <see cref="ChatControl">ChatControl</see> 
        /// and the CurrentPerson/OtherPerson properties are
        /// cleared on the <see cref="ChatControl">
        /// ChatControl</see>
        /// </summary>
        /// <param name="sender">ChatControl</param>
        /// <param name="e">The event args</param>
        private void ChatControl_CloseClick(object sender, RoutedEventArgs e)
        {
            if (chatControlShown)
            {
                ChatControl.CurrentPerson = null;
                ChatControl.OtherPerson = null;
                //get Storyboard animation from window resources
                ((Storyboard)this.Resources["hideChatWindow"]).Begin(this);
                chatControlShown = false;
                mnuDisConnect.IsEnabled = true;
            }
        }

        /// <summary>
        /// This event occurs when a item is clicked within the 
        /// list of connected <see cref="Common.Person">chatters</see>
        /// The showChatWindow animation is found from the WPF 
        /// resources and is triggered, which shows the 
        /// <see cref="ChatControl">ChatControl</see> 
        /// and the CurrentPerson/OtherPerson properties are
        /// set on the <see cref="ChatControl">
        /// ChatControl</see>
        /// </summary>
        /// <param name="otherPerson">The <see cref="Common.Person">other chatter to talk to</see></param>
        private void showChatWindow(Person otherPerson)
        {
            if (!chatControlShown)
            {
                ChatControl.CurrentPerson = currPerson;
                ChatControl.OtherPerson = otherPerson;
                ChatControl.Visibility = Visibility.Visible;
                //get Storyboard animation from window resources
                ((Storyboard)this.Resources["showChatWindow"]).Begin(this);
                chatControlShown = true;
                mnuDisConnect.IsEnabled = false;
            }
        }

        /// <summary>
        /// Gets the <see cref="Common.Person">selected chatter to talk to</see> 
        ///  and calls the showChatWindow() method
        /// </summary>
        /// <param name="sender">lstChatters</param>
        /// <param name="e">The event args</param>
        private void lstChatters_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            showChatWindow(lstChatters.SelectedItem as Person);
        }

        /// <summary>
        /// Close the chat session by calling the <see cref="Proxy_Singleton">
        /// ExitChatSession()</see> method
        /// </summary>
        private void mnuExit_Click(object sender, RoutedEventArgs e)
        {
            ProxySingleton.ExitChatSession();
        }

        /// <summary>
        /// Close the chat session by calling the <see cref="Proxy_Singleton">
        /// ExitChatSession()</see> method, and displaying the disconnected status
        /// within the <see cref="ChatControl">ChatControl</see> recieved messages
        /// text box
        /// </summary>
        private void mnuDisConnect_Click(object sender, RoutedEventArgs e)
        {
            ProxySingleton.ExitChatSession();
            this.ChatControl.AppendText("Disconnected at " + DateTime.Now.ToString() + Environment.NewLine);
        }

        /// <summary>
        /// Uses the data entered on the <see cref="SignInControl">SignInControl</see>
        /// to initialise certain UI elements. And also creates a new 
        /// <see cref="Proxy_Singleton">ProxySingleton</see> and subscribes to its
        /// ProxyEvent/ProxyCallBackEvent events
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void SignInControl_AddButtonClick(object sender, RoutedEventArgs e)
        {
            lstChatters.Items.Clear();
            currPerson = this.SignInControl.CurrentPerson;
            //connect to proxy, and subscribe to its events
            ProxySingleton.Connect(currPerson);
            ProxySingleton.ProxyEvent += new Proxy_Singleton.ProxyEventHandler(ProxySingleton_ProxyEvent);
            //one event subscription for the embedded ChatControl
            ProxySingleton.ProxyCallBackEvent += new Proxy_Singleton.ProxyCallBackEventHandler(this.ChatControl.ProxySingleton_ProxyCallBackEvent);
            //one event subscription for this window
            ProxySingleton.ProxyCallBackEvent += new Proxy_Singleton.ProxyCallBackEventHandler(this.ProxySingleton_ProxyCallBackEvent);
            //set the UI elements using signin data
            this.photoBig.Source = new BitmapImage(new Uri(currPerson.ImageURL));
            this.SignInControl.Visibility = Visibility.Hidden;
            this.MainBorder.Visibility = Visibility.Visible;
            this.mnuBorder.Visibility = Visibility.Visible;
            this.lblInstructions.Content = "You can click on the gridview below to launch a chat window. When the window is opened you may\r\n" +
                                        "either chat using either the Whisper button which will ONLY chat to the person you selected in\r\n" +
                                        "the gridview, or you can use the Say button, which will allow ALL connected people to see the message.\r\n";
            this.txtPerson.Content = "Hello " + currPerson.Name;
        }


        /// <summary>
        /// A delegate to allow a cross UI thread call to be marshalled to correct
        /// UI thread
        /// </summary>
        private delegate void ProxySingleton_ProxyEvent_Delegate(object sender, ProxyEventArgs e);

        /// <summary>
        /// This method checks to see if the current thread needs to be marshalled
        /// to the correct (UI owner) thread. If it does a new delegate is created
        /// which recalls this method on the correct thread
        /// </summary>
        /// <param name="sender"><see cref="Proxy_Singleton">ProxySingleton</see></param>
        /// <param name="e">The event args</param>
        private void ProxySingleton_ProxyEvent(object sender, ProxyEventArgs e)
        {
            if (!this.Dispatcher.CheckAccess())
            {
                this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,
                new ProxySingleton_ProxyEvent_Delegate(ProxySingleton_ProxyEvent),
                sender, new object[] { e });
                return;
            }
            //now marshalled to correct thread so proceed
            foreach (Person person in e.list)
            {
                lstChatters.Items.Add(person);
            }
            this.ChatControl.AppendText("Connected at " + DateTime.Now.ToString() + " with user name " + currPerson.Name + Environment.NewLine);
        }


        /// <summary>
        /// This method is called when ever the <see cref="Proxy_Singleton">
        /// Proxy</see> ProxyCallBackEvent is raised, in response to an incoming message
        /// When this method is called it will examine the events 
        /// ProxyCallBackEventArgs to see what type of message is being recieved, 
        /// and will then call the correspoding method. 
        /// <see cref="ChatProxy">Receive/ReceiveWhisper</see> are not dealt with 
        /// as they are not relevant to this control. They are dealt with by
        /// <see cref="ChatControl">the ChatControl</see>
        /// </summary>
        /// <param name="sender">the sender, which is not used</param>
        /// <param name="e">The ChatEventArgs</param>
        private void ProxySingleton_ProxyCallBackEvent(object sender, ProxyCallBackEventArgs e)
        {
            switch (e.callbackType)
            {
                case CallBackType.Receive:
                    Receive(e.person, e.message);
                    break;
                case CallBackType.ReceiveWhisper:
                    ReceiveWhisper(e.person, e.message);
                    break;
                case CallBackType.UserEnter:
                    UserEnter(e.person);
                    break;
                case CallBackType.UserLeave:
                    UserLeave(e.person);
                    break;

            }
        }
        #endregion
    }
    #endregion
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I currently hold the following qualifications (amongst others, I also studied Music Technology and Electronics, for my sins)

- MSc (Passed with distinctions), in Information Technology for E-Commerce
- BSc Hons (1st class) in Computer Science & Artificial Intelligence

Both of these at Sussex University UK.

Award(s)

I am lucky enough to have won a few awards for Zany Crazy code articles over the years

  • Microsoft C# MVP 2016
  • Codeproject MVP 2016
  • Microsoft C# MVP 2015
  • Codeproject MVP 2015
  • Microsoft C# MVP 2014
  • Codeproject MVP 2014
  • Microsoft C# MVP 2013
  • Codeproject MVP 2013
  • Microsoft C# MVP 2012
  • Codeproject MVP 2012
  • Microsoft C# MVP 2011
  • Codeproject MVP 2011
  • Microsoft C# MVP 2010
  • Codeproject MVP 2010
  • Microsoft C# MVP 2009
  • Codeproject MVP 2009
  • Microsoft C# MVP 2008
  • Codeproject MVP 2008
  • And numerous codeproject awards which you can see over at my blog

Comments and Discussions