5,154,487 members and growing! (19,376 online)
Email Password   helpLost your password?
Platforms, Frameworks & Libraries » Windows Communication Foundation » General     Intermediate

WCF / WPF Chat Application

By Sacha Barber

How to create a peer-to-peer chat application using Windows Communication Foundation
C# 3.0, C#, Windows, .NET, .NET 3.0, WCF, VS2005, VS, Dev

Posted: 25 Jul 2007
Updated: 27 Jul 2007
Views: 150,487
Announcements



Search    
Advanced Search
Sitemap
Prize winner in Competition "Best C# article of Jun 2007"
184 votes for this Article.
Popularity: 11.00 Rating: 4.86 out of 5
3 votes, 1.6%
1
0 votes, 0.0%
2
1 vote, 0.5%
3
9 votes, 4.9%
4
170 votes, 92.9%
5

Contents

Introduction

For those that have read some of my other CodeProject.com articles, you will probably know that I am not shy about trying out new technologies. One good thing about that is that I generally share what I learn right here and this article is one of the hardest ones I've done, IMHO. This article is about how to create a peer-to-peer chat application using Windows Communication Foundation (WCF) and also how to make it look nice using Windows Presentation Foundation (WPF).

When I first started reading about WCF, the first place I looked was the MSDN WCF Samples (which I read a lot), but they weren't that great. I also found lots of chat apps based on the MSDN versions, which were no good, as they could not return the list of users within the chat application. I wanted to create a nice WPF styled app with the list of connected chatters.

So I hunted around a bit more and eventually came across a damn fine/brilliant article by Nikola Paljetak, which I have tailored for this article. I have OK'd this with Nikola and the original article content is here. To be honest, the original article was pure brilliance (it should be mentioned that Nikola is a Professor), but it took a while for me to get what was going on, as the code wasn't commented. I have now commented all code, so I think it will still make a very nice discussion/article for those who are new to WCF/WPF. I was totally new to WCF before this article, so if I can do it, so can all of you.

So that's what this article is all about. At the end of the article, I would hope you understand at least some of the key WCF areas and possibly be inspired enough to look at the WPF side of this article, also.

A Note About the Demo App

Before I start bombarding people with the inner mechanisms of the attached WPF/WCF application, shall we have a quick look at the finished product? There are 3 main areas within the attached demo application:

A login screen

Screenshot - SignIn.png

A main window, from which the user can choose who to chat with:

Screenshot - Main.png

And a window where chatters may openly chat:

Screenshot - Chat.png

The application is based on using Visual Studio 2005 with The Visual Studio Designer for WPF installed, or using Expression BLEND and Visual Studio 2005 combination, or Wordpad if you prefer to write stuff in that. Obviously, as it's a WPF/WCF application, you will also need the .NET 3.0 Framework. This application will cover the following concepts:

  • WCF
    • The new service orientated attributes
    • The use of interfaces
    • The use of callbacks
    • Asynchronous delegates
    • Creating the proxy
  • WPF
    • Styles
    • Templates
    • Animations
    • Databinding
    • Multithreading a WPF application

However, this application is not really that orientated to WPF, as that is covered in numerous other WPF articles at The Code Project. The WPF stuff is really just a wrapper around the WCF article, which is the real guts of this article. Although there is some nice WPF stuff going on, just to make the chat application look nicer than an ordinary console application. I will, however, discuss interesting bits of the WPF implementation.

Prerequisites

  1. To run the code supplied with this article, you will need to install the May 2006 LINQ CTP, which is available here. There is a new March 2007 CTP available, but it's about 4GB for the full install (as its not just LINQ but the entire next generation of Visual Studio codenamed "Orcas") and quite fiddly, and probably going to change anyway. So, the May 2006 LINQ CTP will be OK for the purpose of what this article is trying to demonstrate.
  2. The .NET 3.0 Framework, which is available for download here.

A Brief Overview of the Demo App and What We are Trying to Achieve

In the attached demo application, we are trying to carry our the following functionality:

  1. Allow chatters to pick a name for themselves and pick an image (Avatar) that they would like to be presented by
  2. Allow the chatters to join a peer-to-peer chat
  3. Allow chatters to see who else is available to chat to
  4. Allow chatters to send private message
  5. Allow chatters to send global messages
  6. Allow chatters to leave the chat environment
  7. Make it all look pretty using WPF (not strictly a requirement for a chat app, but I like WPF, so humour me)

