programming4us
programming4us
WEBSITE

Silverlight Recipes : Networking and Web Service Integration - Exchanging Data between Silverlight Applications

8/8/2012 6:08:40 PM

1. Problem

You have two or more separate Silverlight applications composing parts of your overall web page, and you need these applications to exchange data with each other.

2. Solution

Use the local connection feature in Silverlight 3 to enable communication channels between these applications and facilitate cross-application data exchange.

3. How It Works

The local-connection feature in Silverlight 3 enables you to establish communication channels between two or more Silverlight applications on the same web page.

3.1. Receiver Registration

In this mode of communication, an application can act as a sender, a receiver, or both. To register itself with the communication system as a receiver, the application has to provide a unique identity, using which messages are directed to it. This identity is a combination of a receiver name (expressed using a string literal) and the application's web domain name, and needs to yield a unique identifier within the scope of the containing page.

To register itself as a receiver, the application can create an instance of the LocalMessageReceiver class in System.Windows.Messaging, passing in the receiver name as shown here:

LocalMessageReceiver ThisReceiver = new LocalMessageReceiver("ThisReceiverName");

					  

Using this version of the constructor registers the receiver name as unique in its originating domain—other receivers in the page can have the same receiver name as long as they belong to different domains. Registering in this fashion also allows the receiver to receive messages only from those senders on the page that originate from the same domain as the receiver.

The local-connection API offers granular control over the message receiving heuristics. An overloaded constructor for the LocalMessageReceiver class is made available with the following signature:

public LocalMessageReceiver(string receiverName,
      ReceiverNameScope nameScope, IEnumerable<string> allowedSenderDomains);

					  

The ReceiverNameScope enumeration used in the second parameter has two possible values. ReceiverNameScope.Domain has the same effect as the previous constructor, requiring that the receiver name be unique within all receivers on the page originating from the same domain. However, ReceiverNameScope.Global requires that the receiver name be unique across all receivers on the page, regardless of their originating domain name.

