Google Maps in a Windows Form
How to use a Google Map in a Windows Form
Section titled “How to use a Google Map in a Windows Form”The first part of this example explains how to implement it. In the second, I will explain how it works. This tries to be a general example. The template for the map (see step 3) and the example functions are fully customizable.
################################# IMPLEMENTATION #################################
Step 1. Firstly, create a new project and select Windows Form Application. Let’s leave its name as “Form1”.
Step 2. Add a WebBrowser control (which will hold your map) to your Form1. Let’s call it “wbmap”
Step 3. Create a .html file named “googlemap_template.html” with your favourite text editor and paste the following code:
googlemap_template.html
<!DOCTYPE html><html> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"/> <style type="text/css"> html, body { height: 100%; margin: 0; padding: 0; } #gmap { height: 100%; } </style> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> <script type="text/javascript"> function initialize() { //Use window.X instead of var X to make a variable globally available window.markers = new Array(); window.marker_data = [[MARKER_DATA]]; window.gmap = new google.maps.Map(document.getElementById('gmap'), { zoom: 15, center: new google.maps.LatLng(marker_data[0][0], marker_data[0][1]), mapTypeId: google.maps.MapTypeId.ROADMAP }); var infowindow = new google.maps.InfoWindow(); var newmarker, i; for (i = 0; i < marker_data.length; i++) { if (marker_data[0].length == 2) { newmarker = new google.maps.Marker({ position: new google.maps.LatLng(marker_data[i][0], marker_data[i][1]), map: gmap }); } else if (marker_data[0].length == 3) { newmarker = new google.maps.Marker({ position: new google.maps.LatLng(marker_data[i][0], marker_data[i][1]), map: gmap, title: (marker_data[i][2]) }); } else { newmarker = new google.maps.Marker({ position: new google.maps.LatLng(marker_data[i][0], marker_data[i][1]), map: gmap, title: (marker_data[i][2]), icon: (marker_data[i][3]) }); } google.maps.event.addListener(newmarker, 'click', (function (newmarker, i) { return function () { if (newmarker.title) { infowindow.setContent(newmarker.title); infowindow.open(gmap, newmarker); } gmap.setCenter(newmarker.getPosition()); // Calling functions written in the WF window.external.showVbHelloWorld(); window.external.getMarkerDataFromJavascript(newmarker.title,i); } })(newmarker, i)); markers[i] = newmarker; } } google.maps.event.addDomListener(window, 'load', initialize); </script> <script type="text/javascript"> // Function triggered from the WF with no arguments function showJavascriptHelloWorld() { alert("Hello world in HTML from WF"); } </script> <script type="text/javascript"> // Function triggered from the WF with a String argument function focusMarkerFromIdx(idx) { google.maps.event.trigger(markers[idx], 'click'); } </script> </head> <body> <div id="gmap"></div> </body></html>This will serve as our map template. I will explain how it works later.
Step 4. Add the googlemap_template.hmtl file to your project (right click on your project->add->existing item)
Step 5. Once it appears in your Solution Explorer, set its properties to:
- Build Action -> Embedded Resource
Step 6. Add a new class (right click on your project->add->class). In my example I’ll call it GoogleMapHelper.
Step 7. Paste the following code into your class:
GoogleMapHelper.vb
Imports System.IO Imports System.Reflection Imports System.Text
Public Class GoogleMapHelper
' 1- googlemap_template.html must be copied in the main project folder ' 2- add the file into the Visual Studio Solution Explorer (add existing file) ' 3- set the properties of the file to: ' Build Action -> Embedded Resource ' Custom Tool Namespace -> write the name of the project
Private Const ICON_FOLDER As String = "marker_icons/" 'images must be stored in a folder inside Debug/Release folder Private Const MAP_TEMPLATE As String = "WindowsApplication1.googlemap_template.html" Private Const TEXT_TO_REPLACE_MARKER_DATA As String = "[[MARKER_DATA]]" Private Const TMP_NAME As String = "tmp_map.html"
Private mWebBrowser As WebBrowser
'MARKER POSITIONS Private mPositions As Double(,) 'lat, lon ' marker data allows different formats to include lat,long and optionally title and icon: ' op1: mMarkerData = New String(N-1, 1) {{lat1, lon1}, {lat2, lon2}, {latN, lonN}} ' op2: mMarkerData = New String(N-1, 2) {{lat1, lon1,'title1'}, {lat2, lon2,'title2'}, {latN, lonN, 'titleN'}} ' op3: mMarkerData = New String(N-1, 3) {{lat1, lon1,'title1','image1.png'}, {lat2, lon2,'title2','image2.png'}, {latN, lonN, 'titleN','imageN.png'}} Private mMarkerData As String(,) = Nothing
Public Sub New(ByRef wb As WebBrowser, pos As Double(,)) mWebBrowser = wb mPositions = pos mMarkerData = getMarkerDataFromPositions(pos) End Sub
Public Sub New(ByRef wb As WebBrowser, md As String(,)) mWebBrowser = wb mMarkerData = md End Sub
Public Sub loadMap() mWebBrowser.Navigate(getMapTemplate()) End Sub
Private Function getMapTemplate() As String
If mMarkerData Is Nothing Or mMarkerData.GetLength(1) > 4 Then MessageBox.Show("Marker data has not the proper size. It must have 2, 3 o 4 columns") Return Nothing End If
Dim htmlTemplate As New StringBuilder() Dim tmpFolder As String = Environment.GetEnvironmentVariable("TEMP") Dim dataSize As Integer = mMarkerData.GetLength(1) 'number of columns Dim mMarkerDataAsText As String = String.Empty Dim myresourcePath As String = My.Resources.ResourceManager.BaseName Dim myresourcefullPath As String = Path.GetFullPath(My.Resources.ResourceManager.BaseName) Dim localPath = myresourcefullPath.Replace(myresourcePath, "").Replace("\", "/") & ICON_FOLDER
htmlTemplate.AppendLine(getStringFromResources(MAP_TEMPLATE)) mMarkerDataAsText = "["
For i As Integer = 0 To mMarkerData.GetLength(0) - 1 If i <> 0 Then mMarkerDataAsText += "," End If If dataSize = 2 Then 'lat,lon mMarkerDataAsText += "[" & mMarkerData(i, 0) & "," + mMarkerData(i, 1) & "]" ElseIf dataSize = 3 Then 'lat,lon and title mMarkerDataAsText += "[" & mMarkerData(i, 0) & "," + mMarkerData(i, 1) & ",'" & mMarkerData(i, 2) & "']" ElseIf dataSize = 4 Then 'lat,lon,title and image mMarkerDataAsText += "[" & mMarkerData(i, 0) & "," + mMarkerData(i, 1) & ",'" & mMarkerData(i, 2) & "','" & localPath & mMarkerData(i, 3) & "']" 'Ojo a las comillas simples en las columnas 3 y 4 End If Next
mMarkerDataAsText += "]" htmlTemplate.Replace(TEXT_TO_REPLACE_MARKER_DATA, mMarkerDataAsText)
Dim tmpHtmlMapFile As String = (tmpFolder & Convert.ToString("\")) + TMP_NAME Dim existsMapFile As Boolean = False Try existsMapFile = createTxtFile(tmpHtmlMapFile, htmlTemplate) Catch ex As Exception MessageBox.Show("Error writing temporal file", "Writing Error", MessageBoxButtons.OK, MessageBoxIcon.[Error]) End Try
If existsMapFile Then Return tmpHtmlMapFile Else Return Nothing End If End Function
Private Function getMarkerDataFromPositions(pos As Double(,)) As String(,) Dim md As String(,) = New String(pos.GetLength(0) - 1, 1) {} For i As Integer = 0 To pos.GetLength(0) - 1 md(i, 0) = pos(i, 0).ToString("g", New System.Globalization.CultureInfo("en-US")) md(i, 1) = pos(i, 1).ToString("g", New System.Globalization.CultureInfo("en-US")) Next Return md End Function
Private Function getStringFromResources(resourceName As String) As String Dim assem As Assembly = Me.[GetType]().Assembly
Using stream As Stream = assem.GetManifestResourceStream(resourceName) Try Using reader As New StreamReader(stream) Return reader.ReadToEnd() End Using Catch e As Exception Throw New Exception((Convert.ToString("Error de acceso al Recurso '") & resourceName) + "'" & vbCr & vbLf + e.ToString()) End Try End Using End Function
Private Function createTxtFile(mFile As String, content As StringBuilder) As Boolean Dim mPath As String = Path.GetDirectoryName(mFile) If Not Directory.Exists(mPath) Then Directory.CreateDirectory(mPath) End If If File.Exists(mFile) Then File.Delete(mFile) End If Dim sw As StreamWriter = File.CreateText(mFile) sw.Write(content.ToString()) sw.Close() Return True End Function End ClassNote: The MAP_TEMPLATE constant must include the name of your project
Step 8. Now we can use our GoogleMapHelper class to load the map into our webbrowser by simply creating and instance and calling its loadMap() method. How you build your markerData is up to you. In this example, for clarification, I write them by hand. There are 3 options to define the marker data (see GoogleMapHelper class comments). Note that if you use the third option (including title and icons) you must create a folder called “marker_icons” (or whatever you define in the GoogleMapHelper constant ICON_FOLDER) in your Debug/Release folder and place there your .png files. In my case:
I created two buttons in my Form1 to illustrate how the map and the WF interact. Here is how it looks:
And here is the code:
Form1.vb
Imports System.IOImports System.ReflectionImports System.Security.PermissionsImports System.Text<PermissionSet(SecurityAction.Demand, Name:="FullTrust")><System.Runtime.InteropServices.ComVisible(True)>Public Class Form1
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Me.wbmap.ObjectForScripting = Me
Dim onlyPositions As Double(,) = New Double(2, 1) {{42.13557, -0.40806}, {42.13684, -0.40884}, {42.13716, -0.40729}} Dim positonAndTitles As String(,) = New String(2, 2) {{"42.13557", "-0.40806", "marker0"}, {"42.13684", "-0.40884", "marker1"}, {"42.13716", "-0.40729", "marker2"}} Dim positonTitlesAndIcons As String(,) = New String(2, 3) {{"42.13557", "-0.40806", "marker0", "truck_red.png"}, {"42.13684", "-0.40884", "marker1", "truck_red.png"}, {"42.13716", "-0.40729", "marker2", "truck_red.png"}}
'Dim gmh As GoogleMapHelper = New GoogleMapHelper(wbmap, onlyPositions) 'Dim gmh As GoogleMapHelper = New GoogleMapHelper(wbmap, positonAndTitles) Dim gmh As GoogleMapHelper = New GoogleMapHelper(wbmap, positonTitlesAndIcons) gmh.loadMap()End Sub
'############################### CALLING JAVASCRIPT METHODS ##############################'This methods call methods written in googlemap_template.htmlPrivate Sub callMapJavascript(sender As Object, e As EventArgs) Handles Button1.Click wbmap.Document.InvokeScript("showJavascriptHelloWorld")End Sub
Private Sub callMapJavascriptWithArguments(sender As Object, e As EventArgs) Handles Button2.Click wbmap.Document.InvokeScript("focusMarkerFromIdx", New String() {2})End Sub'#########################################################################################
'############################### METHODS CALLED FROM JAVASCRIPT ##########################'This methods are called by the javascript defined in googlemap_template.html when some events are triggeredPublic Sub getMarkerDataFromJavascript(title As String, idx As String) MsgBox("Title: " & title & " idx: " & idx)End Sub
Public Sub showVbHelloWorld() MsgBox("Hello world in WF from HTML")End SubEnd ClassIMPORTANT : don’t forget to add these lines before your class Form1 definition:
<PermissionSet(SecurityAction.Demand, Name:="FullTrust")><System.Runtime.InteropServices.ComVisible(True)>What they do is to tell the .NET Framework that we want fulltrust and make the class visible to COM so Form1 is visible to JavaScript.
Also don’t forget this in your Form1 load function:
Me.wbmap.ObjectForScripting = MeIt exposes your Form1 class to the JavaScript on the googlemap_template.hmtl page.
Now you can execute and it should be working
################################# HOW IT WORKS#################################
Basically, what our GoogleMapHelper class does is to read our googlemap_template.html, make a temporal copy, replace the code related to the markers ([[MARKER_DATA]]) and execute the page in the web browser control of our form. This html loops through all the markers and assigns a ‘click’ listener to each one. This click function is obviously fully customizable. In the example it opens an infowindow if the marker has a title, centers the map in such marker and calls two external functions that are defined in our Form1 class.
On the other hand, we can define other javascript functions (with or without arguments) in this html to be called from our Windows Form (by using wbmap.Document.InvokeScript).