In order to achieve all of this I have developed 3 separate assemblies, which by the end I hope you will understand.

  • ChatService.Dll: the WCF chat server, that allows chat clients to connect and is the main message router
  • Common.Dll: is a simple serializable class which is used by both the ChatService.Dll and the WPFChatter.Dll files
  • WPFChatter.Dll: the pretty WPF wrapper around the client WCF functions

Some Key Concepts Explained

There are a number of key concepts that were mentioned earlier that need to be explained in order for the full application (which covers a lot of ground) to be understood. So I'll just explain each of these a little bit at a time, so the the final application should be a little easier to explain (well that's the idea anyway).

WCF: the New Service Orientated Attributes

There are a number of new attributes that may be used with WCF to adorn our NET classes/interfaces, shown below are the ones that are used as part of the attached demo application.

ServiceContractAttribute

Indicates that an interface or a class defines a service contract in a Windows Communication Foundation (WCF) application. It has the following members:

Name Description
CallbackContract Gets or sets the type of callback contract when the contract is a duplex contract.
ConfigurationName Gets or sets the name used to locate the service in an application configuration file.
HasProtectionLevel Gets a value that indicates whether the member has a protection level assigned.
Name Gets or sets the name for the <portType> element in Web Services Description Language (WSDL).
Namespace Gets or sets the namespace of the <portType> element in Web Services Description Language (WSDL).
ProtectionLevel Specifies whether the binding for the contract must support the value of the ProtectionLevel property.
SessionMode Gets or sets whether sessions are allowed, not allowed or required.
TypeId (Inherited from Attribute)

See the MSDN article for more details.

OperationContractAttribute

Indicates that a method defines an operation that is part of a service contract in a Windows Communication Foundation (WCF) application. It has the following members:

Name Description
Action Gets or sets the WS-Addressing action of the request message.
AsyncPattern Indicates that an operation is implemented asynchronously using a Begin<methodName> and End<methodName> method pair in a service contract.
HasProtectionLevel Gets a value that indicates whether the messages for this operation must be encrypted, signed, or both.
IsInitiating Gets or sets a value that indicates whether the method implements an operation that can initiate a session on the server (if such a session exists).
IsOneWay Gets or sets a value that indicates whether an operation returns a reply message.
IsTerminating Gets or sets a value that indicates whether the service operation causes the server to close the session after the reply message, if any, is sent.
Name Gets or sets the name of the operation.
ProtectionLevel Gets or sets a value that specifies whether the messages of an operation must be encrypted, signed, or both.
ReplyAction Gets or sets the value of the SOAP action for the reply message of the operation.
TypeId (Inherited from Attribute)

See the MSDN article for more details

ServiceBehaviorAttribute

Specifies the internal execution behavior of a service contract implementation. It has the following members:

Name Description
AddressFilterMode Gets or sets the AddressFilterMode that is used by the dispatcher to route incoming messages to the correct endpoint.
AutomaticSessionShutdown Specifies whether to automatically close a session when a client closes an output session.
ConcurrencyMode Gets or sets whether a service supports one thread, multiple threads, or reentrant calls.
ConfigurationName Gets or sets the value used to locate the service element in an application configuration file.
IgnoreExtensionDataObject Gets or sets a value that specifies whether to send unknown serialization data onto the wire.
IncludeExceptionDetailInFaults Gets or sets a value that specifies that general unhandled execution exceptions are to be converted into a System.ServiceModel.FaultException of type System.ServiceModel.ExceptionDetail and sent as a fault message. Set this to true only during development to troubleshoot a service.
InstanceContextMode Gets or sets the value that indicates when new service objects are created.
MaxItemsInObjectGraph Gets or sets the maximum number of items allowed in a serialized object.
Name Gets or sets the value of the name attribute in the service element in Web Services Description Language (WSDL).
Namespace Gets or sets the value of the target namespace for the service in Web Services Description Language (WSDL).
ReleaseServiceInstanceOnTransactionComplete Gets or sets a value that specifies whether the service object is released when the current transaction completes.
TransactionAutoCompleteOnSessionClose Gets or sets a value that specifies whether pending transactions are completed when the current session closes without error.
TransactionIsolationLevel Specifies the transaction isolation level for new transactions created inside the service, and incoming transactions flowed from a client.
TransactionTimeout Gets or sets the period within which a transaction must complete.
TypeId (Inherited from Attribute)
UseSynchronizationContext Gets or sets a value that specifies whether to use the current synchronization context to choose the thread of execution.
ValidateMustUnderstand Gets or sets a value that specifies whether the system or the application enforces SOAP MustUnderstand header processing.