The third parameter, allowedSenderDomains, enables extending the list of sender domains from which the receiver can receive messages beyond the receiver's originating domain. Setting it to LocalMessageReceiver.AnyDomain allows the receiver to receive messages from any sender on the page, regardless of the sender's originating domain. You can also set allowedSenderDomains to a selective list of the domains from which you want to allow message receipt. The following code shows a receiver being registered as unique across all receiver domains on the page, with the abilty to receive messages from senders in two specific domains (http://www.microsoft.com and http://www.silverlight.net):

LocalMessageReceiver ThisReceiver =
     new LocalMessageReceiver("ThisReceiverName",
       ReceiverNameScope.Global, new List<string>{ "http://www.microsoft.com",
         "http://www.silverlight.net"});

					  

3.2. Receiving Messages

When a receiver has been registered, you need to attach a handler to the LocalMessageReceiver.MessageReceived event to receive messages and then call the LocalMessageReceiver.Listen() method to start listening for incoming messages asynchronously. Here is an example:

ThisReceiver.MessageReceived +=
  new EventHandler<MessageReceivedEventArgs>((s, e) =>
  {
    string Msg = e.Message;

   //do something with the received message
    ...
   //optionally send a response message
   string ResponseMessage = PrepareResponseMessage();
   e.Response = ResponseMessage;

  });
ThisReceiver.Listen();

The MessageReceivedEventArgs.Message property contains the string message that was sent. When your code has processed the message, you can also send a response message back to the sender in the MessageReceivedEventArgs.Response property. The response message follows the same rules as any other local connection message: it must be a string that is less than 1 MB in size. We talk more about the Response property in a bit.

3.3. Sending Messages

A sender application has no explicit registration process. To send messages to a receiver, you must construct an instance of System.Windows.Messaging.LocalMessageSender as shown here, passing in the receiver name and the receiver domain as parameters:

LocalMessageSender ThisSender =
  new LocalMessageSender("SomeReceiver","http://localhost");

You can also pass the value LocalMessageSender.Global as the second parameter. In that case, the system attempts to deliver the message to all receivers with the specified name on the page, regardless of what domain they belong to.

Local-connection messages are always sent asynchronously using the LocalMessageSender.SendAsync() method, as show here:

string MyMessage;
//create a message here
ThisSender.SendAsync(MyMessage);

As you can see, the message being sent is of type String. In the current version of Silverlight, only string messages less than 1 MB can be sent and received using the local-connection system. This may seem limiting initially. But consider that you can express any Silverlight data structure in either JSON or XML strings using the Silverlight-supplied serialization mechanisms like data-contract serialization or LINQ to XML XDocument serialization. With that in mind, this approach allows you to build fairly effective and rich data-exchange scenarios.

After the message has been sent, or an attempt to do so fails, the LocalMessageSender.SendCompleted event is raised by the runtime. You need to handle the event to do any error handling or response processing, as shown here:

ThisSender.SendCompleted +=
  new EventHandler<SendCompletedEventArgs>((s, e) =>
  {
    if (e.Error != null)
    {
      //we had an error sending the message - do some error reporting here
    }
    else if (e.Response != null)
    {
      //the receiver sent a response - process it here
    }
  });

Because the send operation is asynchronous and returns immediately, the local-connection system does not raise a direct exception to the sender if a send operation is unsuccessful. Consequently, in the SendCompleted event handler, you should check the SendCompletedEventArgs.Error property of type Exception for any exception that may be raised in the event of an unsuccessful send attempt. In case of a send-related error, this may be set to an instance of System.Windows.Messaging.SendFailedException.

If the send was successful, the SendCompletedEventArgs.Response may contain a response message, depending on whether the receiver sent a response back.

3.4. Request-Response

The Response property is interesting in that it lets you establish a rudimentary request-response correlation using the local connection.

There are no limitations on an application being both a sender and a receiver at the same time. For an application to be both a sender and a receiver, you must perform the appropriate receiver registration and then create both a LocalMessageSender and a LocalMessageReceiver instance, as shown in the previous sections. One way to send responses from a receiver back to a sender would be a role-reversal strategy, where the receiver acts as a sender and the sender acts as a receiver for the response message path. However, because the order of message delivery is not guaranteed in the current implementation, this puts the onus on you to include additional details in the message body, should you need to correlate a sent message with its response.

The Response properties on the MessageReceivedEventArgs and MessageSentEventArgs types let you circumvent that. MessageReceivedEventArgs also contains a Message property and a SenderDomain property, which let the receiver application accurately pair the right response with the incoming message. MessageSentEventArgs also contains Message and Response properties, in addition to information about the receiver that sent the response through the ReceiverDomain and ReceiverName properties. This allows the sender to accurately pair a receiver response with a specific sent message.

4. The Code

The application allows you to change the spending in each category to different values and watch the graph change accordingly. It also lets you drag any bar in the graph using your mouse and watch the corresponding value change in the DataGrid, maintaining the same total.

To adapt that sample to this recipe, you break it into two separate applications. The application named 7.7 HomeExpenseWorksheet encapsulates the DataGrid-based worksheet portion of the sample, whereas the 7.7 HomeExpenseGraph application encapsulates the bar-graph implementation. You then use local-connection-based messaging between the two applications to implement the necessary communication.

Figure 1 shows the applications hosted on the same page.

Figure 1. The expense worksheet and the expense graph applications on the same page

Before we discuss the local-connection-related code changes, let's quickly look at the XAML for the expense worksheet application, shown in Listing 1.

Listing 1. XAML for the HomeExpenseWorksheet application in MainPage.xaml
<UserControl x:Class="Recipe7_7.HomeExpenseWorksheet.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:data=
    "clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
             Width="300"
             Height="600">

  <Grid x:Name="LayoutRoot"
        Background="White">
    <Grid.RowDefinitions>
      <RowDefinition Height="0.8*" />
      <RowDefinition Height="0.2*" />
    </Grid.RowDefinitions>
    <data:DataGrid HorizontalAlignment="Stretch"
                   Margin="8,8,8,8"
                   VerticalAlignment="Stretch"

					  

HeadersVisibility="All"
                   Grid.Row="0"
                   x:Name="dgSpending"
                   AutoGenerateColumns="False"
                   CellEditEnded="dgSpending_CellEditEnded">
      <data:DataGrid.Columns>
        <data:DataGridTextColumn Header="Item"
                                 Binding="{Binding Item,Mode=TwoWay}" />
        <data:DataGridTextColumn Header="Value"
                                 Width="100"
                                 Binding="{Binding Amount,Mode=TwoWay}" />
      </data:DataGrid.Columns>
    </data:DataGrid>
    <StackPanel Orientation="Horizontal"
                Grid.Row="1"
                HorizontalAlignment="Right">
      <Button x:Name="btnAddItem"
              Margin="3,3,3,3"
              Height="30"
              Width="85"
              Content="Add Item"
              Click="btnAddItem_Click" />
      <Button x:Name="btnRemoveItem"
              Margin="3,3,3,3"
              Height="30"
              Width="85"
              Content="Remove Item"
              Click="btnRemoveItem_Click" />
    </StackPanel>
  </Grid>
</UserControl>

					  

The code in Listing 1 shows the only notable changes made to the XAML. As you can see, you add two buttons: clicking btnAddItem adds a new row to the DataGrid, and clicking btnRemoveItem removes the currently selected item from the DataGrid. You also attach a handler to the CellEditEnded event of the DataGrid. We cover the details of the implementations of these handlers later in this section.

To start with the local-connection implementation, recall that messages are string based. However, strings are cumbersome to work with, so you define the application messages as a custom CLR type named Message and then resort to serialization to convert Message instances to string representations before sending them. Listing 2 shows the Message type.

Listing 2. The Message custom type in Messages.cs
using System.Collections.Generic;
using System.IO;
using System.Runtime.Serialization;
using System.Text;

namespace Recipe7_7.SD
{
  public enum MessageType
  {
    ItemRemoved,
    ItemsValueChanged
  }

  [DataContract]
  public class Message
  {
    [DataMember]
    public MessageType MsgType { get; set; }
    [DataMember]
    public List<Spending> Items { get; set; }
    public static string Serialize(Message Msg)
    {
      DataContractSerializer dcSer = new DataContractSerializer(typeof(Message));
      MemoryStream ms = new MemoryStream();
      dcSer.WriteObject(ms, Msg);
      ms.Flush();
      string RetVal = Encoding.UTF8.GetString(ms.GetBuffer(), 0, (int)ms.Length);
      ms.Close();
      return RetVal;
    }

    public static Message Deserialize(string Msg)
    {
      DataContractSerializer dcSer = new DataContractSerializer(typeof(Message));
      MemoryStream ms = new MemoryStream(Encoding.UTF8.GetBytes(Msg));
      Message RetVal = dcSer.ReadObject(ms) as Message;
      ms.Close();
      return RetVal;
    }
  }
}

					  

You handle two kinds of messages in the local connection implementation between the worksheet and the graph applications, as defined in the MessageType enumeration. The MessageType.ItemRemoved value indicates a message that communicates the removal of one or more items; it is sent from the worksheet to the graph only when rows are removed from the worksheet. The MessageType.ItemsValueChanged typed message can be sent in either direction when the values of one or more items change—either in the worksheet for an existing item or a newly added item through user edits, or in the graph when the user drags a bar to resize it.

The Message class contains the MessageType and a list of Items with changed values or a list of Items that were removed. It also defines two static methods that use DataContractSerialization to serialize and deserialize instances of the Message type to and from a string representation. Note that you have the Message class attributed as a DataContract with the Mistyped and Items properties attributed as DataMember.

An individual data item for the application is defined as a class named Spending, and a custom class named SpendingCollection deriving from ObservableCollection<Spending> defines the data collection that initially populates the worksheet and the graph. Listing 3 shows these classes.

Listing 3. Data classes in DataClasses.cs
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.Serialization;
namespace Recipe7_7.SD
{
  public class SpendingCollection : ObservableCollection<Spending>
  {
    public SpendingCollection()
    {
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 1,
        Item = "Utilities",
        Amount = 300
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 2,
        Item = "Food",
        Amount = 350
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 3,

					  

Item = "Clothing",
        Amount = 200
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 4,
        Item = "Transportation",
        Amount = 75
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 5,
        Item = "Mortgage",
        Amount = 3000
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 6,
        Item = "Education",
        Amount = 500
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 7,
        Item = "Entertainment",
        Amount = 125
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 8,
        Item = "Loans",
        Amount = 750
      });
      this.Add(new Spending
      {
        ParentCollection = this,
        ID = 9,
        Item = "Medical",
        Amount = 80
      });

					  

this.Add(new Spending
      {
        ParentCollection = this,
        ID = 10,
        Item = "Miscellaneous",
        Amount = 175
      });
    }

    public double Total
    {
      get
      {
        return this.Sum(spending => spending.Amount);
      }
    }
  }

  [DataContract]
  public class Spending : INotifyPropertyChanged
  {

    public event PropertyChangedEventHandler PropertyChanged;
    internal void RaisePropertyChanged(PropertyChangedEventArgs e)
    {
      if (PropertyChanged != null)
      {
        PropertyChanged(this, e);
      }
    }

    public override int GetHashCode()
    {
      return ID.GetHashCode();
    }

    public override bool Equals(object obj)
    {
      return (obj is Spending) ? this.ID.Equals((obj as Spending).ID) : false;
    }

    SpendingCollection _ParentCollection = null;

    public SpendingCollection ParentCollection
    {

					  

get { return _ParentCollection; }
      set
      {
        _ParentCollection = value;
        if (ParentCollection != null)
        {
          foreach (Spending sp in ParentCollection)
            sp.RaisePropertyChanged(new PropertyChangedEventArgs("Amount"));
        }
      }
    }

    private int _ID = default(int);
    [DataMember]
    public int ID
    {
      get
      {
        return _ID;
      }

      set
      {
        if (value != _ID)
        {
          _ID = value;
          if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs("ID"));
        }
      }
    }

    private string _Item;
    [DataMember]
    public string Item
    {
      get { return _Item; }
      set
      {
        string OldVal = _Item;
        if (OldVal != value)
        {
          _Item = value;
          RaisePropertyChanged(new PropertyChangedEventArgs("Item"));

					  

}
      }
    }

    private double _Amount;
    [DataMember]
    public double Amount
    {
      get { return _Amount; }
      set
      {
        double OldVal = _Amount;
        if (OldVal != value)
        {
          _Amount = value;

          if (ParentCollection != null)
          {
            foreach (Spending sp in ParentCollection)
              sp.RaisePropertyChanged(new PropertyChangedEventArgs("Amount"));
          }
        }
      }
    }
  }
}

					  

