Lat Lays Flat - 第 3 部分:创建 Google Maps .NET 控件





5.00/5 (11投票s)
2005 年 10 月 4 日
12分钟阅读

290617

2764
创建 Google Maps API 的 ASP.NET 服务器控件包装器。
引言
这是三篇文章系列中的第三篇,该系列探讨了我开发的一个自定义 ASP.NET 服务器控件,以方便 .NET 开发者使用 Google Maps API。这是一篇技术文章,不侧重于 Google Maps .NET 控件的使用。如果您想了解这个控件的功能,请查阅 第 1 部分和 第 2 部分。本文假设您熟悉 Google Maps API。您可能会在文章中看到关于“我的控件,我的 GoogleMaps 控件,我的 GMap 控件等”的引用。Google Maps API 不是我创建的;我只是用 C#、XML 和 XSL 为 ASP.NET 创建了一个包装器。
本文的主要目标是向您展示我是如何创建 Google Maps .NET 控件的;我做出的设计决策、使用的技术以及涉及的工具。
涵盖的一些主题包括
- XmlSerialization – 将对象转换为 XML。
- XSL/XSLT – 将 XML 转换为 JavaScript。
- AJAX – 从客户端调用服务器端代码。
- ASP.NET 服务器控件 – 自定义事件、数据绑定、回发数据处理和渲染。
服务器控件
我花了大量时间详细记录了 GMap.cs 的代码。与其逐行阅读并重写我的文档,不如重点介绍 GMap
背后的魔力。GMap
控件包含许多高级内容,仅通过阅读可能难以理解。我鼓励您在阅读本文时参考本系列文章的 第 2 部分。第 1 部分和 第 2 部分是“是什么”,而第 3 部分是“怎么做”。有一队猴子正在全天候努力弄清楚“为什么”。
首先
创建 GMap
控件的第一步是使用 C# 重写 Google Maps API 的所有 JavaScript 对象。如果您下载了代码(请参见上面的下载源文件),然后在 WCPierce.Web 项目中,向下钻取文件夹直到 GoogleMap。在此文件夹中,您将看到每个 Google Maps API 对象都有一个代码文件。GIcon
、GMarker
、GPoint
、GSize
等。每个对象都经过重写,以精确模仿其 JavaScript 对等项的功能。如果您仔细查看这些对象中的任何一个,您会发现它们都带有 System.Xml.Serialization
属性。当我们想将 C# 转换为 XML 时(稍后会详细介绍),这些属性会派上用场。那些 Rolla 的家伙有一篇关于 XML Serialization 的好文章,如果您想了解更多信息。
为什么要费力地用 C# 重写 JavaScript 功能?以便您可以使用您选择的 .NET 语言编写简洁的 Google Maps 代码。下面的示例显示了如何创建一些点和标记并将其添加到地图中
GPoint gp = new GPoint(-122.141944F, 37.441944F);
GMarker gm = new GMarker(gp, "FirstMarker");
gMap.Overlays.Add(gm);
gm = new GMarker(new GPoint(gp.X + 0.005F,
gp.Y + 0.005F), "SecondMarker");
gMap.Overlays.Add(gm);
gMap.CenterAndZoom(gp, 4);
公共方法
Google Maps API 中的每个 public
方法都在 GMap
控件中进行了重写。每个方法都接收提供的参数,并创建执行客户端方法所需的 JavaScript 代码。这可能有点奇怪:服务器端代码正在创建客户端代码。但真正的目标是让代码在客户端执行。下面的示例展示了正确的 JavaScript 的创建
public string CenterAndZoom( GPoint LatLng, int ZoomLevel )
{
string str = String.Format(Utilities.UsCulture,
"{0}.centerAndZoom(new GPoint({1}, {2}), {3});",
this.JsId, LatLng.X, LatLng.Y, ZoomLevel);
initJs.Append(str);
return str;
}
public string ZoomTo(int ZoomLevel )
{
string str = String.Format(Utilities.UsCulture,
"{0}.zoomTo({1});", this.JsId, ZoomLevel);
initJs.Append(str);
return str;
}
控件的 JavaScript ID,this.JsId
,由控件创建以确保唯一性。这允许在页面上操作多个 GMap
控件。生成的 JavaScript 代码被添加到 StringBuilder
initJs
中,并且还是每个方法调用的返回值。这使得开发人员可以使用该控件来初始化 GMap
(例如,在 Page_Load
处理程序中),以及在客户端回调期间进行方法调用(稍后会详细介绍)。
CreateChildControls()
在 CreateChildControls()
方法中,我们添加了四个 HtmlInputHidden
控件
protected override void CreateChildControls()
{
base.CreateChildControls();
string id = this.ID;
HtmlInputHidden centerLatLng = new HtmlInputHidden();
centerLatLng.ID = id + _centerLatLngField;
base.Controls.Add(centerLatLng);
HtmlInputHidden spanLatLng = new HtmlInputHidden();
spanLatLng.ID = id + _spanLatLngField;
base.Controls.Add(spanLatLng);
HtmlInputHidden boundsLatLng = new HtmlInputHidden();
boundsLatLng.ID = id + _boundsLatLngField;
base.Controls.Add(boundsLatLng);
HtmlInputHidden zoomLevel = new HtmlInputHidden();
zoomLevel.ID = id + _zoomLevelField;
base.Controls.Add(zoomLevel);
}
这些控件允许 GMap
控件在回发期间捕获 GMap
的 CenterLatLng
、SpanLatLng
、BoundsLatLng
和 ZoomLevel
值。例如,假设您将一个 GMap
添加到页面和一个 asp:button
。然后用户在地图上移动,可能放大/缩小,然后点击您的按钮。在您的按钮的事件处理程序中,您可以根据用户点击按钮时地图的位置检索地图的中心坐标和当前缩放级别。
OnDataBinding()
GMap
控件允许开发人员将来自各种数据源(自定义业务对象、ArrayList
、DataSet
等)的数据绑定到自动在地图上创建标记。开发人员指定以下字段来实现数据绑定:DataLatitudeField
、DataLongitudeField
、DataMarkerIdField
和 DataIconIdField
。纬度和经度字段是必需的。如果开发人员未指定 MarkerId 和 IconId 字段,则会创建没有 ID 和默认(红色)图标的标记。开发人员现在可以使用类似下面的代码轻松地将标记添加到地图中
DataSet ds = GetCounties();
gMap.DataSource = ds;
gMap.DataMarkerIdField = "CountyName";
gMap.DataLongitudeField = "Longitude";
gMap.DataLatitudeField = "Latitude";
gMap.DataBind();
OnDataBinding()
获取绑定的 DataSource
,并遍历记录以查找指定的字段,为每个项创建一个 GMarker
。
Render()
对于复杂的服务器控件(如 GMap
),Render()
通常是繁忙的方法。但是,我决定做一些巧妙的事情。构成服务器端 GMap
的所有对象(GIcons
、GMarkers
、GPoints
、GPolyines
等)都使用标准的 .NET XML 序列化转换为 XML。还记得我在文章开头指出的序列化属性吗?这与框架的内置功能相结合,使我们能够轻松地从 .NET 对象生成 XML。
protected override void Render(HtmlTextWriter output)
{
base.Render(output);
StringBuilder sb = new StringBuilder();
string path = Page.Server.MapPath(this.ScriptFolderPath +
"/" + "GMap.xsl");
XsltArgumentList xal = GetXsltArguments();
sb.Append(GXslt.Transform(_gxpage, path, xal));
Page.RegisterStartupScript(this.UniqueID, sb.ToString());
}
RaisePostBackEvent()
我创建了 GMap
以支持五个事件:Click、Zoom、Move Start、Move End 和 Marker Click。这些是响应客户端回调的服务器端事件。如果您对客户端回调有点模糊,请查看我的文章系列 AJAX Was Here。归根结底是运行服务器端代码作为客户端事件的结果。通过一些自定义 JavaScript,我们将希望引发的事件以及任何事件参数发送给 GMap
控件。RaisePostBackEvent()
解析事件请求并引发适当的服务器端事件,然后运行开发者的代码。
public void RaisePostBackEvent(string eventArgument)
{
// If this isn't a call back we won't bother
if( !CallBackHelper.IsCallBack )
return;
// Always wrap callback code in a try/catch block
try
{
string[] ea = eventArgument.Split('|');
string[] args = null;
GPointEventArgs pea = null;
string evt = ea[0];
switch( evt )
{
// GMap Click event sends the coordinates
// of the click as event argument
case "GMap_Click":
args = ea[1].Split(',');
pea = new GPointEventArgs(float.Parse(args[0]),
float.Parse(args[1]), this);
this.OnClick(pea);
break;
// GMarker Click event sends the coordinates of
// the click as event argument
case "GMarker_Click":
args = ea[1].Split(',');
GPoint gp = new GPoint(float.Parse(args[0]),
float.Parse(args[1]));
GMarker gm = new GMarker(gp, args[2]);
pea = new GPointEventArgs(gp, gm);
this.OnMarkerClick(pea);
break;
// GMap Move Start event sends the
// coordinates of the center of the
// map where the move started
case "GMap_MoveStart":
args = ea[1].Split(',');
pea = new GPointEventArgs(float.Parse(args[0]),
float.Parse(args[1]));
this.OnMoveStart(pea);
break;
// GMap Move End event sends the
// coordinates of the center of the
// map where the move ended
case "GMap_MoveEnd":
args = ea[1].Split(',');
pea = new GPointEventArgs(float.Parse(args[0]),
float.Parse(args[1]));
this.OnMoveEnd(pea);
break;
// GMap Zoom event sends the old and new zoom levels
case "GMap_Zoom":
args = ea[1].Split(',');
GMapZoomEventArgs zea =
new GMapZoomEventArgs(int.Parse(args[0]),
int.Parse(args[1]));
this.OnZoom(zea);
break;
// Default: we don't know what the
// client was trying to do
default:
CallBackHelper.Write(String.Empty);
break;
}
}
// Had some odd Thread Abort Exceptions
// going around. Something to do with
// the default behavior of Response.End.
// Just ignore these exceptions
catch(Exception e)
{
if( !(e is System.Threading.ThreadAbortException) )
CallBackHelper.HandleError(e);
}
}
开发人员可以“连接”这些事件,类似于下面的示例。
private void Page_Load(object sender, System.EventArgs e)
{
//. . .
gMap.MarkerClick +=
new GMapClickEventHandler(gMap_MarkerClick);
//. . .
}
或
<wcp:GMap runat="server" id="gMap" Width="750px" Height="525px"
EnableClientCallBacks="True" OnMarkerClick="gMap_MarkerClick"/>
protected string gMap_MarkerClick(object s, GPointEventArgs pea)
{
//. . .
}
每个事件的结果都会写回客户端。结果应该是开发人员希望在客户端执行的 JavaScript 代码。
protected virtual void OnMarkerClick(GPointEventArgs pea)
{
GMapClickEventHandler eh =
(GMapClickEventHandler)base.Events[GMap.EventMarkerClick];
if(eh != null)
{
CallBackHelper.Write(eh(pea.Target, pea));
}
else
{
CallBackHelper.Write(String.Empty);
}
}
这可能很难理解,因此我鼓励您在阅读本文时参考本系列文章的 第 2 部分。
LoadPostData()
GMap
控件中最后一个有趣的方面是 LoadPostData()
方法。此方法在回发期间调用,以便自定义控件可以从已发布的表单中检索数据。在这里,我们获取在 CreateChildControls()
期间添加的 HtmlInputHidden
控件中的值。我们获取这些值并将其保存到成员变量中,以便开发人员在需要时可以访问它们。
public bool LoadPostData(string postDataKey,
System.Collections.Specialized.NameValueCollection postCollection)
{
string uId = this.UniqueID;
try { _centerLatLng = (GPoint)postCollection[uId + _centerLatLngField]; }
catch { }
try { _spanLatLng = (GSize)postCollection[uId + _spanLatLngField]; }
catch { }
try { _boundsLatLng = (GBounds)postCollection[uId + _boundsLatLngField]; }
catch { }
try { _zoomLevel = Convert.ToInt32(postCollection[uId + _zoomLevelField]); }
catch { }
return false;
}
XSL
在我开始解释 GMap.xsl 之前,我想提供一些个人观点。在从事 GMap
工作之前,我从未用过 XSL。我认为尝试一下会很有趣,并出于几个原因决定使用它
- Google Maps API 仍在不断发展。如果 Google 决定更改 API,我需要一种简单的方法来调整我的控件生成的 JavaScript。XSL 似乎是答案。
- 我想学习新东西,看看 XSL 到底是怎么回事。
- 我想通过 XSL 这个热门词来推广我的文章,吸引更广泛的受众。
现在我已经实际在项目中使用了 XSL,我得出了这个结论:XSL 是魔鬼。我不确定我是否是唯一有这种想法的人,所以我做了一些研究,偶然发现了一篇 Michael Leventhal 的精彩文章,标题为 XSL Considered Harmful。我认为 Michael 提出了很多很好的观点,我开发过程中也遇到了其中许多。但考虑到这篇文章已经有六年了,而我们今天仍然使用 XSL,我想那些施虐狂赢得了保留 XSL 作为可行开发工具的斗争。但现在,从我的个人观点中走出来。
下面是一个由简单 GMap
生成的 XML 示例
<?xml version="1.0" encoding="utf-16"?>
<GXPage xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Overlays>
<GMarker Id="Motel 6 (#1054)" IconId="BlueMarker">
<Point X="-122.029" Y="37.3953" />
</GMarker>
</Overlays>
<Controls>
<GSmallMapControl />
</Controls>
<Icons>
<GIcon Id="BlueMarker"
Image="https:///TestWeb/Advanced/blueMarker.png"
Shadow="" PrintImage="" MozPrintImage="" PrintShadow=""
Transparent="">
<ImageMap />
</GIcon>
</Icons>
</GXPage>
这个 XML 片段不应与 Google Maps 原生理解的原始/当前 XML 混淆。我决定做自己的事情的原因是 Google Maps XML 格式没有正式的文档,而且我不想承诺可能在不久的将来发生变化的内容。现在的目标是将 XML 转换为创建 Google Map 所必需的 JavaScript。这是通过我创建的名为 GMap.xsl 的 XSL 样式表实现的。
用于创建 Google Map 的一些数据不在 XML 文件中。一些值作为参数传递给 XSL 样式表。XML 只包含地图数据,如标记、图标、折线、控件等。GMap.xsl 首先指定输出为文本(而不是 HTML),并声明要传递给样式表的许多参数。开发人员可以使用 GMap
的 GetXsltArguments()
方法访问这些值。
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:param name="jsId" />
<xsl:param name="divId" />
<xsl:param name="controlId" />
<xsl:param name="enableClientCallBacks" />
<xsl:param name="friendlyControlId" />
<xsl:param name="initJs" />
<xsl:param name="enableDragging" />
<xsl:param name="enableInfoWindow" />
<xsl:param name="zoomLevel" />
<xsl:param name="mapType" />
样式的下一部分获取提供的参数,并使用它们来创建和初始化具有适当 API 值的 Google Map。在创建 GMap
期间调用的任何方法(CenterAndZoom()
、OpenInfoWindow()
等)都会添加到 InitJS
成员变量中。InitJS
包含使客户端能够调用所需 Google API 方法的 JavaScript(请参见上面的公共方法)。实际的地图初始化代码放置在一个方法中,该方法稍后在窗口加载完成后调用。这样做的原因是为了避免 Internet Explorer 出现“操作已中止”错误。
var <xsl:value-of select="$jsId" /> = null;
function <xsl:value-of select="$friendlyControlId" />_Render() {
if( GBrowserIsCompatible()) {
<xsl:value-of select="$jsId" /> =
new GMap( document.getElementById(
"<xsl:value-of select="$divId" />"));
<xsl:value-of select="$jsId" />.id = '<xsl:value-of select="$controlId" />';
<xsl:if test="$enableDragging=false()">
<xsl:value-of select="$jsId" />.disableDragging();
</xsl:if>
<xsl:if test="$enableInfoWindow=false()">
<xsl:value-of select="$jsId" />.disableInfoWindow();
</xsl:if>
<xsl:value-of select="$jsId" />.zoomTo(
<xsl:value-of select="$zoomLevel" />);
<xsl:value-of select="$jsId" />.setMapType(
<xsl:value-of select="$mapType" />);
<xsl:value-of select="$initJs" />
如果开发人员在 GMap
控件上启用了客户端回调,则将五个服务器端回调事件 Click、Marker Click、Zoom、Move Start 和 Move End 附加到 GMap
。这些事件的名称为 GMap_Server<Event Name>
(稍后将在下面的 JavaScript 部分中详细介绍)。
<xsl:if test="$enableClientCallBacks=true()">
GEvent.addListener(<xsl:value-of select="$jsId" />, 'click',
window.GMap_ServerClick);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'movestart',
window.GMap_ServerMoveStart);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'moveend',
window.GMap_ServerMoveEnd);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'zoom',
window.GMap_ServerZoom);
</xsl:if>
一些默认事件已“连接”到 Google Map。开发人员不必指定哪些 JavaScript 函数响应哪些事件,而是将名为 GMap_<Event Name>
的默认事件附加到 Google Map。如果开发人员想要响应这些事件,则由开发人员创建这些 JavaScript 函数。所有这些事件都是可选的。这是通过一些 JavaScript 技巧实现的。如果页面或链接的 JavaScript 文件中找不到名为 GMap_Click
的事件,则使用一个空函数 _ef()
代替。所以,如果你写了它,它就会运行。
GEvent.addListener(<xsl:value-of select="$jsId" />,
'click', window.GMap_Click||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />,
'move', window.GMap_Move||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'movestart',
window.GMap_MoveStart||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'moveend',
window.GMap_MoveEnd||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />,
'zoom', window.GMap_Zoom||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'maptypechanged',
window.GMap_MapTypeChanged||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'windowopen',
window.GMap_WindowOpen||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'windowclose',
window.GMap_WindowClose||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'addoverlay',
window.GMap_AddOverlay||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'removeoverlay',
window.GMap_RemoveOverlay||_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />, 'clearoverlays',
window.GMap_ClearOverlays||_ef);
两个附加的事件绑定允许 GMap
捕获地图移动或缩放时的状态。数据从 Google Map 复制到 GMap
生成的四个 HtmlInputHidden
字段。
GEvent.addListener(<xsl:value-of select="$jsId" />,
'moveend', window.GMap_SaveState|_ef);
GEvent.addListener(<xsl:value-of select="$jsId" />,
'zoom', window.GMap_SaveState||_ef);
window.GMap_SaveState(<xsl:value-of select="$jsId" />);
GMap.xsl 的其余部分并不像看起来那么可怕。每个地图对象都创建了一个相应的 XSL 模板。每个模板都创建了创建地图的 Google Maps API 对象所需的 JavaScript。标记的默认和客户端回调事件也使用与地图相同的命名约定添加,如下所示
<xsl:if test="$enableClientCallBacks=true()">
GEvent.addListener(<xsl:value-of select="$gmId" />,
'click', window.GMarker_ServerClick);
</xsl:if>
GEvent.addListener(<xsl:value-of select="$gmId" />,
'click', window.GMarker_Click||_ef);
GEvent.addListener(<xsl:value-of select="$gmId" />,
'infowindowopen', window.GMarker_InfoWindowOpen||_ef);
GEvent.addListener(<xsl:value-of select="$gmId" />,
'infowindowclose', window.GMarker_InfoWindowClose||_ef);
上面示例中完全转换的 XML 如下所示
<script type="text/javascript">
var gMap_Js = null;
function gMap_Render() {
if( GBrowserIsCompatible()) {
gMap_Js = new GMap( document.getElementById("gMap_Div"));
gMap_Js.id = 'gMap';
gMap_Js.zoomTo(1);
gMap_Js.setMapType(G_MAP_TYPE);
gMap_Js.centerAndZoom(new GPoint(-105.5, 39), 10);
GEvent.addListener(gMap_Js, 'click',
window.GMap_ServerClick);
GEvent.addListener(gMap_Js, 'movestart',
window.GMap_ServerMoveStart);
GEvent.addListener(gMap_Js, 'moveend',
window.GMap_ServerMoveEnd);
GEvent.addListener(gMap_Js, 'zoom',
window.GMap_ServerZoom);
GEvent.addListener(gMap_Js, 'click',
window.GMap_Click||_ef);
GEvent.addListener(gMap_Js, 'move',
window.GMap_Move||_ef);
GEvent.addListener(gMap_Js, 'movestart',
window.GMap_MoveStart||_ef);
GEvent.addListener(gMap_Js, 'moveend',
window.GMap_MoveEnd||_ef);
GEvent.addListener(gMap_Js, 'zoom',
window.GMap_Zoom||_ef);
GEvent.addListener(gMap_Js, 'maptypechanged',
window.GMap_MapTypeChanged||_ef);
GEvent.addListener(gMap_Js, 'windowopen',
window.GMap_WindowOpen||_ef);
GEvent.addListener(gMap_Js, 'windowclose',
window.GMap_WindowClose||_ef);
GEvent.addListener(gMap_Js, 'addoverlay',
window.GMap_AddOverlay||_ef);
GEvent.addListener(gMap_Js, 'removeoverlay',
window.GMap_RemoveOverlay||_ef);
GEvent.addListener(gMap_Js, 'clearoverlays',
window.GMap_ClearOverlays||_ef);
GEvent.addListener(gMap_Js, 'moveend',
window.GMap_SaveState|_ef);
GEvent.addListener(gMap_Js, 'zoom',
window.GMap_SaveState||_ef);
window.GMap_SaveState(gMap_Js);
var gMarker1 = new GMarker(
new GPoint(-122.029,37.3953), BlueMarker);
gMarker1.id='Motel 6 (#1054)';
GEvent.addListener(gMarker1, 'click',
window.GMarker_ServerClick);
GEvent.addListener(gMarker1, 'click',
window.GMarker_Click||_ef);
GEvent.addListener(gMarker1, 'infowindowopen',
window.GMarker_InfoWindowOpen||_ef);
GEvent.addListener(gMarker1, 'infowindowclose',
window.GMarker_InfoWindowClose||_ef);
gMap_Js.addOverlay(gMarker1);
gMap_Js.addControl(new GSmallMapControl());
}
}
window.addListener(window, 'load', gMap_Render);
var BlueMarker = new GIcon(_defaultMarker.icon);
BlueMarker.image =
"https:///TestWeb/Advanced/blueMarker.png";
</script>
JavaScript
GMap
的客户端回调和回发数据功能封装在可下载代码中的 GMapX.js 文件中。大多数回调的底层结构都是使用我在另一组 文章中讨论的 CallBackObject
构建的。大多数其他方法将填补本文到此部分留下的空白。
前几行创建了一些全局变量,这些变量在页面上添加 GMap
时会使用。还有一些代码用于向 Google Maps API GMap
对象添加一个方法。getOverlayById()
允许开发人员通过先前分配的字符串标识符检索覆盖。接下来的两行代码为将事件处理程序附加到 DOM 对象提供跨浏览器支持。
var _ef = function(){};
var _defaultMarker = new GMarker();
var _gMapRegX = new RegExp(":", "gi");
_gMapRegX.compile(":", "gi");
GMap.prototype.getOverlayById=function(a)
{
for(var b=0;b<this.overlays.length;b++)
{
if(this.overlays[b].id==a)return this.overlays[b];
}
return null;
};
function addListener(a,b,c,d)
{
if(a.addEventListener)
{
a.addEventListener(b,c,d);
return true;
}
else if(a.attachEvent)
{
var e=a.attachEvent("on"+b,c);
return e;
}
else
{
alert("Handler could not be attached");
}
}
function bind(a,b,c,d)
{
return window.addListener(a,b,
function()
{
d.apply(c,arguments)
}
);
}
接下来,我们将跳到 GMap_Server<Event Name>
函数。每个函数构建一个参数字符串,包括应该引发的服务器端事件名称以及与之对应的事件参数,格式为 {event}|{argument1},{argument2}...{argumentN}
。
function GMap_ServerClick(overlay, point)
{
var arg = 'GMap_Click|'+point.x+','+point.y;
__DoCallBack(this, arg);
}
还记得 XML 样式表是如何将事件绑定到 GMap
的吗?当这些事件被触发时,this
代表导致事件的 GMap
(除了 GMap_MarkerClick
,其中 this
代表被点击的标记)。使用 this
,我们可以收集需要发送回服务器以执行服务器端事件的参数。
在构建了事件参数之后,将调用 __DoCallBack()
,并将 this
(GMap
)和参数传递过去。__DoCallBack()
构建一个新的 CallBackObject
,分配 OnComplete()
和 OnError()
委托,调用 GMap_SaveState()
复制 GMap
的状态,然后使用 cbo.DoCallBack()
执行到服务器的异步请求。
function cbo_Complete(responseText, responseXML)
{
eval(responseText);
}
function cbo_Error(status, statusText, responseText)
{
alert('Error: ' + status + '\n' + statusText + '\n' +
responseText);
}
function __DoCallBack(eventTarget, eventArgument)
{
var cbo = new CallBackObject();
cbo.OnComplete =
function(){cbo_Complete.apply(eventTarget, arguments)};
cbo.OnError = cbo_Error;
window.GMap_SaveState(eventTarget);
cbo.DoCallBack(eventTarget.id, eventArgument);
}
这里值得注意的一点是 OnComplete()
委托的赋值。
- 赋值是使用匿名函数完成的。
- 该函数调用
cbo_Complete
函数的apply()
方法,传递eventTarget
(即正在操作的GMap
或GMarker
)
为什么这样做而不是简单地分配 cbo_Complete
函数?当服务器从回调返回其响应时,它将执行 cbo_Complete
函数。cbo_Complete
函数简单地解释并执行服务器的返回值。返回值应该是开发人员希望在客户端执行的 JavaScript。开发人员现在可以在其返回的代码中使用 this
来操作引发事件的 GMap
或 GMarker
。来自 第 2 部分的 State Quarters 示例利用了此功能,通过调用代表状态的 GMarker
的 OpenInfoWindow()
方法。
最后,GMap_SaveState()
获取对相关 GMap
的引用,该引用要么通过 this
,要么通过传递的 eventTarget
。然后代码获取对 GMap
渲染的 HtmlInputHidden
元素的引用,并用当前地图状态值更新它们。请记住,此方法在客户端回调之前以及主表单因点击提交按钮而提交到服务器之前调用。
function GMap_SaveState(eventTarget)
{
var evt = eventTarget.pan?eventTarget:this;
var evtId = evt.id.replace_gMapRegX,'_');
document.getElementById(evtId + '_CenterLatLng').value =
evt.getCenterLatLng();
document.getElementById(evtId + '_SpanLatLng').value =
evt.getSpanLatLng();
document.getElementById(evtId + '_BoundsLatLng').value =
evt.getBoundsLatLng();
document.getElementById(evtId + '_ZoomLevel').value =
evt.getZoomLevel();
}
结论
这是否让任何人觉得过于复杂?现在您已经阅读了这篇文章,您是否庆幸我将所有这些封装在一个简洁的小服务器控件中?我非常希望收到阅读了整篇文章的任何人的反馈。请通过下面的论坛告诉我,您认为哪些方面得到了充分的覆盖,哪些功能您认为需要更多的解释。
我最近得到了一份 VS 2005 Beta 2 的副本,并且花了很多时间研究 Microsoft Atlas(因此这篇文章的发布延迟了)。请继续关注有关 Atlas、Virtual Earth 和 Markup Maps 的内容。