More than often working with Silverlight you may want to communicate from a plugin instance to another instance in the same page. Coordinating scenes, across different plugin instances, may be a difficult issue with Silverligth 2.0, involving HTML Bridge and often limitated because this bridge needs to be enabled. With the recently released version 3.0 beta 1 working in this way it is simple like drinking a glass of water thanks to a new feature called Local Connection.

What is a Local Connection?

The new Silverlight 3.0 allow the plugin to expose a listener, with a specific name, that may be addresses by other plugin instances knowing this name. If you had ever used named-pipes for inter process communications you may found that Local Connections are very similar. There are two classes you may use to establish a connection; LocalMessageReceiver and LocalMessageSender. The first one is used to expose an endpoint with this few lines of code:

   1: LocalMessageReceiver receiver = new LocalMessageReceiver(name);
   2: receiver.MessageReceived += new EventHandler<MessageReceivedEventArgs>(Receiver_MessageReceived);
   3: receiver.Listen();

The "name" used when you create the instance of the receiver is the name you have to use when you create the LocalMessageSender. Here is how to create a sender in a remote plugin. The sender needs simply to be instantiated and is ready to send messages. The most beautiful thing is that a couple of Received and Sender may communicate across plugins not always in the same page but also across plugins instantiated in different browsers.

Here is how to instantiate the LocalMessageSender and send a message to a listening receiver:

   1: LocalMessageSender sender = new LocalMessageSender(name);
   2: sender.SendAsync("this is a message");

Obviously the receiver must exist and listening. The messages you may exchange over this channel are plain strings so if you have to send complex informations you have to create a private protocol wrapping this data. As an example you may use JSON or XML Serializer to send class instances to another plugin.

A live example

We have now to create a working example of this tecnique, so let' try to coordinate the movement of one element across four instances, disposed to compose a square. We call this instances with a name, that will be the name of the listening channel and identify the position the plugin assume in the square. the names are "nw", "ne", "sw" and "se" like the cardinal directions.

With a good design we create a class "Communicator" that encapsulate the communicationg channel and expose a method Move() called to move the element on all the listening instances and an event "PositionChanged" that notify to the user interface that the elements position needs to be changed. The Communicator class instance a single LocalMessageReceiver with the name of the local plugin a three LocalMessageSender named with the names of the other plugins.

   1: private List<LocalMessageSender> Senders { get; set; }
   2: private LocalMessageReceiver Receiver { get; set; }
   3:  
   4: public Communicator(string name, IEnumerable<string> clients)
   5: {
   6:     this.Receiver = new LocalMessageReceiver(name);
   7:     this.Receiver.MessageReceived += new EventHandler<MessageReceivedEventArgs>(Receiver_MessageReceived);
   8:     this.Receiver.Listen();
   9:  
  10:     this.Senders = new List<LocalMessageSender>();
  11:  
  12:     foreach (string client in clients)
  13:         this.Senders.Add(new LocalMessageSender(client));
  14: }

 

Now that we have created the channels to allow the position exchange we have to plan how to exchange the position coordinates. X and Y position wil be transmitted in a pipe-separated string. Deserializing this string imply to split the string in two parts and create a Point instance with the double values parsed in the strings. In the MessageReceived event we raise the PositionChanged event deserializing the incoming message and forwarding the point to the UI to apply it to a translate transform the shift the graphical element on the plugin area.

   1: public event EventHandler<PositionChangedEventArgs> PositionChanged;
   2:  
   3: private void Receiver_MessageReceived(object sender, MessageReceivedEventArgs e)
   4: {
   5:     string[] coords = e.Message.Split('|');
   6:  
   7:     this.OnPositionChanged(
   8:         new Point(double.Parse(coords[0]), double.Parse(coords[1])));
   9: }
  10:  
  11: protected virtual void OnPositionChanged(Point point)
  12: {
  13:     EventHandler<PositionChangedEventArgs> handler = this.PositionChanged;
  14:  
  15:     if (handler != null)
  16:         handler(this, new PositionChangedEventArgs(point));
  17: }

In the Move() method we have to perform two actions. The first action serialize the Point to a string and send it to other plugins using the SendAsync() method. The message is delivered to the receivers that will execute the MessageReceived event code. The second action we have to perform is raising a local PositionChanged event to update the position of the local element:

   1: public void Move(Point point)
   2: {
   3:     string message = string.Format("{0}|{1}", point.X, point.Y);
   4:  
   5:     foreach (LocalMessageSender sender in this.Senders)
   6:         sender.SendAsync(message);
   7:  
   8:     this.OnPositionChanged(point);
   9: }

Now that the Communicator class is ready we have simply to wrap mouse events to handle dragging inside a plugin. This is a simple task using MouseLeftButtonDown, MouseLeftButtonUp and MouseMove. The Loaded event evaluate the name of the plugin and initially move the element in a particular position to let it appear the same under the plugins. Using a translate transform allow to exchange coordinated that are the same on all the instances so we do not need to apply any calculation. Here is the code:

   1: private void LayoutRoot_MouseMove(object sender, MouseEventArgs e)
   2: {
   3:     // if StartPoint is not null the Mouse button is pressed
   4:     if (this.StartPoint.HasValue)
   5:     {
   6:         Point position = e.GetPosition(this.LayoutRoot);
   7:  
   8:         double x = this.StartTransform.Value.X + position.X - this.StartPoint.Value.X;
   9:         double y = this.StartTransform.Value.Y + position.Y - this.StartPoint.Value.Y;
  10:  
  11:         this.Communicator.Move(new Point(x, y));
  12:     }
  13: }
  14:  
  15: private void Communicator_PositionChanged(object sender, PositionChangedEventArgs e)
  16: {
  17:     transform.X = e.Point.X;
  18:     transform.Y = e.Point.Y;
  19: }

As you may see the incoming message is applied to the transformation. In this way the element appear to move on all the instances in the same moment. To have a try of this effect simply click this link and drag with the mouse into each circle. You will see the element moving in each plugin instance at the same time giving a strange effect.

But the best demostration is to open every instance in a new browser window. If you assign at every new window the correct string "nw", "ne", etc... you may move the element in a windows and you will see the same elements moving in the other windows. Local connections are capable to communicate also throught different browser instances.

I think this new feature is really interesting. I may imagine a couple of ways to use Local connections, but I leave to your fantasy to discover new useful scenarion where you may apply this tecnique.

Download Code: (856 KB)

Demo Video: (1.9 MB)


Aggiungi Commento