The only changes worth noting are the addition of an ID property to the Spending class to uniquely identify it in a collection, and the overrides for GetHashCode() and Equals() to facilitate locating or comparing spending instances based on their IDs. The changes are noted in bold in Listing 3.

Now, let's look at the application code. Listing 4 lists the codebehind for the worksheet application.

Listing 4. The MainPage codebehind in MainPage.xaml.cs for the HomeExpenseWorksheet application
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Messaging;
using Recipe7_7.SD;

namespace Recipe7_7.HomeExpenseWorksheet
{
  public partial class MainPage : UserControl
  {

//data source
    SpendingCollection SpendingList = new SpendingCollection();
    //create a sender
    LocalMessageSender WorksheetSender =
      new LocalMessageSender("SpendingGraph",
        LocalMessageSender.Global);
    //create a receiver
    LocalMessageReceiver WorksheetReceiver =
      new LocalMessageReceiver("SpendingWorksheet",
        ReceiverNameScope.Global, LocalMessageReceiver.AnyDomain);

    public MainPage()
    {
      InitializeComponent();

      //bind data
      dgSpending.ItemsSource = SpendingList;

      //handle message receipt
      WorksheetReceiver.MessageReceived+=
        new EventHandler<MessageReceivedEventArgs>((s,e) =>
      {
        //deserialize message
        Message Msg = Message.Deserialize(e.Message);
        //if item value changed
        if (Msg.MsgType == MessageType.ItemsValueChanged)
        {
          //for each item for which value has changed
          foreach (Spending sp in Msg.Items)
          {
            //find the corrsponding item in the data source and replace value
            SpendingList[SpendingList.IndexOf(sp)] = sp;
          }
        }
      });

      //handle send completion
      WorksheetSender.SendCompleted +=
        new EventHandler<SendCompletedEventArgs>((s, e) =>
      {
        //if error
        if (e.Error != null)
        {
          //we had an error sending the message - do some error reporting here
        }

					  

//if there was a response
        else if (e.Response != null)
        {
          //the receiver sent a response - process it here
        }
      });

      //start listening for incoming messages
      WorksheetReceiver.Listen();
    }

