Click here to Skip to main content
Click here to Skip to main content
Add your own
alternative version

WCF / WPF Chat Application

, 22 Jul 2011
How to create a peer-to-peer chat application using Windows Communication Foundation
Prize winner in Competition "Best C# article of Jun 2007"
chatters.zip
Chatters
chat.png
ChatService
bin
Release
ChatService.exe
ChatService.pdb
ChatService.vshost.exe
ClassDiagram1.cd
Properties
Common
bin
Debug
Person.dll
Person.pdb
Person.vshost.exe
ClassDiagram1.cd
Properties
WPFChatter
bin
Debug
Person.dll
Person.pdb
WPFChat.exe
WPFChat.pdb
WPFChat.vshost.exe
chat.png
ClassDiagram1.cd
Resources
VaneWebSpiderLP.zip
VaneWebSpiderLP
VainWebSpider.suo
VaneWebSpider
bin
Debug
PieChart.dll
VainWebSpider.exe
VainWebSpider.vshost.exe
VainWebSpider.vshost.exe.manifest
Release
PieChart.dll
Properties
Settings.settings
Resources
arrow.png
exit.png
hide.png
LinkIcon.png
Person.png
ShowForm.png
Squares.png
Thumbs.db
TopBanner.png
WebAgent.ico
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)

About the Author

Sacha Barber
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 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

| Advertise | Privacy | Mobile
Web03 | 2.8.140721.1 | Last Updated 22 Jul 2011
Article Copyright 2007 by Sacha Barber
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid