Swing与SWT是基于Java的两种完全不同的视图控件技术,前者是jdk自带,后者是Eclipse的衍生物,两者都有广大的用户群,而结合使用的案例也很多,笔者使用Swing多年,最近有幸作了个Eclipse RCP与Swing结合的例子,期间发现了些难点,特作本文了以皮毛之见。
相关概念
Swing
Swing为JDK自带,是正统的java界面控件,历史悠久,设计优雅,是java开发界面组件的第一选择,但Swing组件更新缓慢,几年来都没有太大变化,加上官方NetBeans臃肿缓慢的表现,让依赖本地库、界面native的SWT得以发展壮大。SWT
SWT是eclipse项目的衍生物,全称是“Standard Widget Toolkit”,SWT需要依赖本地库,界面风格与本地程序协调一致,借助eclipse的优秀表现,SWT给人以运行流畅,界面漂亮的印象,赢得了不少用户的青睐。Eclipse RCP
Eclipse RCP是一个用于创建和部署富客户端应用的平台,它包含Equinox(一个基于OSGi标准的组件框架),用于构建丰富的具有本地GUI的桌面应用程序,并提供通过服务端升级桌面程序的完整体系。如何创建Eclipse RCP项目
安装插件
首先我们需要安装Eclipse RCP Plug-in Developer Resources插件,当然你也可以使用Eclipse IDE for Java EE Developer,这个版本的Eclipse包含的插件比较全创建一个Plug-in Project,添加两个View到透视图
新建一个Plug-in Project,向导中,Rich Client Application选项选择yes,后面有模版可供选择,不熟悉Eclipse RCP的可以选个模版练手。Eclipse RCP中每个ViewPart表示一个可插拔的视图,而Perspective类的作用是对View作布局,这里我们创建两个view:NavigationView,View,左边导航树(借用了RCP自带的mail模版),右边主面板用来嵌入Swing组件,初步为如下效果:如何在Eclipse RCP中嵌入Swing组件
Eclipse RCP详细使用说明可以参考文档:,本文的主要目的是介绍Swing组件的嵌入,下面我们在右边的View中加入一个Swing面板。
使用SWT_AWT桥实现Swing组件的嵌入,使用SWT_AWT.new_Frame(composite)实现SWT组件中嵌入AWT的Frame对象,而AWT的Frame中就可以添加Swing组件了,参考下面的代码
public class View extends ViewPart { public static final String ID = "HelloRCP.view"; private JPanel swingPanel; public void createPartControl(Composite parent) { Composite composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND); final Frame frame = SWT_AWT.new_Frame(composite); SwingUtilities.invokeLater(new Runnable() { public void run() { swingPanel = new JPanel(); JButton button = new JButton("Swing Button"); swingPanel.add(button); frame.add(swingPanel); } }); } public void setFocus() {}}
运行效果:
Eclipse UI线程与Swing事件派发线程
上面的代码中我们留意到,创建Swing的JPanel我们是在Swing的事件派发线程(EDT)中调用的,EDT的作用是保证Swing线程的安全,任何Swing界面相关的操作都需要在EDT中执行,与Swing的这一设计类似,SWT也有一个UI线程,任何SWT界面相关的操作都必需在这个UI线程中调用,下面我们对按钮添加点击监听,实现视图名称的修改,注意代码需要在getDisplay().asyncExec中调用
public void createPartControl(Composite parent) { Composite composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND); final Frame frame = SWT_AWT.new_Frame(composite); SwingUtilities.invokeLater(new Runnable() { public void run() { swingPanel = new JPanel(); swingPanel.setDoubleBuffered(true); JButton button = new JButton("Click and change view name"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { getSite().getShell().getDisplay().asyncExec(new Runnable() { public void run() { View.this.setPartName("New Swing View"); } }); } }); swingPanel.add(button); frame.add(swingPanel); } });}
嵌入TWaver Demo
嵌入了Swing组件,可惜不够好看,直接把TWaver Demo放进来如何,于是拷贝TWaver Demo的源码,引用twaver.jar到build path,在createPartControl(Composite parent)中创建一个demo.MainPane,放到右边的面板中:
public void createPartControl(Composite parent) { Composite composite = new Composite(parent, SWT.EMBEDDED | SWT.NO_BACKGROUND); final Frame frame = SWT_AWT.new_Frame(composite); SwingUtilities.invokeLater(new Runnable() { public void run() { Demo.init(); MainPane mainPanel = new MainPane(); mainPanel.setDoubleBuffered(true); frame.add(mainPanel); ... });}
Eclipse RCP工程中如何引入第三方jar
这里还有件重要的事要做,Eclipse RCP中使用第三方jar需要在Runtime Classpath中引入这个jar,可以按下图中的设置
Eclipse View之间如何交互
Eclipse RCP框架中,每个view都是独立的,在一个view中没法直接得到另一个view的实例,要实现view之间的交互需要一些特殊手段,如通过selection监听器监听其他视图的selection事件,详细使用可参考。
本例中实现的是点击左边SWT的导航树,同步选中右边TWaver的对应的树节点,要实现这样的功能,要求右边的视图能监听左边视图的变化事件,首先我们在NavigationView中注册导航树为事件提供者:
注意下面的这行代码:this.getSite().setSelectionProvider(treeViewer);其作用是向系统提供一个消息源,这样当这个树选中变化时,别的视图就可以监听到这些事件public void createPartControl(Composite parent) { treeViewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); ... this.getSite().setSelectionProvider(treeViewer); ... }
然后在View视图中监听上面的消息源
public void createPartControl(Composite parent) { ... this.getSite().getPage().addSelectionListener(NavigationView.ID, new ISelectionListener() { public void selectionChanged(IWorkbenchPart arg0, ISelection arg1) { ... } }); ...}
通过以上两步就实现了视图View监听视图NavigationView中变化事件的功能,同理,如果需要在NavigationView视图中监听View视图中的事件,则需要注册View为事件源,然后在NavigationView中监听该事件,有了这种消息传递机制,我们就可以实现各个视图间的通信了,而具体同步的代码我这里就不多说了。
补充一个小技术点
Windows操作系统下,SWT中的Swing组件可能出现闪烁的问题,可以通过调用下面的函数,设置为true,以避免该问题
JComponent#public void setDoubleBuffered(boolean aFlag)
最终运行结果