See the MSDN article or more details. Here is an example of how these new WCF attributes are used within the demo application, Service project -> ChatService.cs.

[ServiceContract(SessionMode = SessionMode.Required, 
    CallbackContract = typeof(IChatCallback))]
interface IChat
{
    [OperationContract(IsOneWay = true, IsInitiating = false, 
        IsTerminating = false)]
    void Say(string msg);

    [OperationContract(IsOneWay = true, IsInitiating = false, 
        IsTerminating = false)]
    void Whisper(string to, string msg);

    [OperationContract(IsOneWay = false, IsInitiating = true, 
        IsTerminating = false)]
    Person[] Join(Person name);
    
    [OperationContract(IsOneWay = true, IsInitiating = false, 
        IsTerminating = true)]
    void Leave();
}

WCF: the Use of Interfaces

"The notion of a contract is the key to building a WCF service. Those of you that have a background in classic DCOM or COM technologies might be surprised to know that WCF contracts are expressed using interface-based programming techniques (everything old is new again!). While not mandatory, the vast amount your WCF applications will begin by defining a set of .NET interface types that are used to represent the set of members a given WCF type will support. Specifically speaking, interfaces that represent a WCF contract are termed service contracts. The classes (or structures) that implement them are termed service types."

Pro C# with .NET3.0, Apress. Andrew Troelsen

So there you are -- that's what a nice new book says -- but what does this look like to us in code? Well the actual ChatService.cs class implements the IChat interface just mentioned, and so looks like this:

[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, 
    ConcurrencyMode = ConcurrencyMode.Multiple)]
public class ChatService : IChat
{

}

WCF: the Use of Callbacks

Recall earlier, when I mentioned the ServiceContractAttribute I also mentioned that one of its properties was a CallBackContract that was defined as, which allows the WCF service to call the client back. This new WCF method is very nice. I remember trying to do something like this with remoting and events back to the client, and it was not fun at all. I much prefer this method. Let's have a look. Recall that the original service contract interface was declared like this:

[ServiceContract(SessionMode = SessionMode.Required, 
    CallbackContract = typeof(IChatCallback))]
interface IChat
{
....
}

Well we still need to define an interface to allow the callback to work, so an example of this may be (as done in the demo app):

interface IChatCallback
{
    [OperationContract(IsOneWay = true)]
    void Receive(Person sender, string message);

    [OperationContract(IsOneWay = true)]
    void ReceiveWhisper(Person sender, string message);

    [OperationContract(IsOneWay = true)]
    void UserEnter(Person person);

    [OperationContract(IsOneWay = true)]
    void UserLeave(Person person);
}

WCF: Asynchronous Delegates

"The .NET Framework allows you to call any method asynchronously. To do this you define a delegate with the same signature as the method you want to call; the common language runtime automatically defines BeginInvoke and EndInvoke methods for this delegate, with the appropriate signatures.

The BeginInvoke method initiates the asynchronous call. It has the same parameters as the method you want to execute asynchronously, plus two additional optional parameters. The first parameter is an AsyncCallback delegate that references a method to be called when the asynchronous call completes. The second parameter is a user-defined object that passes information into the callback method. BeginInvoke returns immediately and does not wait for the asynchronous call to complete. BeginInvoke returns an IAsyncResult, which can be used to monitor the progress of the asynchronous call.

The EndInvoke method retrieves the results of the asynchronous call. It can be called any time after BeginInvoke; if the asynchronous call has not completed, EndInvoke blocks the calling thread until it completes. "

Calling Synchronous Methods Asynchronously

WCF: Creating the Proxy

In order to for the client to communicate with a WCF service, we need a proxy object. This can be quite a daunting task (and a little complicated to be honest). Luckily like a lot of things in .NET 3.0/3.5 there are tools provided to make our lives easier (you still have to know about them though), and WCF is no different. It has a little tool called "svcutil" which comes to the rescue.

