基于 SWT 实现桌面版 Google Map

来源:百度文库 编辑:神马文学网 时间:2024/04/29 07:55:57
魏 强, 硕士研究生, 东北大学软件学院
魏强,东北大学软件学院硕士研究生,现在主要从事 Eclipse 插件的开发,同时热爱着 Web 技术,尤其对 Java Web 相关技术,更是情有独钟。他的邮箱是:neuswc20063500@gmail.com
 
简介: 目前 Google Map 的使用已经相当普及,程序员使用 Google Map 构造丰富多彩的 WEB应用。但是很遗憾,Google 没有提供 Java 桌面版的 Google Map,这让很多 Java 桌面应用开发者有些失望。于此同时,SWT作为 IBM 开发的非常优秀的 JAVA 桌面开发包,提供了名为 Browser 的控件,不仅提供了基本的 HTML 显示功能,还提供了与JavaScript 交互的新特性,这为我们与 Google Map 交互提供了捷径。本文利用 Browser的这些特性,通过合理设计模式进行类设计,开发一个 SWT 地图控件。
标记本文!
发布日期: 2010 年 8 月 02 日
级别: 初级
建议: 0 (添加评论)
平均分 (共 6 个评分 )
不知道什么时候开始,地图应用已经如此的普及,基于地图的 WEB 应用丰富多彩。比较著名的有 Google Map、YahooMap、Bing Map 等等,其中使用率最高的要属 GoogleMap,它简单易用,包含丰富的内容,并且具有良好的用户体验。与此同时,地图应用也普及到桌面应用上,人们试图基于桌面地图开发新应用,用的比较普遍的是 GIS(Geographic Information System),然而 GIS 对比 Google Map的简单易用、信息丰富,还是有一定的差距。因此,很多人希望将 Google Map 集成到桌面应用当中,可以让桌面控件与 Google Map进行数据交互。本文将基于 SWT(Standard Widget Toolkit)桌面技术,集成 Google Map,制作桌面版 GoogleMap 的 SWT 控件。
文章首先展示使用开发好的 Google Map 控件的使用实例,让读者对目标程序有个大致的印象,接下来简介了重要的Browser 控件,以及基于 Browser 控件实现 Google Map 控件的实现原理,包括需要解决的问题,给出实现控件的步骤,最后做简单的总结,告诉读者如此设计控件的优点所在。
回页首
为了让读者更好的了解文章的目的,首先展示开发好的 Google Map 控件的使用实例,如图 1。注意,操作 Google Map 控件的都是 Java 代码,刚打开时地图是最原始的 Google 地图。

