引自http://blog.csdn.net/firefight/archive/2007/02/06/1503103.aspx
使用Batik创建SVG应用程序
Batik工具集提供的JSVGCanvas模块是一个swing 组件,用于显示静态或动态SVG文档。通过JSVGCanvas模块,开发人员可以轻松显示SVG文档(通过URI地址或DOM树)并对其进行操作,例如旋转、缩放、摇动、选择文本或激活超级链接等。首先介绍如何创建JSVGCanvas并集成到一个swing应用程序中。接下来解释如何完成与SVG画布相关的常用功能,例如如何跟踪SVG文档渲染时发生的所有事件,以及如何通过JavaTM语言操作SVG文档。
创建JSVGCanvas
JSVGCanvas是一个swing 组件并遵循swing设计规则(Swing design rule[4])。这意味着组件不是线程安全的,而且所有操作应当参照swing教程描述使用。JSVGCanvas 也是一个JavaBean 组件,因此可以在可视化应用程序开发工具中使用。下例中演示了如何轻松创建和使用JSVGCanvas 组件(见图3)。
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.gvt.GVTTreeRendererAdapter;
import org.apache.batik.swing.gvt.GVTTreeRendererEvent;
import org.apache.batik.swing.svg.SVGDocumentLoaderAdapter;
import org.apache.batik.swing.svg.SVGDocumentLoaderEvent;
import org.apache.batik.swing.svg.GVTTreeBuilderAdapter;
import org.apache.batik.swing.svg.GVTTreeBuilderEvent;
public class SVGApplication {
public static void main(String[] args) {
JFrame f = new JFrame("Batik");
SVGApplication app = new SVGApplication(f);
f.getContentPane().add(app.createComponents());
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.setSize(400, 400);
f.setVisible(true);
}
JFrame frame;
JButton button = new JButton("Load...");
JLabel label = new JLabel();
JSVGCanvas svgCanvas = new JSVGCanvas();
public SVGApplication(JFrame f) {
frame = f;
}
public JComponent createComponents() {
final JPanel panel = new JPanel(new BorderLayout());
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
p.add(button);
p.add(label);
panel.add(p, BorderLayout.NORTH);
panel.add(svgCanvas, BorderLayout.CENTER);
// Set the button action.
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JFileChooser fc = new JFileChooser(".");
int choice = fc.showOpenDialog(panel);
if (choice == JFileChooser.APPROVE_OPTION) {
File f = fc.getSelectedFile();
try {
svgCanvas.setURI(f.toURL().toString());
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
});
// Set the JSVGCanvas listeners.
svgCanvas.addSVGDocumentLoaderListener(new SVGDocumentLoaderAdapter() {
public void documentLoadingStarted(SVGDocumentLoaderEvent e) {
label.setText("Document Loading...");
}
public void documentLoadingCompleted(SVGDocumentLoaderEvent e) {
label.setText("Document Loaded.");
}
});
svgCanvas.addGVTTreeBuilderListener(new GVTTreeBuilderAdapter() {
public void gvtBuildStarted(GVTTreeBuilderEvent e) {
label.setText("Build Started...");
}
public void gvtBuildCompleted(GVTTreeBuilderEvent e) {
label.setText("Build Done.");
frame.pack();
}
});
svgCanvas.addGVTTreeRendererListener(new GVTTreeRendererAdapter() {
public void gvtRenderingPrepare(GVTTreeRendererEvent e) {
label.setText("Rendering Started...");
}
public void gvtRenderingCompleted(GVTTreeRendererEvent e) {
label.setText("");
}
});
return panel;
}
}
图 3: 运行中的SVG应用程序
事件处理机制
如上例所示,每当设置JSVGCanvas 的URI或SVG DOM时(通过setURI或setSVGDocument方法),相关文档首先被解析(在URI的情况),然后创建、渲染和有选择的更新。为了接收这些不同的阶段的事件通知,正确的方法是实现一个侦听器并附加到该组件。有五种类型的侦听器:
- SVGDocumentLoaderListener – 提供了用于跟踪SVGDocumentLoaderEvent 事件的一组方法。它描述了SVG装载阶段,即使用SVG文件创建SVG DOM树。
- GVTTreeBuilderListener –提供了用于跟踪GVTTreeBuilderEvent事件的一组方法。它描述了创建阶段,即通过SVG DOM树创建一个GVT(图形矢量工具集),然后GVT树被用于渲染文档。
- SVGLoadEventDispatcherListener –提供了用于跟踪SVGLoadEventDispatcherEvent事件的一组方法。它描述了DOM 'SVGLoad'的事件派发阶段。该事件仅在动态类型文档中触发。
- GVTTreeRendererListener –提供了用于跟踪GVTTreeRendererEvent事件的一组方法。它描述了渲染阶段,即使用一个GVT树创建图像。在动态文档中该事件只在最初渲染时被触发一次。
- UpdateManagerListener –提供了用于跟踪UpdateManagerEvent事件的一组方法。它描述了运行阶段,即显示更新管理器启动,然后显示线程可能被挂起、恢复或停止。该侦听器可用于跟踪图像更新情况。只有动态文档触发该事件。
使用JavaTM脚本
Batik工具集提供了简便的,基于JavaTM语言的SVG文档脚本。在前一节中,我们学习了如何显示一个SVG文档;本节描述如何操作当前在JSVGCanvas中显示的SVG文档。下面的例子中演示了如何操作SVG文档。注意开发人员不需要考虑图像的更新问题,在事件侦听器激活后画布根据需要进行自动更新。
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import org.apache.batik.swing.JSVGCanvas;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherAdapter;
import org.apache.batik.swing.svg.SVGLoadEventDispatcherEvent;
import org.apache.batik.script.Window;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.events.EventTarget;
public class SVGApplication {
public static void main(String[] args) {
new SVGApplication();
}
JFrame frame;
JSVGCanvas canvas;
Document document;
Window window;
public SVGApplication() {
frame = new JFrame();
canvas = new JSVGCanvas();
// Forces the canvas to always be dynamic even if the current
// document does not contain scripting or animation.
canvas.setDocumentState(JSVGCanvas.ALWAYS_DYNAMIC);
canvas.addSVGLoadEventDispatcherListener
(new SVGLoadEventDispatcherAdapter() {
public void svgLoadEventDispatchStarted
(SVGLoadEventDispatcherEvent e) {
// At this time the document is available...
document = canvas.getSVGDocument();
// ...and the window object too.
window = canvas.getUpdateManager().
getScriptingEnvironment().createWindow();
// Registers the listeners on the document
// just before the SVGLoad event is dispatched.
registerListeners();
// It is time to pack the frame.
frame.pack();
}
});
frame.addWindowListener(new WindowAdapter() {
public void windowOpened(WindowEvent e) {
// The canvas is ready to load the base document
// now, from the AWT thread.
canvas.setURI("doc.svg");
}
});
frame.getContentPane().add(canvas);
frame.setSize(800, 600);
frame.show();
}
public void registerListeners() {
// Gets an element from the loaded document.
Element elt = document.getElementById("elt-id");
EventTarget t = (EventTarget)elt;
// Adds a 'onload' listener
t.addEventListener("SVGLoad", new OnLoadAction(), false);
// Adds a 'onclick' listener
t.addEventListener("click", new OnClickAction(), false);
}
public class OnLoadAction implements EventListener {
public void handleEvent(Event evt) {
// Make some actions here...
// ... for example start an animation loop:
window.setInterval(new Animation(), 50);
}
}
public class OnClickAction implements EventListener {
public void handleEvent(Event evt) {
// Make some actions here...
// ... for example schedule an action for later:
window.setTimeout(new DelayedTask(), 500);
}
}
public class Animation implements Runnable {
public void run() {
// Insert animation code here...
}
}
public class DelayedTask implements Runnable {
public void run() {
// Make some actions here...
// ... for example displays an alert dialog:
window.alert("Delayed Action invoked!");
}
}
}
在SVG文档中注册的DOM侦听器在画布更新线程中被激活。为了避免冲突的情况,开发人员不应该在另一个线程中操作DOM树,而应当切换到画布更新线程进行更新操作。从外部线程切换到画布更新线程的方法如下:
// Returns immediately
canvas.getUpdateManager().getUpdateRunnableQueue().
invokeLater(new Runnable() {
// Insert some actions on the DOM here
});
// Waits until the Runnable is invoked
canvas.getUpdateManager().getUpdateRunnableQueue().
invokeAndWait(new Runnable() {
// Insert some actions on the DOM here
});
与常规的事件侦听器相似,当Runnable从更新线程激活时,图形被更新。
Batik同时提供了SVG<script>元素的扩展,以便在SVG文档中执行Java 程序。所有使用bridge模块的Batik应用程序都可以使用该功能(例如JSVGCanvas和 ImageTranscoder模块)。为了使用该扩展,<script>元素中的'type'属性必须设置为application/java-archive。另外,xlink:href属性应设置为执行程序所在的jar文档URI地址。该jar文件的表述文件(manifest)必须包括下表中的入口项:
Script-Handler: <classname>
<classname>必须是实现org.apache.batik.script.ScriptHandler接口的类的名称。该类可以直接放在jar文件中,也可以位于表述文件Class-Path入口项所包含的其它jar文件中。
总结
在本文中,我们了解到开发人员如何使用Batik工具集创建、操作和显示SVG内容。Batik的模块具有良好的扩展性而且易于使用,通过本文的学习,JavaTM开发人员现在可以着手编写客户端或服务器端的SVG应用程序。另外,Batik项目是Apache 软件基金会 (ASF)倡导的开源志愿项目。这就意味着有很多方式对该项目贡献自己的力量,包括直接参与(编码、写文档、回答问题、提供想法、报告错误、错误修改建议等等),或资源捐献(公开代码、硬件、软件、出席会议、演讲等)。本项目特别关注使用Batik模块的应用程序,包括各种工具和扩展,因此请积极的通过Batik邮件列表为本项目作出贡献,邮件列表为batik-users@xml.apache.org。
参考资料
[1] "The official SVG page at W3C",
SVG工作组,网址 http://www.w3.org/Graphics/svg.
[2] "The Document Object Model",
DOM工作组,网址 http://www.w3.org/DOM.
[3] "The Batik SVG Generator Tutorial",
Batik小组,网址http://xml.apache.org/batik/svggen.html.
http://www.svgopen.org/2002/papers/kormann__developing_svg_apps_with_batik/