So how do we create a proxy for our little WCF service (ChatService.exe for the demo app) using svcutil. Well I have read one thing that said you should be able to just start the WCF service, point svcutil at the running WCF service, and be able to create the client proxy that way. But I have to say, I could NOT get that to work at all. It seems to a common gripe, if you search the internet. So the way I got it to work was as follows:

  1. Open a visual studio command prompt, and change to the directory that contains the WCF service
  2. Run the following command line: svcutil <YOUR_SERVICE.exe>
  3. This will list a few files, namely *.wsdl, and *.xsd, and a schemas.microsoft.com.2003.10.Serialization.xsd file
  4. Next you need to run the following command line: svcutil *.wsdl *.xsd /language:C# /out:MyProxy.cs /config:app.config
  5. You now have 2 new client files, MyProxy.cs and app.config, so you can copy these to your client application

To give you an idea of what svcutil.exe produces in terms of client files, let's have a look. Here is the MyProxy.cs C# file that was auto-generated by svcutil.exe.

//---------------------------------------------------------------------------

// <auto-generated>

//     This code was generated by a tool.

//     Runtime Version:2.0.50727.312

//

//     Changes to this file may cause incorrect behavior and will be lost if

//     the code is regenerated.

// </auto-generated>

//---------------------------------------------------------------------------

namespace Common
{
    using System.Runtime.Serialization;
    
    
    [System.CodeDom.Compiler.GeneratedCodeAttribute(
        "System.Runtime.Serialization", "3.0.0.0")]
    [System.Runtime.Serialization.DataContractAttribute()]
    public partial class Person : object, 
        System.Runtime.Serialization.IExtensibleDataObject
    {       
        private System.Runtime.Serialization.ExtensionDataObject 
            extensionDataField;
        
        private string ImageURLField;
        
        private string NameField;
        
        public System.Runtime.Serialization.ExtensionDataObject ExtensionData
        {
            get
            {
                return this.extensionDataField;
            }
            set
            {
                this.extensionDataField = value;
            }
        }
        
        [System.Runtime.Serialization.DataMemberAttribute()]
        public string ImageURL
        {
            get
            {
                return this.ImageURLField;
            }
            set
            {
                this.ImageURLField = value;
            }
        }
        
        [System.Runtime.Serialization.DataMemberAttribute()]
        public string Name
        {
            get
            {
                return this.NameField;
            }
            set
            {
                this.NameField = value;
            }
        }
    }
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", 
    "3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="IChat", 
CallbackContract=typeof(IChatCallback), 
    SessionMode=System.ServiceModel.SessionMode.Required)]
public interface IChat
{
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        IsInitiating=false, Action="http://tempuri.org/IChat/Say")]
    void Say(string msg);
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        IsInitiating=false, Action="http://tempuri.org/IChat/Whisper")]
    void Whisper(string to, string msg);
    
    [System.ServiceModel.OperationContractAttribute(
        Action=http://tempuri.org/IChat/Join, 

        ReplyAction="http://tempuri.org/IChat/JoinResponse")]
    Common.Person[] Join(Common.Person name);
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        IsTerminating=true, IsInitiating=false, 
        Action="http://tempuri.org/IChat/Leave")]
    void Leave();
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", 
    "3.0.0.0")]
public interface IChatCallback
{
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        Action="http://tempuri.org/IChat/Receive")]
    void Receive(Common.Person sender, string message);
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        Action="http://tempuri.org/IChat/ReceiveWhisper")]
    void ReceiveWhisper(Common.Person sender, string message);
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        Action="http://tempuri.org/IChat/UserEnter")]
    void UserEnter(Common.Person person);
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        Action="http://tempuri.org/IChat/UserLeave")]
    void UserLeave(Common.Person person);
}

[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", 
    "3.0.0.0")]
public interface IChatChannel : IChat, System.ServiceModel.IClientChannel
{
}

[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", 
    "3.0.0.0")]
