Freigeben über


Using Virtual Earth in your WPF application

Technorati Tags: virtual earth, wpf, ve, geotagging

I wanted to experiment more with WPF, since I don't get to use it in my Real Job. Also, I love me some Virtual Earth, so I was looking to find a way to merge the two. What I came up with is an application which would allow you to tag your photos with geo-location information.

One of the difficulties was with this combination of VE and WPF; it's not an outta-the-box scenario, but it's definitely doable. The crux of the issue is that VE is a web application - it relies on HTML and AJAX to do its magic - but WPF doesn't ship with a full web browser control.

This dilemma is solved by the fact that WPF does support embedding standard Winforms controls, and Winforms does in fact ship with a web browser control.

Aside from that, the only other interesting issue I faced was that for this application to work, I needed two-way communication between the JavaScript running in the browser, and the C# code in my WPF app. Turns out this is also doable :)

I'll divide the rest of the post into the logical parts that I had to deal with.

Creating the Winforms control

This is the easy part. Add a project to your solution of type "Windows Forms Control Library". You'll get your typical Winforms canvas to play with, onto which you drag a WebBrowser control from the toolbox. That's essentially it for your control - it's just a web browser inside a container. You will need to call a few methods on the WebBrowser control from your WPF code. To do this, you can either wrap those methods in your user control, or directly expose the WebBrowser control as a property of your user control:

    1: public WebBrowser WebBrowserControl
    2: {
    3:     get { return browserMain; }
    4: }
Embedding the Winforms user control in your WPF app

In order to embed this user control, you'll need to do a few things in your WPF project:

  • Add a reference to System.Windows.Forms
  • Add a reference to WindowsFormsIntegration
  • Add a reference to the new user control project you created.

Once that's done, you can add a WPF WindowsFormsHost, which is a control that hosts Winforms controls. In your XAML, throw this where you want the map to appear:

    1: <WindowsFormsHost x:Name="hostWinForms" />

 

Now notice that this control is somewhat empty - nowhere is your user control mentioned. The WindowsFormsHost is just a husk in which you need to put your custom control. So, let's give this host control a child:

 

    1: browserControl = new WinFormsWebBrowserControl.WebBrowser();
    2: hostWinForms.Child = browserControl;

That's it. Now you can treat this browserControl object like a normal WPF control, and it will do your evil bidding. Let's talk more about that...

Getting the Winforms control to show a map

If you've ever done anything with VE in the browser before, you'll know this is a piece of cake. To get it working in this scenario, you write an HTML page just like you would for a normal website, and then you set the URL of the WebBrowser to point to this file. For example:

    1: browserControl.WebBrowserControl.Url = new Uri("VE.htm");

 

