A while back I discovered a great post about how to establish communication between Silverlight controls on the same page. I had already known how it could be accomplished using Javascript and an example of this approach is used by my Silverlight Gallery. But this other way caught my attention and I have decided to post about it. Jeff Prosise's Blog contains a series of posts called Cool Silverlight Tricks. Currently there are only 5 tricks, but, they are all great. Make sure you check them out. I will not be discussing the Javascript approach in the blog post. The reader is referred to this post
Jeff basically replaces all the Javascript that would normally be used to establish communication with equivalent Silverlight HTMLBridge calls. Now why didn't I think of that! Let's take a look at some code.
We start by creating a Silverlight project. This project will require two Silverlight controls. I have decide to call the controls SourceSilverlight and TargetSilverlight. The SourceSilverlight control, as it name implies, will be the control that will send commands to the TargetSilverlight. I liked the visual aspect to Jeff's example so I used it here. Both controls will have a circle positioned identically on their respective canvases. When the user moves the circle in the SourceSilverlight control it sends the new postion to the TargetSilverlight control causing the circle to move identically.
Listing 1 contains the code behind for the SourceSilverlight UserControl. I have excluded the XAML markup as it is extremely simple containing an ellipse with a name of ball. The MouseMove, MouseLeftButtonDown and MouseLeftButtonUp events are all wired up and are used to enable dragging.
Listing 1: SourceSilverlight UserControl Code Behind
1
2 Imports System.Windows.Browser
3
4 Partial Public Class Page
5 Inherits UserControl
6
7 Public Sub New()
8 InitializeComponent()
9 End Sub
10
11 #Region "Move Ball Events"
12 Private isMouseDown As Boolean = False
13 Private Sub ball_MouseLeftButtonUp( _
14 ByVal sender As System.Object, _
15 ByVal e As System.Windows.Input.MouseButtonEventArgs)
16 isMouseDown = False
17 CType(sender, Ellipse).ReleaseMouseCapture()
18 End Sub
19
20 Private Sub ball_MouseLeftButtonDown( _
21 ByVal sender As System.Object, _
22 ByVal e As System.Windows.Input.MouseButtonEventArgs)
23 isMouseDown = True
24 CType(sender, Ellipse).CaptureMouse()
25 End Sub
26
27 Private Sub ball_MouseMove( _
28 ByVal sender As System.Object, _
29 ByVal e As System.Windows.Input.MouseEventArgs)
30 If isMouseDown Then
31 Dim ball As Ellipse = CType(sender, Ellipse)
32 Dim cx As Double = e.GetPosition(LayoutRoot).X
33 Dim cy As Double = e.GetPosition(LayoutRoot).Y
34 ball.SetValue(Canvas.LeftProperty, cx - ((ball.Width) / 2))
35 ball.SetValue(Canvas.TopProperty, cy - ((ball.Height) / 2))
36 MoveTargetBall(cx - ((ball.Width) / 2), cy - ((ball.Height) / 2))
37 End If
38 End Sub
39 #End Region
40
41 #Region "Communication to Target Silverlight"
42 'This is the Jeff Proise's cool silverlight trick. No need for javacript.
43 'Just call HTML bridge functionality duplicating what you would have done
44 'in javascript.
45 Private _target As ScriptObject = Nothing
46 Public ReadOnly Property Target() As ScriptObject
47 Get
48 If _target Is Nothing Then
49 'I have hardcoded the id of the target silverlight control.
50 'A better solution would be to use the InitParameters feature.
51 Dim slhost As HtmlElement = HtmlPage.Document.GetElementById("Xaml2")
52 Dim content As ScriptObject = CType(slhost.GetProperty("content"), ScriptObject)
53 _target = CType(content.GetProperty("SLScriptObject_TargetSilverlight"), ScriptObject)
54 End If
55 Return _target
56 End Get
57 End Property
58
59 Private Sub MoveTargetBall(ByVal x As Double, ByVal y As Double)
60 Target.Invoke("MoveBall", x, y)
61 End Sub
62 #End Region
63
64 End Class
I have divided the code into two regions. The first region deals with mouse events and is responsible for moving the circle. Note that at the end of the ball_MouseMove method we call method MoveTargetBall passing as parameters the new position of the circle. I will not discuss how this region of code works as it is a standard drag implementation and is not relevant to the topic at hand. The second region of code deals directly with the communication between the SourceSilverlight and TargetSilverlight control.
Let's examine this code. The real work is done in the ReadOnly Target Property. In this property, we first obtain a reference to the TargetSilverllght control. This is accomplished by calling the GetElementById method passing to it the ID of the TargetSilverlight control. For the purposes of this post I have hard coded this ID, however, a better approach would be to make use the InitParameters. Next using this reference, we call the GetProperty method passing the string content. This returns a reference to a ScriptObject, a representation of a DOM object, and in this case it references the TargetSilverlight control. Finally, using this ScriptObject reference, we make a call to GetProperty this time passing the string SLScriptObject_TargetSilverlight. What does this string represent you may be asking? Great question, basically, this is the token that was used when the TargetSilverlight control registered a scriptable object. This will become clearer when we look at the code behind for the TargetSilverlight control.
So all that is required to invoke a registered method from the TargetSilverlight Control is to called the Invoke method passing the string representation of the method and the X and Y position of the circle.
A note of caution. While this approach eliminates any need for Javascript, it comes at a price. This method requires 4 round trips between Silverlight and the browser (lines 51, 52, 53 and 60), whereas, a Javascript approach requires only one round trip.
Listing 2 contains the code behind for the TargetSilverlight control.
List 2: TargetSilverlight Control Code Behind
1
2 Imports System.Windows.Browser
3
4 Partial Public Class Page
5 Inherits UserControl
6
7 Public Sub New()
8 InitializeComponent()
9 End Sub
10
11 <ScriptableMember()> _
12 Public Sub MoveBall(ByVal x As Double, ByVal y As Double)
13 ball.SetValue(Canvas.LeftProperty, x)
14 ball.SetValue(Canvas.TopProperty, y)
15 End Sub
16
17 Private Sub Page_Loaded( _
18 ByVal sender As Object, _
19 ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
20 HtmlPage.RegisterScriptableObject("SLScriptObject_TargetSilverlight", Me)
21 End Sub
22 End Class
Take a look at the MoveBall method, this the method that the SourceSilverlight control called to communicate the new position of the circle (line 60 in listing 1). Note the use of the ScriptableMemberAttribute. Basically, this makes the method accessible to Javascript callers or in our case via the HTML bridge from another Silverlight control. The Page_Load method calls the RegisterScriptableObject method passing to it the token SLScriptObject_TargetSilverlight and this is the typical pattern for exposing a Silverlight method to a Javascript caller.
The last listing, Listing 3, contains the page markup used to host these two Silverlight controls. Nothing out the ordinary.
Listing 3: Markup used to host the Silverlight controls.
12 <body style="height:100%;margin:0;">
13 <form id="form1" runat="server" style="height:100%;">
14 <asp:ScriptManager ID="ScriptManager1" runat="server">
15 </asp:ScriptManager>
16 <div style="float: left; padding-right: 10px; padding-left: 10px; padding-top: 10px;">
17 <asp:Silverlight
18 ID="Xaml1"
19 runat="server"
20 Source="~/ClientBin/SourceSilverlight.xap"
21 MinimumVersion="2.0.31005.0" Width="300px" Height="300px" />
22 </div>
23 <div style="padding-top: 10px;">
24 <asp:Silverlight
25 ID="Xaml2"
26 runat="server"
27 Source="~/ClientBin/TargetSilverlight.xap"
28 MinimumVersion="2.0.31005.0" Width="300px" Height="300px" />
29 </div>
30 </form>
31 </body>
If you would like to see this in action please navigate to this site.
Guess the movie
Go ahead, Zeus. Throw down a thunderbolt, let the earth swallow me up. I defy you!