public partial class ChatClient : 
    System.ServiceModel.DuplexClientBase<IChat>,
    IChat
{
    
    public ChatClient(System.ServiceModel.InstanceContext callbackInstance) : 
            base(callbackInstance)
    {
    }
    
    public ChatClient(System.ServiceModel.InstanceContext callbackInstance, 
        string endpointConfigurationName) : 
            base(callbackInstance, endpointConfigurationName)
    {
    }
    
    public ChatClient(System.ServiceModel.InstanceContext callbackInstance, 
        string endpointConfigurationName, string remoteAddress) : 
            base(callbackInstance, endpointConfigurationName, remoteAddress)
    {
    }
    
    public ChatClient(System.ServiceModel.InstanceContext callbackInstance, 
            string endpointConfigurationName, 
                System.ServiceModel.EndpointAddress remoteAddress) : 
            base(callbackInstance, endpointConfigurationName, remoteAddress)
    {
    }
    
    public ChatClient(System.ServiceModel.InstanceContext callbackInstance, 
            System.ServiceModel.Channels.Binding binding, 
                System.ServiceModel.EndpointAddress remoteAddress) : 
            base(callbackInstance, binding, remoteAddress)
    {
    }
    
    public void Say(string msg)
    {
        base.Channel.Say(msg);
    }
    
    public void Whisper(string to, string msg)
    {
        base.Channel.Whisper(to, msg);
    }
    
    public Common.Person[] Join(Common.Person name)
    {
        return base.Channel.Join(name);
    }
    
    public void Leave()
    {
        base.Channel.Leave();
    }
}

And here is the client App.Config that was auto-generated by svcutil.exe.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <system.serviceModel>
        <bindings>
            <basicHttpBinding>
                <binding name="DefaultBinding_IChat" closeTimeout="00:01:00"
                    openTimeout="00:01:00" receiveTimeout="00:10:00" 
                    sendTimeout="00:01:00"
                    allowCookies="false" bypassProxyOnLocal="false" 
                    hostNameComparisonMode="StrongWildcard"
                    maxBufferSize="65536" maxBufferPoolSize="524288" 
                    maxReceivedMessageSize="65536"
                    messageEncoding="Text" textEncoding="utf-8" 
                    transferMode="Buffered"
                    useDefaultWebProxy="true">

                    <readerQuotas maxDepth="32" 
                       maxStringContentLength="8192"
                       maxArrayLength="16384"
                       maxBytesPerRead="4096" 
                       maxNameTableCharCount="16384" />
                    <security mode="None">
                        <transport clientCredentialType="None" 
                            proxyCredentialType="None" realm="" />
                        <message clientCredentialType="UserName" 
                            algorithmSuite="Default" />
                    </security>
                </binding>

            </basicHttpBinding>
        </bindings>
        <client>
            <endpoint binding="basicHttpBinding" 
                bindingConfiguration="DefaultBinding_IChat"
                contract="IChat" name="DefaultBinding_IChat_IChat" />
        </client>
    </system.serviceModel>

</configuration>

So as you can see, these files can simply be used straightaway within your own client application to communicate with the WCF service. But wait, we are still not finished with svcutil.exe. Recall that I mentioned asynchronous delegates -- so why did I do that? Well the svcutil.exe also allows us to create asynchronous proxy code, using one of the command line switches. To do this, we use the following command line (notice the /a option):

svcutil *.wsdl *.xsd /a /language:C# /out:MyProxy.cs /config:app.config

...instead of:

svcutil *.wsdl *.xsd /language:C# /out:MyProxy.cs /config:app.config

...which we used previously. This will then change the format of the C# (or VB .NET) file we get out. What we get now for each WCF service method is an asynchronous one. So, we would get the following:

    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        IsInitiating=false, Action="http://tempuri.org/IChat/Say")]
    void Say(string msg);
    
    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        IsInitiating=false, AsyncPattern=true, 
        Action="http://tempuri.org/IChat/Say")]
    System.IAsyncResult BeginSay(string msg, System.AsyncCallback callback, 
        object asyncState);
    
    void EndSay(System.IAsyncResult result);

...instead of:

    [System.ServiceModel.OperationContractAttribute(IsOneWay=true, 
        IsInitiating=false, Action="http://tempuri.org/IChat/Say")]
    void Say(string msg);

Hopefully you can see where this ties in with the WCF: Asynchronous Delegates section mentioned earlier. But just to be sure, here's a more detailed description of what's going on. Using the /a switch of svcutil.exe, you can generate a proxy that contains asynchronous methods in addition to the synchronous ones. For each operation in the original contract, the asynchronous proxy and contract will contain two additional methods of this form:

The OperationContractAttribute offers the AsyncPattern Boolean property. AsyncPattern only has meaning on the client-side copy of the contract. You can only set AsyncPattern to true on a method with a Begin<Operation>( )-compatible signature. The defining contract must also have a matching method with an End<Operation>( )-compatible signature. These requirements are verified at proxy load time. What AsyncPattern does is bind the underlying synchronous method with the Begin/End pair, and correlates the synchronous execution with the asynchronous one.

