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. AviAnonymous
August 25, 2007
Link Listing - August 25, 2007Anonymous
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. -vivekAnonymous
October 04, 2007
It would be nice , if you can post your code in your blog. ThanksAnonymous
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.tvAnonymous
February 28, 2008
Very Great ! Could you share the source code ? MyEmail:phongthienvu86@yahoo.com ThanksAnonymous
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.comAnonymous
April 22, 2008
hi Can u send me your code please . thank Mymail: ruijing2@hotmail.comAnonymous
May 01, 2008
Excellent! why is not this code available for download? it would be really helpful. thanks in advanceAnonymous
May 08, 2008
Cold you send me too? Veryvery thanks :DAnonymous
May 19, 2008
This was exactly what I was looking for.. Could you send a copy of the code to sandygk@hotmail.comAnonymous
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 SamAnonymous
July 11, 2008
Can you send me a copy of your source code. My mailid is silkykasturia@gmail.comAnonymous
August 11, 2008
Nice job dditweb! Could you send me a copy of your source...please. arthurwilson@optimate.net.auAnonymous
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.comAnonymous
December 10, 2008
Could u please send me a copy of your sample code thanks sallyhehe@hotmail.comAnonymous
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.comAnonymous
March 06, 2009
Hello, can you send me copy of your source code? My email address is robneox(at)gmail.comAnonymous
June 02, 2009
Hi , I am in need of code can you just mail it to me in this address vigneshkumar1988@gmail.comAnonymous
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 comAnonymous
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.comAnonymous
February 15, 2010
Can I get a copy of the code please. wallstreetinsider@gmail.comAnonymous
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.nzAnonymous
March 29, 2011
Can i have a copy of the code too thank hope i not too late yuanfa86@gmail.comAnonymous
July 18, 2011
Can I also have a copy please? andrew.goodwin@cownaycorp.com