基于SWT实现桌面版GoogleMap

声明:本文属魏强(乘风归去)( 执行脚本(该功能在后面被大量使用)
setUrl(url: String)
设置网页地址

控件基本原理

实际上该Google Map控件就是一个Browser控件打开Google Map网页的结果,这个网页中Google Map占用了整个页面,Google Map控件提供的各个Java接口,实际上是Java触发JavaScript调用Google Map API的结果。按照Google Map控件的需求,需要解决如下表的几个问题。
l Browser控件执行JavaScript
l 屏蔽默认的浏览器弹出菜单
l 传递SWT数据到Google Map
l 传递Google Map数据到SWT
l 屏蔽F5刷新
l Google Map控件与Google Map同步缩放

开发环境配置

开发Google Map控件使用到Browser控件的新特性,这需要新版本的SWT支持。这里建议直接使用最新版的RCP开发Eclipse:Eclipse for RCP/Plug-in Developer,使用该版本Eclipse,可以减少很多不必要的配置工作。 点击下载:Eclipse for RCP/Plug-in Developers。
另外,请确认使用普通浏览器可以正常的访问Google Map,这样可以确保开发的Google Map控件的可用性。

Browser控件执行JavaScript

Browser控件执行JavaScript是指在Browser打开的网页内执行某段JS的功能,是由Browser触发执行JS脚本。这个过程类似于利用IE打开某个网页,并且在IE的地址栏中输入JS内容,如图2。按回车,就会在当前网页中弹出hello world对话框,如图3。

图 2. 地址栏输入JS脚本

基于SWT实现桌面版GoogleMap


图 3. 执行脚本


基于SWT实现桌面版GoogleMap
Browser执行JS的原理也是如此,通过调用Brower的execute方法,可以执行某段JS脚本,目标是Browser的当前网页。例如可以这样调用execute方法:execute(“alert(‘hello world’)”),以此实现上述的弹出hello world对话框的功能。事实上执行的脚本可以更加复杂,它可以操作当前网页内部的DOM结构,修改当前网页的内容等等。

屏蔽默认的浏览器弹出菜单'

在使用Browser控件的时候,右击Browser的显示区域,会弹出默认浏览器的下拉菜单,如图4。

图 4. 默认弹出菜单


基于SWT实现桌面版GoogleMap
桌面控件的下拉菜单应该是可以自定义的,为了让Google Map控件更加贴近SWT桌面控件,并且防止用户点击下拉菜单的选项而引起误操作,需要屏蔽默认的Browser右键弹出菜单。

清单 2. 屏蔽右键菜单

final Browser browser = …;
browser.addMouseListener(new MouseListener() {
public void mouseDoubleClick(MouseEvent arg0) {
}
public void mouseDown(MouseEvent event) {
if (event.button == 3)
browser.execute("document.oncontextmenu = function() {return false;}");
}
public void mouseUp(MouseEvent arg0) {
}
});
为Browser控件添加鼠标事件监听,在mouseDown中处理鼠标单击事件,event.button == 3代表右击操作。如果是鼠标右击事件,browser执行脚本:document.oncontextmenu = function() {return false;},这段JS脚本可以阻止浏览器的当前网页弹出右键菜单。

传递SWT数据到Google Map

Browser执行JavaScript脚本,是SWT向JavaScript传递数据的一种途径,因为Browser所执行的JavaScript脚本可以由Java字符串组合而成,传递的数据就包含在字符串当中。清单3是让Browser弹出对话框,显示的内容是Java端传递过去的。

清单 3. Browser执行JS的alert操作

String content = “hello world”;
Browser browser = …;
browser.execute(“alert(‘”+content+“’)”);
清单3中可以看出,弹出对话框显示的内容,是由content字符串决定。Java端修改该content字符串,也就修改了浏览器对话框显示的内容,这也就是Java向JS传递数据的方法。

传递Google Map数据给SWT

最新版本的SWT,支持JavaScript调用Java方法,利用这个特性可以实现Google Map传递数据给Java。要实现传递这个功能,需要按三步走,第一步,Java端需要实现类继承于org.eclipse.swt.browser.BrowserFunction处理JavaScript的调用请求,该类只在新版本的SWT中存在,如清单4。

清单 4. 处理JavaScript调用请求

public class InitFunction extends BrowserFunction {
public InitFunction(Browser browser, String name) {
//name为该函数的名字,JavaScript根据这个名字调用该函数
super(browser, name);
}
public Object function(Object[] arguments) {
//arguments为JavaScript传递来的参数,包含Java端需要的数据
String content = ((String)arguments[0]);
System.out.println(content);
//返回数据给JavaScript端
return content;
}
}
第二步,绑定BrowserFunction到Browser,使得所有Browser打开的网页内,都允许JavaScript调用该BrowserFunction。

清单 5. 绑定BrowserFunction到Browser

Browser browser = …;
new InitFunction(browser, "InitComplete");
第三步,在Browser打开的网页内调用绑定好的名为InitComplete的函数。

清单 6. JavaScript端调用BrowserFunction


上面三个步骤的例子的数据流如图5.

图 5. JavaScript与Java的数据流


基于SWT实现桌面版GoogleMap
主要过程是JavaScript调用绑定的BrowserFunction,传递数据给Java端,Java端得到数据并且处理,返回结果,JavaScript获得结果并处理。

实现步骤

了解前面几个部分的技术基础后,就可以逐步设计实现Google Map的控件了。下个部分将讲解实例展示部分的各个功能实现。

建立模板HTML

该Google Map控件是基于Browser的,控件中实际显示的Google Map也是一个网页,只是这个网页中,Google Map占用了整个页面。因此,需要设计一个模板HTML,该模板网页中使用一个DIV占据整个页面,并且利用Google Map API在此DIV上建立地图。Google Map控件的初始化过程,实际上就是Browser打开模板HTML文件的过程。清单7是模板HTML文件主要代码。

清单 7. 模板HTML文件










屏蔽F5刷新

使用Browser还有一个问题,当用户按F5时,Browser打开的网页会执行刷新操作,网页又会重新加载。为了防止用户误操作,解决方法是需要修改Browser显示的网页内容,重写document.onkeydown方法,如清单8。也就是说,在Google Map控件的开发中,需要修改该控件的模板HTML文件,在模板文件中添加清单8的方法,那么用户按F5就不执行刷新。

清单 8. 屏蔽F5刷新

function document.onkeydown()
{
if ( event.keyCode==116)
{
event.keyCode = 0;
event.cancelBubble = true;
return false;
}
}

Google Map自动缩放

虽然使用模板HTML文件解决了Google Map的显示问题,但是使用Browser的时候,Browser的父容器被调整高度时,Browser有可能会出现滚动条。这在大部分情况下是用户不希望看到的,从用户的角度看,控件被调整大小,地图也应该自动调整大小。清单9为地图自动缩放的代码。

清单 9. 屏蔽F5刷新

browser.addControlListener(new ControlListener() {
public void controlMoved(ControlEvent arg0) {
}
public void controlResized(ControlEvent e) {
browser
.execute("document.getElementById('map_canvas').style.height="
+ browser.getClientArea().height);
}
});
browser.addProgressListener(new ProgressListener() {
public void changed(ProgressEvent arg0) {
}
public void completed(ProgressEvent arg0) {
browser
.execute("document.getElementById('map_canvas').style.height="
+ browser.getClientArea().height);
}
});
为Browser添加事件监听,一个是ProgressListener,监听Browser加载HTML的进度,在页面加载结束时,会触发completed方法,另外一个是ControlListener,在用户设置控件大小时会执行controlResized方法,在上述两个方法中调用Browser的execute方法,执行如下脚本:"document.getElementById('map_canvas').style.height="+browser.getClientArea().height。目的是修改模板HTML文件中放置Google Map的DIV的高度,使得DIV的高度和Browser的ClientArea区域(Browser实际显示的区域)的高度相同,这样滚动条就不会出现。至于宽度为什么不设置,是因为模板HTML中已经设置了DIV的宽度为100%,所以宽度已经会自动缩放,而高度是不能按百分比设置的,需要进行如上处理。

新建Google Map,设置中心点

定义GMap2、GLatLng类,GMap2代表整个地图控件,GLatLng存放经纬度信息,在GMap2类中声明setCenter(GLatLng latlng, int scale)方法,第一个参数是中心点的经纬度,第二个参数是地图的缩放级别。

清单 10. 新建地图,设置中心点

final GMap2 map2 = new GMap2(parent, null);
map2.invoke(new IInvoke() {
public void invoke() {
map2.setCenter(new GLatLng(39.917, 116.397), 14);
}
});
清单10中parent是SWT中Browser所在的父容器,属于SWT对象,IInvoke参数对象的invoke方法是一个回调方法,当地图加载完毕后会调用该invoke方法。回调的原理是定义InitFunction继承于BrowserFunction,命名为Init,这些在GMap2的构造函数中实现,在模板HTML中在地图加载后调用该Init方法,通知Java端地图已经加载完毕,InitFunction接收通知就调用IInvoke参数对象的invoke方法。实际执行setCenter操作的原理是调用Browser的execute方法执行js脚本,调用Google Map API,如清单11,也正是因为调用Google Map API对地图进行操作,才需要等待地图被加载完毕。

清单 11. setCenter实现

public void setCenter(GLatLng latlng, int scale) {
browser.execute("map.setCenter(new GLatLng(" + latlng.getLat() + ", "
+ latlng.getLng() + "), " + scale + ");");
}

添加标记

定义GMarker类,它代表地图的标记,它包含一个GLatLng对象属性,代表此标记所在的经纬度。清单12使用GMarker给地图中心添加标记。清单13是addOverlay的实现代码。

清单 12. 为地图添加标记

map2.addOverlay(new GMarker(map2.getCenter()));

清单 13. addOverlay实现(GMarker)

public void addOverlay(GMarker gMarker) {
browser.execute("map.addOverlay(new GMarker(new GLatLng("
+ gMarker.getCenter().getLat() + ","
+ gMarker.getCenter().getLng() + ")));");
}

弹出提示框

GMap2有一个方法名为openInfoWindow,用于在某个经纬度上弹出信息框。实现如清单14

清单 14. openInfoWindow实现

public void openInfoWindow(GLatLng latlng, String text) {
browser.execute("map.openInfoWindow(new GLatLng(" + latlng.getLat()
+ ", " + latlng.getLng() + "),document.createTextNode('" + text
+ "'));");
}

绘画直线

定义GPolyline类,它代表地图上的某个直线,它包含名为from和to的两个GLatLng对象属性,代表直线的起始经纬度和终点经纬度,color属性指定连线的颜色,scale指定了连线的大小。清单15表明了添加连线的实现原理。

清单 15. addOverlay实现(GPolyline)

public void addOverlay(GPolyline polyline) {
browser.execute("map.addOverlay(new GPolyline([new GLatLng("
+ polyline.getFrom().getLat() + ","
+ polyline.getFrom().getLng() + "),new GLatLng("
+ polyline.getTo().getLat() + "," + polyline.getTo().getLng()
+ ")], '" + polyline.getColor() + "', " + polyline.getScale()
+ "));");
}

添加事件监听

事件监听的原理是在Java端定义事件处理的BrowserFunction;在模板HTML中为地图添加相应事件监听,在事件处理中调用定义的BrowserFunction,传递数据给Java端;BrowserFunction接收数据进行处理,比如操作数据库。清单16是定义的SingleClickFunction,处理鼠标单击事件,清单17绑定SingleClickFunction到Browser上,清单18是模板HTML中定义事件监听,清单19是Java端为地图添加事件监听。

清单 16. SingleClickFunction的实现

public class SingleClickFunction extends BrowserFunction {
private GMap2 map;
public SingleClickFunction (GMap2 map, String name) {
super(map.getBrowser(), name);
this.map = map;
}
public Object function(Object[] arguments) {
IListener listener = map.getListener(GMap2.CLICK);
if (listener != null) {
return listener.function(arguments);
}
return null;
}
public GMap2 getMap() {
return map;
}
}

清单 17. 绑定SingleClickFuction

GMap2 map = …;
new SingleClickFunction(map, “SingleClick”);

清单 18. 模板HTML中添加监听

GEvent.addListener(map,"click", function(overlay,latlng)
{
//调用BrowserFunction传递数据给Java端
SingleClick (latlng.lat(), latlng.lng());
});

清单 19. Java端为地图添加单击监听

map2.addListener(GMap2.CLICK, new IListener() {
public Object function(Object[] arguments) {
//接收传递的数据并进行处理
double lat = ((Double) arguments[0]).doubleValue();
double lng = ((Double) arguments[1]).doubleValue();
System.out.println(“lat is ” + lat + “ lng is ” + lng);
return null;
}
});

为什么要这么做

之所以文章介绍如何实现一个这样的Google Map控件,目的是为了可以将Google Map集成到SWT桌面应用当中,使得编写过Google Map Web应用的人可以很轻松的上手使用该控件。有些读者会有这样的疑问,为什么不直接编写模板HTML页面,让模板HTML页面丰富多彩,而不需要设计很多类。笔者认为,编写模板HTML的做法缺少灵活性,模板HTML页面与Java交互变得非常死板。而设计详细的Java类,使得与JavaScript的交互变得非常简单、粒度小,Java代码和JavaScript调用可以互相穿插调用,业务逻辑可以让Java程序控制,让JavaScript的调用真正的可以面向对象,更容易控制和修改,同时也使得单纯熟悉SWT桌面编程的人也可以使用Google Map。另外,使用这样的设计,地图信息可以保存到数据库中,也就是在地图的操作过程中,可以进行数据库的交互作业,那么这样就和桌面程序无缝结合了。
模板HTML应该用于修改地图的显示样式,比如标记的图片、连线的样式等等可以在模板HTML里设置,至于业务逻辑应该放置到Java端。
笔者的目的也在于让读者了解SWT Browser的新特性,介绍Java与JavaScript交互的各种方式方法以及技巧。

总结

本文的目的主要在于让读者了解SWT Browser的特性以及Java与JavaScript交互的技巧,如果读者有兴趣的话,可以继续扩展该Google Map SWT控件,使得它变得更加OO(Object Oriented),更加的易用。由于笔者知识水平有限,如果有错误的地方,欢迎联系我指正。

参考资源

· 参考 Enhanced JavaScript Bridge 文章,可以了解Browser的特性。
· 查看“Google地图API参考”,参考Google Map API,丰富SWT Google Map控件。
Tags: 

延伸阅读

最新评论

发表评论