When the client invokes a method of the form Begin<Operation>( ) with AsyncPattern set to true, it tells WCF not to try to directly invoke a method by that name on the service. Instead, it will use a thread from the thread pool to synchronously call the underlying method (identified by the Action name). The synchronous call will block the thread from the thread pool, not the calling client. The client will only be blocked for the slightest moment it takes to dispatch the call request to the thread pool. The reply method of the synchronous invocation is correlated with the End<Operation>( ) method.

As the attached demo application makes use of asynchronous methods for the Join operation, asynchronous delegates are using within the code to achieve this.

WCF: Configuration

As with all .NET applications, WCF applications allow the application to be configured via a configuration file. This will be discussed later on, for now you just need to know that the following items may be configured in an App.Config file for a WCF application:

  • Service addresses
  • Service type
  • Behavior configuration
  • Endpoints
  • Binding types
  • Service security

WPF: Styles / Templates

WPF styles and templates allow us to change how standard components look. This is quite a well documented feature, but what I will say is that by using a little bit of styling one is able to convert a rather plain ListView into a ListView that looks like the figure shown below. Quite nice, I think. So how is this done? Well, it's all down to styling. The figure below shows a standard ListView item which is styled and has some custom data templates assigned. Each item in the ListView is actually a Common.Person object, which will be discussed later.

Screenshot - listview.png

All that has been done is that I have applied a style to a standard .NET ListView control. Here is the XAML that does this. However, I say I am not going to dwell on these WPF features, as they are well documented and not really part of the main thrust of this article. I just wanted those people reading this that hadn't come across WPF before, to know what can be done with it.

 <Style x:Key="ListViewContainer" TargetType="{x:Type ListViewItem}">
    <Setter Property="FontWeight" Value="Normal"/>
    <Setter Property="Foreground" Value="#FF000000"/>

    <Setter Property="FontFamily" Value="Agency FB"/>

    <Setter Property="FontSize" Value="15"/>
    <Style.Triggers>
      <Trigger Property="IsMouseOver" Value="true">
        <Setter Property="Foreground" Value="Black" />
        <Setter Property="Background">

          <Setter.Value>

            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
              <LinearGradientBrush.GradientStops>
                <GradientStop Color="#D88" Offset="0"/>
                <GradientStop Color="#D31" Offset="1"/>
              </LinearGradientBrush.GradientStops>

            </LinearGradientBrush>

          </Setter.Value>
        </Setter>
        <Setter Property="Cursor" Value="Hand"/>
      </Trigger>
      <MultiTrigger>

        <MultiTrigger.Conditions>

          <Condition Property="IsSelected" Value="true" />
          <Condition Property="Selector.IsSelectionActive" Value="true" />
        </MultiTrigger.Conditions>
        <Setter Property="Background">
          <Setter.Value>

            <LinearGradientBrush StartPoint="0,0" EndPoint="0,1">

              <LinearGradientBrush.GradientStops>
                <GradientStop Color="#0E4791" Offset="0"/>
                <GradientStop Color="#468DE2" Offset="1"/>
              </LinearGradientBrush.GradientStops>
            </LinearGradientBrush>

          </Setter.Value>

        </Setter>
        <Setter Property="Foreground" Value="Black" />
      </MultiTrigger>
    </Style.Triggers>
  </Style>  


  <!-- Gridview Templates  -->

  <DataTemplate x:Key="noTextHeaderTemplate"/>

  <DataTemplate x:Key="textCellTemplate">
    <TextBlock Margin="10,0,0,0" Text="{Binding}" 
        VerticalAlignment="Center"/>
  </DataTemplate>

  <DataTemplate x:Key="imageCellTemplate">

    <Border CornerRadius="2,2,2,2" Width="40" Height="40" 
      Background="#FFFFC934" BorderBrush="#FF000000" Margin="3,3,3,3">
      <Image HorizontalAlignment="Center" VerticalAlignment="Center" 
           Width="Auto" Height="Auto" 
           Source="{Binding Path=ImageURL}" Stretch="Fill" 
           Margin="2,2,2,2"/>
    </Border>
  </DataTemplate>

  .....
  .....

  <ListView DockPanel.Dock="Bottom" Margin="0,-10,0,0"  
        VerticalAlignment="Bottom" 
        x:Name="lstChatters" SelectionMode="Single" 
        ItemContainerStyle="{StaticResource ListViewContainer}" 
        Background="{x:Null}" BorderBrush="#FFFFFBFB" Foreground="#FFB5B5B5"
            Opacity="1" BorderThickness="2,2,2,2" 
        HorizontalAlignment="Stretch" Width="Auto" Height="Auto">

  <ListView.View>
  <GridView>
      <GridView.ColumnHeaderContainerStyle>
          <Style TargetType="GridViewColumnHeader">
              <Setter Property="Visibility" Value="Hidden" />
              <Setter Property="Height" Value="0" />

          </Style>
      </GridView.ColumnHeaderContainerStyle>
      <GridViewColumn Header="Image" 
            HeaderTemplate="{StaticResource noTextHeaderTemplate}" 
            Width="100" CellTemplate="{StaticResource imageCellTemplate}"/>
          <GridViewColumn DisplayMemberBinding="{Binding Path=Name}" 
            Header="First Name" 
            HeaderTemplate="{StaticResource textCellTemplate}" Width="100"/>
      </GridView>
  </ListView.View>

  </ListView>