    //handle add row button click
    private void btnAddItem_Click(object sender, RoutedEventArgs e)
    {
      //add a new Spending instance to the data source
      SpendingList.Add(new Spending() { ParentCollection = SpendingList });
    }

    //handle a cell edit
    private void dgSpending_CellEditEnded(object sender,
      DataGridCellEditEndedEventArgs e)
    {
      //send a message
      WorksheetSender.SendAsync(Message.Serialize(
        new Message()
        {
          //message type - Item value changed
          MsgType = MessageType.ItemsValueChanged,
          //the changed Spending instance
          Items = new List<Spending> { e.Row.DataContext as Spending }
        }));
    }

    //remove the selected item
    private void btnRemoveItem_Click(object sender, RoutedEventArgs e)
    {
      //if there is a selected row
      if (dgSpending.SelectedItem != null)
      {
        //get the corresponding Spending instance
        Spending target = dgSpending.SelectedItem as Spending;
        //remove it from the data source
        SpendingList.Remove(target);
        //send a message
        WorksheetSender.SendAsync(Message.Serialize(

					  

new Message()
        {
          //message type - Item Removed
          MsgType = MessageType.ItemRemoved,
          //the item that was removed
          Items = new List<Spending> { target }
        }));
      }
    }
  }
}

As you can see, you start by creating a LocalMessageSender and a LocalMessageReceiver instance, respectively, named WorksheetSender and WorksheetReceiver, as members of the codebehind class. WorksheetSender is created to let you send messages to a receiver named SpendingGraph, which is globally unique across all receivers on the page. WorksheetReceiver registers this application as a receiver named SpendingWorksheet, again with a global namescope, and prepares to receive incoming messages from senders in any domain.