(I recommend getting the explicit path to the HTML file, since you can never trust what environment you'll be in. Better yet, perhaps embed it as a resource).

 

For the actual contents of VE.htm, see the interactive SDK for a working sample. It's really a trivial HTML page.

Talking from WPF to JavaScript

My geo-tagging app needed to be able to give various commands to the map. For example, telling the map div to resize in response to the WPF window resizing, or adding a push-pin. The concept is actually very simple: You can execute any JavaScript command you want, just by calling the WebBrowser.Document.InvokeScript method.

So the map resize functionality consisted of a JavaScript method in my VE.htm page:

    1: function Resize(width, height)
    2: {
    3:     map.Resize(width, height);
    4: }

(The map object is the typical one created by the call to the VEMap() constructor)

... Which was called from WPF like this:

    1: void hostWinForms_SizeChanged(object sender, SizeChangedEventArgs e)
    2: {
    3:     browserControl.WebBrowserControl.Document.InvokeScript("Resize", browserControl.Width, browserControl.Height);
    4: }

I'm basically telling the map to resize to the size of my user control. Notice how the arguments are passed through without casting or anything weird. It Just Works.

Talking from JavaScript to WPF

This is the final bit and a little bit more complex - but not much harder to implement. Javascript supports a system to call methods in the window that's hosting it. This is done using window.external.<method name> .

The catch is twofold:

  • You need to tell the browser control where to direct these method calls to.
  • The class that accepts these method calls need to be marked with an attribute that allows it to be visible to COM classes (which the browser control is).

Here's the minor issue: It's tempting to just make your main WPF page the go-to object and have it handle all the calls. The problem is that because it inherits from Window, it can't take this attribute.

We get around this by making an intermediate WPF class that can take the attribute and that knows about your main window class. This object can then call methods in your main window. Something like this:

    1: [ComVisible(true)]
    2: public class ObjectForScriptingHelper
    3: {
    4:     MainWindow m_Window;
    5:  
    6:     public ObjectForScriptingHelper(MainWindow w)
    7:     {
    8:         m_Window = w;
    9:     }
   10:  
   11:     public void MapPositionChange(double lat, double lon, int zoom)
   12:     {
   13:         m_Window.MapPositionChanged(lat, lon, zoom);
   14:     }
   15: }

Looking at the above, we have two methods:

  • Constructor: Takes an instance of my MainWindow class, and stores it for later reference.
  • MapPositionChange: This is a method which will be called by the JavaScript on the VE.htm page whenever the map wants to inform us that the user changed the map location. When this happens, this method calls a corresponding method in the MainWindow class, which simply records the Latitude/Longitude.

So the calling path is like this:

    JavaScript --> ObjectForScriptingHelper instance --> MainWindow instance.

How do we tell the JavaScript to call the ObjectForScriptingHelper class, rather than just the user control that's hosting the WebBrowser control? Like this, in our MainWindow constructor:

    1: ScriptHelper = new ObjectForScriptingHelper(this);
    2: browserControl.WebBrowserControl.ObjectForScripting = ScriptHelper;
Conclusion

That's it! Once you have this framework set up, you can add method calls back/forth between the VE.htm page and the WPF code. So it's a little complex, but there's no major code to be written. Once you get the concept it's not too bad - and the end result is gorgeous.

Avi

Comments

  • Anonymous
    August 22, 2007
    Hi, can you send me a copy of your code? I am also in Microsoft and my alias is qiweiye Thanks.

  • Anonymous
    August 23, 2007
    Sure, I've just sent it. Tell me if you have issues with it, although it's totally experimental code. Avi

  • Anonymous
    August 25, 2007
    Link Listing - August 25, 2007

  • Anonymous
    September 15, 2007
    Hi , I am looking for WPF application with Virtual Earth aupport . Can you please send me the source code to my mail ID : vivekhire@yahoo.com Waiting for your replt. -vivek

  • Anonymous
    October 04, 2007
    It would be nice , if you can post your code in your blog. Thanks

  • Anonymous
    October 23, 2007
    Great Article.  If your making the source code public, I would also like a copy of it.  My email is: bdsoko@gmail.com Thanks...

  • Anonymous
    January 13, 2008
    Great post can you share the source code? email: danny.mcguire@ardcorp.tv

  • Anonymous
    February 28, 2008
    Very Great ! Could you share the source code ? MyEmail:phongthienvu86@yahoo.com Thanks

  • Anonymous
    March 04, 2008
    Could you share the source code? My mail: baotieu311@yahoo.com Thanks so much !!!

  • Anonymous
    March 05, 2008
    hi! great article! posting the code would be nice.

  • Anonymous
    April 22, 2008
    hi Can u send me your code please . thank Mymail: ruijing2@hotmail.com

  • Anonymous
    April 22, 2008
    hi Can u send me your code please . thank Mymail: ruijing2@hotmail.com

  • Anonymous
    May 01, 2008
    Excellent! why is not this code available for download? it would be really helpful. thanks in advance

  • Anonymous
    May 08, 2008
    Cold you send me too? Veryvery thanks :D

  • Anonymous
    May 19, 2008
    This was exactly what I was looking for.. Could you send a copy of the code to sandygk@hotmail.com

  • Anonymous
    May 27, 2008
    Hi I saw ur blog today .. It would be a great help if you could send me the source code for the above sample to my id cuul_sam@yahoo.com I am stuck up and really need this code for going on ... Please help Regards Sam

  • Anonymous
    July 11, 2008
    Can you send me a copy of your source code. My mailid is silkykasturia@gmail.com

  • Anonymous
    August 11, 2008
    Nice job dditweb! Could you send me a copy of your source...please. arthurwilson@optimate.net.au

  • Anonymous
    November 17, 2008
    Nice Blog. Could you please post your sample code?

  • Anonymous
    December 01, 2008
    Hi, Could I also ask for a copy of your sample code= ...thx erik.svensen@hotmail.com

  • Anonymous
    December 10, 2008
    Could u please send me a copy of your sample code thanks sallyhehe@hotmail.com

  • Anonymous
    December 10, 2008
    Thank you for your post. Could you send me a copy of your source code? I also work for Microsoft. My alias is jizho. Thanks!

  • Anonymous
    December 18, 2008
    I too thank you and wish to have a copy of your code.  Send to johncannon4@msn.com.  Thanks!

  • Anonymous
    February 20, 2009
    Hi, I'm attending ImagineCup thesedays. My rols is Map system, so could you send me a copy of your source code? My e-mail address is toimarokfan@nate.com

  • Anonymous
    March 06, 2009
    Hello, can you send me copy of your source code? My email address is robneox(at)gmail.com

  • Anonymous
    June 02, 2009
    Hi , I am in need of code can you just mail it to me in this address  vigneshkumar1988@gmail.com

  • Anonymous
    June 06, 2009
    Hi, its a nice trick and a good lesson on forms and wpf. Can I have a copy of your code? Thanks in advance. (SchnittgerS@web.de)

  • Anonymous
    July 04, 2009
    can you share the code plz pooran at live dot com

  • Anonymous
    July 09, 2009
    Thanks! Can i have a copy to0? (t-yishan@microsoft.com)

  • Anonymous
    September 02, 2009
    Could you please share the source code? email: rvenkat_gri@yahoo.com

  • Anonymous
    February 15, 2010
    Can I get a copy of the code please. wallstreetinsider@gmail.com

  • Anonymous
    February 27, 2010
    Many thanks man, works a treat ! This was exactly what I was looking for.

  • Anonymous
    July 15, 2010
    I was hope if it isn't too late to ask for a copy... Send the copy to punisherball54@gmail.com many thanks!!

  • Anonymous
    December 15, 2010
    If it isn't too late would you mind sending me a copy ... Send to damian.mcdonald@xtra.co.nz

  • Anonymous
    March 29, 2011
    Can i have a copy of the code too thank hope i not too late yuanfa86@gmail.com

  • Anonymous
    July 18, 2011
    Can I also have a copy please? andrew.goodwin@cownaycorp.com