WPF: Animations

Animations are another element of WPF (again well documented, so I'll not go into it too much). I have not gone too overboard with animations in the demo application, but I do use animation twice (because one simply has to if they are developing WPF stuff).

Once to load the ChatControl and once to hide the ChatControl. The only thing that is special is the way that I am using animation. They are part of the Window1.xaml but the triggers are performed in code behind logic. As this may be useful to some people I'll give a small example.

In Window1.xaml I have declared an animation as follows, the one shown below loads the ChatControl by growing it from 0 X/Y scale to 100% X/Y scale over a period of 1 second, and is triggered when a user clicks a ListView item.

      <!-- Show Chat Window Animation -->
    <Storyboard x:Key="showChatWindow">

      <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
            Storyboard.TargetName="ChatControl" 
            Storyboard.TargetProperty="(UIElement.RenderTransform).(
            TransformGroup.Children)[0].(ScaleTransform.ScaleX)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
        <SplineDoubleKeyFrame KeyTime="00:00:001" Value="1"/>
      </DoubleAnimationUsingKeyFrames>
      <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
            Storyboard.TargetName="ChatControl" 
            Storyboard.TargetProperty="(UIElement.RenderTransform).(
            TransformGroup.Children)[0].(ScaleTransform.ScaleY)">
        <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>

        <SplineDoubleKeyFrame KeyTime="00:00:001" Value="1"/>
      </DoubleAnimationUsingKeyFrames>
    </Storyboard>

I trigger this animation directly from code-behind. So how do I do that? Well, let's have a look at the code, shall we? It's fairly easy; the code to do that is as follows:

//get Storyboard animation from window resources

((Storyboard)this.Resources["showChatWindow"]).Begin(this);

WPF: Databinding

The styled ListView that I am using within Window1.xaml utilizes data binding in order to bind a List of Person objects. This is done by using Templates as already shown in the WPF : Styles/Templates section. But just in case you're not so sure about all this WPF stuff, what actually happens is that I use a DataTemplate which specifies just how the ListView will display its data. To do that, I define the following DataTemplates and these DataTemplates include the Binding values. This allows a collection of Person objects to be bound to the ListView.

  <DataTemplate x:Key="textCellTemplate">

   <TextBlock Margin="10,0,0,0" Text="{Binding}" VerticalAlignment="Center"/>
  </DataTemplate>

  <DataTemplate x:Key="imageCellTemplate">
    <Border CornerRadius="2,2,2,2" Width="40" Height="40" 
        Background="#FFFFC934" BorderBrush="#FF000000" Margin="3,3,3,3">

      <Image HorizontalAlignment="Center" VerticalAlignment="Center" 
         Width="Auto" 
         Height="Auto" Source="{Binding Path=ImageURL}" Stretch="Fill" 
         Margin="2,2,2,2"/>

    </Border>
  </DataTemplate>

WPF: Multithreading a WPF Application

Threading in WPF is quite similar to .NET 2.0/Win forms, you still have the issue of threads that are not on the same owner thread as a UI component needing to be marshaled to the correct thread. The only difference is the keywords. For example, in .NET 2.0, one would probably have done:

if (this.InvokeRequired)
{
    this.Invoke(new EventHandler(delegate
    {
        progressBar1.Value = e.ProgressValue;
    }));
}
else
{
    progressBar1.Value = e.ProgressValue;
}