During construction, you attach handlers to WorksheetReceiver.MessageReceived and WorksheetSender.SendCompleted. In the MessageReceived handler, you deserialize the incoming message and then process it. You only handle messages of type MessageType.ItemsValueChanged, because these are the only types of messages the HomeExpenseGraph application can generate. As a part of the processing, if you do receive Spending instances that have changed, you replace them accordingly in the expense worksheet datasource. In the SendCompleted handler, you show a skeletal set of statements for handling error conditions and response messages—we leave it as an exercise for you to implement error handling and response correlation as needed.

In the Click event handler for the Button named btnAddItem, you add a new Spending item to the datasource. However, you do not immediately send a message to the HomeExpenseGraph application, because the Spending item still does not have any meaningful data. Instead, you use the CellEditEnded event handler to send item-change notifications. In that handler, you construct a new Message instance with the changed Spending item as the only item in the Message.Items collection, and you set the MsgType property to MessageType.ItemsValueChanged. You then serialize the message and send it through the WorksheetSender.SendAsync() method.

In the Click handler for btnRemoveItem, you first remove the Spending instance bound to the selected DataGrid row from the datasource collection. Then, you use the same approach to serialize and send a Message instance, with the MsgType property set to MessageType.ItemRemoved.

Let's look at the HomeExpenseGraph application. Listing 5 shows the codebehind for the MainPage in that application.

Listing 5. The MainPage aodebehind in MainPage.xaml.cs for the HomeExpenseGraph application
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Messaging;
using System.Windows.Shapes;

using Recipe7_7.SD;

namespace Recipe7_7.HomeExpenseGraph
{
  public partial class MainPage : UserControl
  {
    //variables to enable mouse interaction
    private bool MouseLeftBtnDown = false;
    private bool Dragging = false;
    Point PreviousPos;
    //data source
    SpendingCollection SpendingList = null;
    //create a sender
    LocalMessageSender GraphSender =
      new LocalMessageSender("SpendingWorksheet",
        LocalMessageSender.Global);
    //create a receiver
    LocalMessageReceiver GraphReceiver =
     new LocalMessageReceiver("SpendingGraph",
       ReceiverNameScope.Global, LocalMessageReceiver.AnyDomain);