图 1 中,左半部分为 Google Map 控件部份,任意点击地图,可以在点击处添加标记;右半部分为功能按钮,提供了设置中心点、添加标记、打开提示窗口、绘画直线这些功能。
例子中数据形式是以 ,(逗号)分隔,各个按钮的功能和数据要求如下表。
功能 数据要求
设置中心点 经度 , 纬度
设置标记 经度 , 纬度
弹出信息 经度 , 纬度 , 弹出信息
绘画直线 起始点经度 , 起始点纬度 , 终点经度 , 终点纬度 , 颜色 , 粗细
使用该 Google Map 桌面控件的 Java 代码风格与直接在 HTML 文件中使用 Google Map API编程风格非常相似,目的是让编写过 Google Map Web 应用的读者可以快速上手使用该桌面控件,而不会有陌生感。 清单 1展示了该控件各接口的使用例子,查看代码注释可以了解各个接口的含义。
public void createPartControl(Composite parent) { // 新建 Google Map,与 SWT 控件使用方法相同 final GMap2 map2 = new GMap2(parent, null); // 添加鼠标点击事件监听 map2.addListener(GMap2.CLICK, new IListener() { public Object function(Object[] arguments) { // 获得点击的经度 double lat = ((Double) arguments[0]).doubleValue(); // 获得点击的纬度 double lng = ((Double) arguments[1]).doubleValue(); GLatLng center = new GLatLng(lat, lng); // 移动适配地图的中心点 map2.fromLatLngToDivPixel(center); // 在中心添加标记 map2.addOverlay(new GMarker(center)); return null; } }); // 初始化操作需要放到 IInvoke 接口的 invoke 方法中 map2.invoke(new IInvoke() { public void invoke() { // 设置 Map 的中心点 map2.setCenter(new GLatLng(39.917, 116.397), 14); // 在中心打开消息框,显示 hello world 消息 map2.openInfoWindow(map2.getCenter(), "hello world"); // 在中心添加标记 map2.addOverlay(new GMarker(map2.getCenter())); // 在中心点和(44,120)点之间绘画直线 map2.addOverlay(new GPolyline(map2.getCenter(), new GLatLng(44, 120), "#ff0000", 10)); } }); }
回页首
在继续了解本文介绍的 Google Map 控件之前,需要了解 org.eclipse.swt.browser.Browser控件,org.eclipse.swt.browser.Browser 叫做浏览器控件,它使用系统默认浏览器(Windows 中的IE、Linux 中的 FireFox)作为控件内核,开发者使用 Browser 控件,就可以让程序带有浏览器的功能。Browser类主要提供了如表 2 的常用接口,可以看到 Browser 提供了浏览器一大部分基本功能。
方法名 功能
back() 浏览器后退
forward() 浏览器前进
refresh() 浏览器刷新
stop() 停止加载页面
setText() 设置显示内容
execute(script: String); 执行脚本(该功能在后面被大量使用)
setUrl(url: String) 设置网页地址
回页首
实际上该 Google Map 控件就是一个 Browser 控件打开 Google Map 网页的结果,这个网页中 GoogleMap 占用了整个页面,Google Map 控件提供的各个 Java 接口,实际上是 Java 触发 JavaScript 调用 GoogleMap API 的结果。按照 Google Map 控件的需求,需要解决如下表的几个问题。
Browser 控件执行JavaScript
屏蔽默认的浏览器弹出菜单
传递 SWT 数据到Google Map
传递Google Map 数据到 SWT
屏蔽 F5 刷新
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 打开的网页内执行某段 JS 的功能,是由 Browser触发执行 JS 脚本。这个过程类似于利用 IE 打开某个网页,并且在 IE 的地址栏中输入 JS 内容,如 图2。按回车,就会在当前网页中弹出 hello world 对话框,如图 3。


Browser 执行 JS 的原理也是如此,通过调用 Brower 的 execute 方法,可以执行某段 JS 脚本 , 目标是 Browser 的当前网页。例如可以这样调用 execute 方法:execute(“alert( ‘hello world ’ )”), 以此实现上述的弹出 hello world对话框的功能。事实上执行的脚本可以更加复杂,它可以操作当前网页内部的 DOM 结构,修改当前网页的内容等等。
回页首
在使用 Browser 控件的时候,右击 Browser 的显示区域,会弹出默认浏览器的下拉菜单,如 图 4。

桌面控件的下拉菜单应该是可以自定义的,为了让 Google Map 控件更加贴近 SWT 桌面控件,并且防止用户点击下拉菜单的选项而引起误操作,需要屏蔽默认的 Browser 右键弹出菜单。
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 脚本可以阻止浏览器的当前网页弹出右键菜单。
回页首
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 传递数据的方法。
回页首
最新版本的 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。

主要过程是 JavaScript 调用绑定的 BrowserFunction,传递数据给 Java 端,Java 端得到数据并且处理,返回结果,JavaScript 获得结果并处理。
回页首
了解前面几个部分的技术基础后,就可以逐步设计实现 Google Map 的控件了。下个部分将讲解实例展示部分的各个功能实现。
该 Google Map 控件是基于 Browser 的,控件中实际显示的 Google Map也是一个网页,只是这个网页中,Google Map 占用了整个页面。因此,需要设计一个模板 HTML,该模板网页中使用一个 DIV占据整个页面,并且利用 Google Map API 在此 DIV 上建立地图。Google Map 控件的初始化过程,实际上就是Browser 打开模板 HTML 文件的过程。清单 7 是模板 HTML 文件主要代码。
清单 7. 模板 HTML 文件

使用 Browser 还有一个问题,当用户按 F5 时,Browser打开的网页会执行刷新操作,网页又会重新加载。为了防止用户误操作,解决方法是需要修改 Browser 显示的网页内容,重写document.onkeydown 方法,如 清单 8。也就是说,在 Google Map 控件的开发中,需要修改该控件的模板 HTML文件,在模板文件中添加 清单 8 的方法,那么用户按 F5 就不执行刷新。
function document.onkeydown() { if ( event.keyCode==116) { event.keyCode = 0; event.cancelBubble = true; return false; } }
虽然使用模板 HTML 文件解决了 Google Map 的显示问题,但是使用 Browser 的时候,Browser的父容器被调整高度时,Browser有可能会出现滚动条。这在大部分情况下是用户不希望看到的,从用户的角度看,控件被调整大小,地图也应该自动调整大小。清单 9 为地图自动缩放的代码。
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%,所以宽度已经会自动缩放,而高度是不能按百分比设置的,需要进行如上处理。
定义 GMap2、GLatLng 类 ,GMap2 代表整个地图控件,GLatLng 存放经纬度信息,在 GMap2 类中声明setCenter(GLatLng latlng, int scale) 方法 , 第一个参数是中心点的经纬度,第二个参数是地图的缩放级别。
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 对地图进行操作,才需要等待地图被加载完毕。
public void setCenter(GLatLng latlng, int scale) { browser.execute("map.setCenter(new GLatLng(" + latlng.getLat() + ", " + latlng.getLng() + "), " + scale + ");"); }
定义 GMarker 类,它代表地图的标记,它包含一个 GLatLng 对象属性,代表此标记所在的经纬度。清单 12 使用 GMarker 给地图中心添加标记。清单 13 是 addOverlay 的实现代码。
map2.addOverlay(new GMarker(map2.getCenter()));
public void addOverlay(GMarker gMarker) { browser.execute("map.addOverlay(new GMarker(new GLatLng(" + gMarker.getCenter().getLat() + "," + gMarker.getCenter().getLng() + ")));"); }
GMap2 有一个方法名为 openInfoWindow,用于在某个经纬度上弹出信息框。实现如清单 14
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 表明了添加连线的实现原理。
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端为地图添加事件监听。
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; } }
GMap2 map = … ; new SingleClickFunction(map, “SingleClick”);
GEvent.addListener(map,"click", function(overlay,latlng) { // 调用 BrowserFunction 传递数据给 Java 端 SingleClick (latlng.lat(), latlng.lng()); });
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 桌面编程的人也可以使用 GoogleMap。另外,使用这样的设计,地图信息可以保存到数据库中,也就是在地图的操作过程中,可以进行数据库的交互作业,那么这样就和桌面程序无缝结合了。
模板 HTML 应该用于修改地图的显示样式,比如标记的图片、连线的样式等等可以在模板 HTML 里设置,至于业务逻辑应该放置到 Java 端。
笔者的目的也在于让读者了解 SWT Browser 的新特性,介绍 Java 与 JavaScript 交互的各种方式方法以及技巧。
回页首
本文的目的主要在于让读者了解 SWT Browser 的特性以及 Java 与 JavaScript交互的技巧,如果读者有兴趣的话,可以继续扩展该 Google Map SWT 控件,使得它变得更加 OO(ObjectOriented),更加的易用。由于笔者知识水平有限,如果有错误的地方,欢迎联系我指正。
回页首
描述名字大小下载方法
样例代码 com.ibm.googlemap.rar 32KBHTTP
关于下载方法的信息
参考Enhanced JavaScript Bridge 文章,可以了解 Browser 的特性。
查看“Google 地图 API 参考”,参考 Google Map API,丰富 SWT Google Map 控件。
访问 developerWorksOpen source 专区获得丰富的 how-to 信息、工具和项目更新以及最受欢迎的文章和教程,帮助您用开放源码技术进行开发,并将它们与 IBM 产品结合使用。
_xyz