...while in WPF we would (and I do) use the following syntax. Note : CheckAccess() is marked as false)> so don't be expecting to see it using Intellisense. However, it does work.

        /// <summary>


        /// A delegate to allow a cross UI thread call to be marshaled 

        /// 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);
        }

Well, you know what, if you've got to this point without falling asleep, I think you're ready to deal with the inner workings of the attached demo application(s). It should be child's play now, as we've covered all the key elements. There's nothing new to say, apart from how the demo app uses all this stuff (though, some of it we've already discussed). So it should be just a question of explaining it all now.

How This All Works in the DEMO Application

Well how about we start with a sequence diagram (I know UML isn't that great for distributed apps, so I've annotated it with comments, but I hope you get the general idea).

Screenshot - Join_Operations.png

I apologize that the text on this diagram is so small, but that's down to the restrictions on image sizes here at The Code Project. I'll even give you some class diagrams, for those that prefer them. Remember that there are 3 separate assemblies (ChatService / Common / WPFChatter), which I talked about earlier:

ChatService

Screenshot - ChatService.png

In order for this service to work correctly, there is a special configuration file. It could also have been done in code, but App.Config is just more flexible. So, let's have a look at the ChatService.exe App.Config, shall we? Well, it looks like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>

    <add key="addr" value="net.tcp://localhost:22222/chatservice" />
  </appSettings>
  <system.serviceModel>

    <services>
      <service name="Chatters.ChatService" behaviorConfiguration="MyBehavior">
        <endpoint address=""
                  binding="netTcpBinding"
                  bindingConfiguration="DuplexBinding"
                  contract="Chatters.IChat" />

      </service>
    </services>

    <behaviors>

      <serviceBehaviors>
        <behavior  name="MyBehavior">

          <serviceThrottling maxConcurrentSessions="10000" />
        </behavior>
      </serviceBehaviors>
    </behaviors>

    <bindings>
      <netTcpBinding>

        <binding name="DuplexBinding" sendTimeout="00:00:01">
          <reliableSession enabled="true" />
          <security mode="None" />
        </binding>

      </netTcpBinding>
    </bindings>

  </system.serviceModel>
</configuration>

As you can see, this App.Config file contains all the information required to enable the service to operate. WCF supports a lot of different binding options, such as:

Binding type Description
BasicHttpBinding A binding that is suitable for communicating with WS-Basic Profile conformant Web Services, for example, ASMX-based services. This binding uses HTTP as the transport and Text/XML as the default message encoding.
WSHttpBinding A secure and interoperable binding that is suitable for non-duplex service contracts.
WSDualHttpBinding A secure and interoperable binding that is suitable for duplex service contracts or communication through SOAP intermediaries.
WSFederationHttpBinding A secure and interoperable binding that supports the WS-Federation protocol, enabling organizations that are in a federation to efficiently authenticate and authorize users.
NetTcpBinding A secure and optimized binding suitable for cross-machine communication between WCF applications.
NetNamedPipeBinding

A secure, reliable, optimized binding that is suitable for on-machine communication between WCF applications.

NetMsmqBinding A queued binding that is suitable for cross-machine communication between WCF applications.
NetPeerTcpBinding

A binding that enables secure, multi-machine communication.

MsmqIntegrationBinding A binding that is suitable for cross-machine communication between a WCF application and existing MSMQ applications.

For the demo application, I am using NetTcpBinding. For more information on bindings in WCF applications, you can see this link.

Common

This is a very simple class that is used by the ChatService and WPFChatter assemblies. This class represents a single chatter and may be sent over the WCF channel due the special WCF annotations that have been applied to this class. The entire class is listed below, as it's not so big:

Screenshot - Common.png
using System
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
using System.ComponentModel;

namespace Common
{
    #region Person class
    /// <summary>

    /// This class represnts a single chat user that can participate in 

    /// this chat application

    /// This class implements INotifyPropertyChanged to support one-way 

    /// and two-way WPF bindings (such that the UI element updates when 

    /// the source has been changed dynamically)

    /// [DataContract] specifies that the type defines or implements a 

    /// data contract and is serializable by a serializer, such as 

    /// the DataContractSerializer

    /// </summary>

    [DataContract]
    public class Person : INotifyPropertyChanged
    {
        #region Instance Fields
        private string imageURL;
        private string name;
        public event PropertyChangedEventHandler PropertyChanged;
        #endregion
        #region Ctors
        /// <summary>

        /// Blank constructor

        /// </summary>


        public Person()
        {
        }
        
        ///