    public MainPage()
    {
      InitializeComponent();

      SpendingList = this.Resources["REF_SpendingList"] as SpendingCollection;
      //handle property changed for each Spending - this is used to send item
      //value changed messages
      foreach (Spending sp in SpendingList)
      {
        sp.PropertyChanged +=
          new System.ComponentModel.
            PropertyChangedEventHandler(Spending_PropertyChanged);
      }
      //handle message receipts
      GraphReceiver.MessageReceived +=
        new EventHandler<MessageReceivedEventArgs>((s, e) =>
      {
        //deserialize message
        Message Msg = Message.Deserialize(e.Message);
        //if value changed
        if (Msg.MsgType == MessageType.ItemsValueChanged)
        {
          //for each changed Spending instance
          foreach (Spending sp in Msg.Items)

					  

{
            //if it exists
            if (SpendingList.Contains(sp))
            {
              //replace it with the changed one
              SpendingList[SpendingList.IndexOf(sp)] = sp;
            }
            else
            {
              //add the new one
              SpendingList.Add(sp);
            }
            //handle property changed
            sp.PropertyChanged +=
              new System.ComponentModel.
                PropertyChangedEventHandler(Spending_PropertyChanged);
            //force a recalc of the bars in the graph
            sp.ParentCollection = SpendingList;
          }
        }
        //item removed
        else if (Msg.MsgType == MessageType.ItemRemoved)
        {
          foreach (Spending sp in Msg.Items)
          {
            //unhook the event handler
            SpendingList[SpendingList.IndexOf(sp)].PropertyChanged
              -= Spending_PropertyChanged;
            //remove from data source
            SpendingList.Remove(sp);
          }
          //force a recalc of the bars in the graph
          if (SpendingList.Count > 0)
            SpendingList[0].ParentCollection = SpendingList;

        }
      });

      //start listening for incoming messages
      GraphReceiver.Listen();
    }
    void Spending_PropertyChanged(object sender,
      System.ComponentModel.PropertyChangedEventArgs e)
    {
      //send a message

					  

GraphSender.SendAsync(
        Message.Serialize(
            new Message
            {
              //changed item
              Items = new List<Spending> { sender as Spending },
              //message type - item value changed
              MsgType = MessageType.ItemsValueChanged
            }));

    }

    private void Rectangle_MouseMove(object sender, MouseEventArgs e)
    {
      if (MouseLeftBtnDown)
      {
        Rectangle rect = (Rectangle)sender;
        if (Dragging == false)
        {
          Dragging = true;
          rect.CaptureMouse();
        }

        Point CurrentPos = e.GetPosition(sender as Rectangle);
        double Moved = CurrentPos.X - PreviousPos.X;
        if (rect.Width + Moved >= 0)
        {
          rect.Width += Moved;
        }
        PreviousPos = CurrentPos;
      }
    }

    private void Rectangle_MouseLeftButtonDown(object sender,
      MouseButtonEventArgs e)
    {
      MouseLeftBtnDown = true;
      PreviousPos = e.GetPosition(sender as Rectangle);
    }

    private void Rectangle_MouseLeftButtonUp(object sender,
      MouseButtonEventArgs e)
    {
      Rectangle rect = (Rectangle)sender;
      if (Dragging)

					  

{
        Dragging = false;
        rect.ReleaseMouseCapture();
      }
      MouseLeftBtnDown = false;
    }
  }
}

As before, this application needs to both send and receive messages. As shown in Listing 5, you create instances of LocalMessageSender and LocalMessageReceiver such that this application can receive messages from the worksheet application and send messages to it as well.

In the constructor, after the datasource is bound, you handle the PropertyChanged event for each item in the collection. The PropertyChanged event is raised whenever the user drags a bar within the graph; if you look at the handler for the PropertyChanged event, note that you send a message to the other application indicating thus action.

You also handle the MessageReceived event as before. In the handler, you handle messages of both types—where item values are changed and where items are removed.

If an item value changes, you check to see if the item that changed already exists or was newly created in the worksheet application and does not exist in the datasource for this application. If it is an existing item, you replace it with the changed item; if it is a new item, you add it to the collection. You also attach a handler to the Spending item so that you can track changes to it in this application. Finally, you set the Spending.ParentCollection property to the datasource to which it was added to in which it was replaced. If you look at the definition of the Spending type in Listing 5, you see that this forces a property-change notification for all the items in the datasource. The bar graph displays the spending as percentages of the total, and this causes the bar graph's bar widths to be recalculated based on the new values.

If an item is removed, you first unattach the PropertyChanged event handler from the item that was removed and then remove it from the datasource collection. When the removals are complete, you force a similar recalculation of the bar widths based on the new percentages.

Other  
 
 
Video tutorials
- How To Install Windows 8

- How To Install Windows Server 2012

- How To Install Windows Server 2012 On VirtualBox

- How To Disable Windows 8 Metro UI

- How To Install Windows Store Apps From Windows 8 Classic Desktop

- How To Disable Windows Update in Windows 8

- How To Disable Windows 8 Metro UI

- How To Add Widgets To Windows 8 Lock Screen

- How to create your first Swimlane Diagram or Cross-Functional Flowchart Diagram by using Microsoft Visio 2010
programming4us programming4us
Video
programming4us
 
 
programming4us