Wednesday, April 30, 2008

Silverlight 2.0 integration with Windows Forms

Here's how you can host Silverlight 2.0 beta 1 applications in existing Windows Forms applications and have two-way communication between them. There may be an ActiveX approach that will accomplish this, but the approach I took was with a WebBrowser control.

First, set up the Windows Forms client. I'll use Form1, the hardest working form of them all. I simply drag the WebBrowser control onto the form. In its constructor, I set a few properties needed for having the embedded WebBrowser:


public partial class Form1 : Form {
public Form1() {
InitializeComponent();

webBrowser1.AllowWebBrowserDrop =
webBrowser1.IsWebBrowserContextMenuEnabled =
webBrowser1.WebBrowserShortcutsEnabled = false;
webBrowser1.ObjectForScripting = this;

webBrowser1.Navigate("http://<url of Silverlight application>");
}


I also added a property and a method in the Windows Form for the hosted Silverlight application to call:


public void ButtonClicked(string message) {
MessageBox.Show("ButtonClicked: " + message);
}

public string Test {
set {
MessageBox.Show("Test: " + value);
}
}


In the Silverlight application, use managed code like the following (e.g. in a click event handler, load event, etc.) to call the custom methods on the hosting form (note that you need a reference to System.Windows.Browser to get to the browser):



using System.Windows.Browser;
/* ... */

string msg = "Clicked at " + DateTime.Now.ToString();
HtmlWindow w = HtmlPage.Window;
ScriptObject o = (ScriptObject)w.GetProperty("external");
if (null != o)
{
try
{
// Set a property value on the host form
o.SetProperty("Test", msg);

// Call a method on the host form
o.Invoke("ButtonClicked", new object[] { msg });
}
catch { }
}


Note the catch block: I haven't yet discovered how to detect if the Silverlight application is being hosted in a WebBrowser from inside (if it is being run directly in a browser, the 'external' object exists but invoking custom properties and methods will fail).

Calling methods in the Silverlight application from the WebBrowser control is a bit more complex and involves a few steps:

1) Stage the Silverlight application and methods for scripting

In App.xaml, you must register each Silverlight page for scripting support. For example, if I have a page called Page, I need code like the following:



Page p = new Page();
this.RootVisual = p;
HtmlPage.RegisterScriptableObject("Page", p); // The identifier ("Page") here is arbitrary and refers to your key for identifying the object from script



Now that I've done this, I have to mark each Silverlight page class with the ScriptableType attribute:


[ScriptableType()]
public partial class Page : UserControl
{
/* ... */



Also, each public method I intend to call in the marked class from external script must be marked with the attribute:


[ScriptableMember()]
public void SetSearchText(string msg)
{
/* ... */



2) Set up scripting support in the web page hosting the Silverlight application

The WebBrowser control can call script methods within the page, but doesn't seem to be able to call the Silverlight control object directly (I made several attempts to do this but was unsuccessful). What I ended up doing that worked was to set up a script method corresponding to each method I want to call within the Silverlight application.

First, alter the tag on the Silverlight control so that it calls a script method of yours when it loads:



<div style="height:100%;">
<asp:Silverlight ID="Xaml1" runat="server" Source="~/ClientBin/MySilverlightApp.xap" Version="2.0" Width="100%" Height="100%" OnPluginLoaded="pluginLoaded" />
</div>



Now, add a script block in the HEAD tag with a method corresponding to the above to store a reference to the Silverlight control when it loads:



<head runat="server">
<title>Test Page For DiggSample</title>
<script type="text/javascript">
var scControl;

function pluginLoaded(sender) {
scControl = sender.get_element();
}
</script>
...



Next, add one script method for each method you marked exposed within the Silverlight application. Note the formatting (you must call it in the form: objectName.Content.<name you chose as the Silverlight script object identifier>.method(arguments)):



<script type="text/javascript">
function SetSearchText(msg) {
scControl.Content.Page.SetSearchText(msg);
}
</script>



Finally, from within the Windows Forms application, you can call the method you exposed via the WebBrowser control as follows:



private void btnSetSearchText_Click(object sender, EventArgs e)
{
webBrowser1.Document.InvokeScript("SetSearchText", new string[] { "foo" });
}



That's it--you should be able to have two-way communication between the Windows Forms application and the Silverlight application hosted via its WebBrowser control.

4 comments:

  1. Are you able to step into the Silverlight application in the debugger? If so, how? When I set a breakpoint in the silverlight app and start the debugger it says "The breakpoint will not currently be hit. No symbols have been loaded for this document".

    ReplyDelete
  2. I've only been able to get the Silverlight debugger to work when I use the Silverlight project types and debug via Visual Studio and IIS or Cassini rather than from the Forms client.

    ReplyDelete
  3. Hi,

    Great job with Silverlight/desktop integration. I've implemented a silverlight application based on your code, but i can't pass Complex types as a parameter to Silverlight methods from my desktop application, it seems that only simple types work. Is it possible to do that?

    ReplyDelete
  4. can u help me with it?
    i can't understand how it works, and can't understand where parts of this code i must to insert

    ReplyDelete