京山游侠

专注技术,拒绝扯淡
posts - 50, comments - 868, trackbacks - 0, articles - 0
  BlogJava :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理
上一篇
Eclipse RCP详解(04):Eclipse RCP相关的学习资料及国内相关图书点评

  前面几篇都是使用Ubuntu系统及其自带的Eclipse 3.8.1。在这一篇里,我将为大家展示Fedora 20和其自带的Eclipse 4.3.1。同时,为了方便我随时切换操作系统(Ubuntu、Fedora、Win7),我在GitHub上注册了一个账号,并把示例代码放了上去。我切换系统后,只需要Fetch一下,就可以继续开发。当然,有兴趣看我代码的朋友,也可以clone一份到自己的机器上。地址:
https://github.com/littleloach/examples.git
  关于Eclipse和Git的整合,请看这里:浅论Maven和Git的原理及展示其与Eclipse的集成
  事实证明,Eclipse RCP不仅是跨平台的,而且是跨版本的。也就是说,即使我在Fedora中使用了不一样的Eclipse版本,我在Ubuntu中写的代码到Fedora中也不需要修改。(我在Win7中用的Eclipse版本和Ubuntu中的相同。)

  Fedora 20和Fedora 19都是我喜欢的版本。因为它们的开发代号分别为Heisenberg和薛定谔的猫,不难看出,它们都和量子力学有关,对我这样的理工男吸引力那肯定是巨大的。而Heisenberg更具有传奇性。不仅因为他是量子力学的主要创始人和诺贝尔奖获得者,更因为历史上到现在都没有定论的海森堡谜题:为什么当初他没有造出原子弹,是他真的搞错了,还是他故意搞错了?故事是这样的:第二次世界大战开始后,迫于纳粹德国的威胁,丹麦的大物理学家玻尔离开了心爱的哥本哈根理论物理研究所,离开了朝夕相处的来自世界各地的同事,远赴美国。德国的许多科学家也纷纷背井离乡,坚决不与纳粹势力妥协。然而,有一位同样优秀的物理学家却留下来了,并被纳粹德国委以重任,负责领导研制原子弹的技术工作,远在异乡的玻尔愤怒了,他与这位过去的同事产生了尖锐的矛盾,并与他形成了终生未能化解的隔阂。有趣的是,这位一直未能被玻尔谅解的科学家却在1970年获得了“玻尔国际奖章”,而这一奖章是用以表彰“在原子能和平利用方面做出了巨大贡献的科学家或工程师”的。历史在此开了个巨大的玩笑,这玩笑的主人公就像他发现的“测不准原理”一样,一直让人感到困惑和不解。他就是量子力学的创始人——海森堡。也许他当初就是故意留下来而且故意造不出原子弹的吧。

  在正式介绍JFace和结构化数据之前,先向大家展示几张Fedora的截图。
  第一张,Fedora刚安装完成后,其Gnome桌面是很丑的:



  所以需要进行一些Art Work,比如多安装几个wallpaper啦,多安装几个theme啦,然后安装一个gnome-tweak-tool更改一下字体、字体微调和抗锯齿啦。Fedora 19和20还有一个地方比较令人蛋疼,那就是它提供的gnome-terminal是不支持半透明背景的,如果想要半透明背景,只有自己编译gnome-terminal。我太懒,所以只好把KDE中的konsole借来用用了。好在KDE程序在gnome中也可以正常运行。下面是经过我折腾后的Fedora 20:



  有人说,GTK+程序的列表两行之间的宽度大到可以塞进一头大象。以前在Ubuntu下用Eclipse不觉得,换到Fedora中发现这个宽度确实是挺大的,看着很不舒服。不过这都不是个事,换个GTK+的theme一切都搞定。下面一张是Eclipse的截图,还是很漂亮的说:


  下面开始今天的正题。

结构化数据
  结构化数据可不是我一拍脑袋想出来的术语。平时我们在组织和保存数据的时候,离不开数据结构。我们经常使用的数组、链表或多维数组来保存数据,也经常使用数据库的表格保存数据,但是我们并不是时时刻刻想着结构化这么一个概念。但是在Eclipse中就不一样了,结构化数据是它的一个很重要的理念,只要有可能,它都会把数据组织成结构化的。比如,如果你在Eclipse中选择了一些什么,那么这些选择的东西就是以IStructuredSelection展示出来的。如下图,可见Structured无处不在:
    

  数组(包括链表)、树和表格(包括多维数组)是我们常用的数据结构。List控件、Tree控件和Table控件是我们常用的展示数据的控件。然而逻辑和形式并不总是一一对应的。比如形式上可以用Tree控件表现的数据,逻辑上并不一定是用树结构组织起来的数据。想想我们做数据库的ORM映射,如果一个表中的一行数据和另一个表中的多行数据相关联(一对多映射),写成Java Entity类的时候就是一个Entity中包含有一个另一个Entity的List或Set。这两个Entity各有各的Class Name,而不是统一叫TreeNode。这两个Entity之间也不用getParent和getChildren互相访问。但是从形式上,依然可以用一个Tree控件来展示它们,不是吗?

JFace中的Viewer
  在该系列博文的第02篇中,我展示了SWT的List控件和Tree控件。这两个控件的使用是很简单的,List控件只需要使用add()方法就可以在列表中添加一个字符串,Tree控件需要先构造一个TreeItem对象,然后用setText()方法添加一个字符串。控件只能用来显示字符串和字符串前面的一个小图标,不能保存复杂的数据。MVC是常用的设计模式,Model用来保存数据,而控件只是负责显示,所以中间一定要有一个机制将Model中的数据转换为能让控件显示的字符串和图标,并且要能维持数据在结构化上的逻辑联系。
  JFace中的Viewer是对SWT中的控件的封装,它使用的正是MVC模式。如果不了解Viewer的哲学,使用Viewer就无从下手。Viewer的哲学是这样的:Viewer对它要显示的数据是怎么组织的没有任何要求,可以使用它的setInput()方法将任何一个对象作为它的Model;很显然Viewer不可能智能到显示任何结构的数据,所以必须给它提供一个ContentProvider,ContentProvider就是负责将任意类型的Model转化为结构化的数据;Model经过ContentProvider转化为结构化数据后,Viewer再对该结构中的每一项进行显示,前面提到过控件只能显示文字和图片,所以还需要一个LabelProvider来将数据转化成要显示的文字和图片。
  由上面的描述可以看出,要使用Viewer必须最少编写三样东西:一些Model、一个ContentProvider和一个LabelProvider。当然,Viewer还有更多的功能,比如对数据进行排序和过滤,只需要提供相应的SortProvider和FilterProvider即可,但这两个Provider不是必须的。虽然要写的类比较多,但是JFace都定义好了Interface,我们只需要实现即可,在IDE的帮助下,这个工作一点也不难。
  
一个简单的示例
  文字内容写得再多,也不如一个例子来得直接。我这里模拟HIS(Hospital Information System)系统中一个简单的功能来展示Viewer的用法。在医院里,一个住院部(Inpatient Department)可以有很多个科室(Department),比如内科、外科、五官科等,一个科室里面可以有很多个病人(Patient)。很显然,这些数据用一个TreeViewer显示是再合适不过了。先看看这个示例程序界面的总体框架,如下图:


  这只是一个框架,结合上面的界面,我简要说说我要实现的功能:
  1、最左边的视图使用了一个TreeViewer,用来显示住院部的所有科室和病人;
  2、工具栏有两个按钮,如果最左边的视图中被选择的对象是科室,则带加号的按钮可用,其功能是为该科室增加一个病人;如果最左边的视图中被选择的对象是病人,则带减号的按钮可用,其功能是删除这个病人;
  3、中间的视图使用一个TableViewer控件,当最左边的视图中被选择的对象是科室时,则以表格的形式显示该科室所有病人的详细信息,每一行代表一个病人;
  4、最右边的视图使用一个ListViewer控件,仅仅显示当前选择的内容。

为示例程序准备数据
  该示例程序用到的数据是一个住院部有多个科室、一个科室有多个病人,很显然这是一个典型的一对多的关系。所以我们的Model需要三个类:Inpatient、Department、Patient。其关系如下:

  其中的Gender是一个枚举。下面是它们的代码:
Gender.java

Inpatient.java

Department.java

Patient.java

  然后,创建一个PatientView类,在里面使用一个TreeViewer控件,创建一个Inpatient类的实例,然后添加一点点测试数据,然后把Inpatient的实例作为viewer的input即可。如下图:


  当然,如果没有设置好ContentProvider和LabelProvider,数据是不可能正常显示的。所以我们还需要创建一个PatientContentProvider类和PatientLabelProvider类。好在JFace中有早就定义好的接口,我们只需要从这些接口实现即可,如下图:
  

  我们先来看PatientContentProvider需要实现哪些方法,如下图,可以看出IDE已经自动把框架搭建起来了,我们只需要填空即可(下图中的空是我已经填好了的):


  第一个要实现的方法是getElements,该方法要求我们把Input的数据转化成一个Object数组返回。在这个例子中,Input进来的是Inpatient类的对象,所以只需要先将inputElement转型为Inpatient,然后返回它里面的Department数组即可。第二个要实现的方法是hasChildren,在本例中,如果element是Department,则它就有children,如果是Patient就没有children。最后要实现的一对方法就是getChildren和getParent,很显然,如果element是Department,则它的children是它里面保存的List<Patient>,不过返回的时候要把它转化成数组,如果element是Patient,则它的parent是它的department。So easy!不是吗?
  而PatientLabelProvider的实现就更简单了,如下图:
 
  看图填空,我们只需要完成getText方法和getImage方法即可。逻辑也很简单,就是判断element是Department还是Patient,然后分别返回相应的字符串和图片即可。在这里,我还故意把不同的性别显示为不同的图标。有了截图,我就不贴代码了。

  俗话说一通百通,使用不同的Model和Viewer,就给它不同的ContentProvider和LabelProvider即可。

处理Selection事件
  关于数据的展示,我们轻松搞定。不过这还不算完,还有工作要做。现在数据显示出来了,我们就可以用鼠标来点击,来选择。那么我们选择的数据怎么样来影响我们程序的行为呢?在前面几篇对GUI的探讨中我说过,程序和用户的交互是通过事件处理来进行的。也就是说,当我们选择了Viewer中的一项或多项的时候,Viewer应该发送一个Selection事件,我们向程序中注册一个SelectionListener就应该可以捕获到这个事件。
   为了让patientViewer向程序发送Selection事件,我们需要让它成为程序的selectionProvider。通过在PatientView中添加如下一行代码(最后一行):

  有了发送Selection事件的源,接着应该有接受Selection事件的Listener。先从最简单的功能做起。最右边的视图只是用来显示所选择的对象,所以从它先开始。最右边的视图类是SelectionView,里面用了一个ListViewer,在这里,我们先让SelectionView实现ISelectionListener接口,然后把它注册到Workbench中。如下图,关键代码我把它标出来了:


  selectionChanged()方法将在有目标被选中的时候调用,该方法的实现也很简单,就是把selection作为ListViewer的input,让它显示即可。而要使用ListViewer,我们又得为它提供一个ContentProvider和一个LabelProvider。下面是ContentProvider和LabelProvider的实现,也很简单,这次ContentProvider实现了IStructuredContentProvider接口,而且从代码中可以看出,selection是一个IStructuredSelection,哪怕我们只选中一项,它也是Structured的。




做好之后,运行程序是这样的效果:
 

  然后再来实现中间那个视图DepartmentDetailView的功能。在这个视图中使用了一个TableViewer。使用TableViewer比使用ListViewer和TreeViewer要稍微复杂一点,因为需要我们自己添加表格的列,并设置表格的属性。和SelectionView一样,DepartmentDetailView实现ISelectionListener,并把自己注册到Workbench中。代码如下:


  TableViewer依然需要一个ContentProvider和一个LabelProvider,不过没有ITableContentProvider,因为TableViewer是把每一行当成一个element,所以它只需要和ListViewer一样的ContentProvider即可。但是TableViewer需要的LabelProvider不一样,要实现ITableLabelProvider,在这里,TableViewer才处理不同的列。如下图:




  完成后,运行程序是这样的效果:


根据用户的选择决定工具栏按钮是否可用
  终于进入今天的最后一个议题了。 在前面的几篇中,我陆续用过主菜单、视图工具栏,今天用到了主工具栏。菜单和工具栏是GUI程序产生命令的主要方式,还有就是快捷键和弹出菜单。在Eclipse中,它们的本质都一样,都是通过关联到一个command和一个handler来实现其功能,通过一个menuContribution来指定它们的位置。在今天的示例中,我把menuContribution的locationURI设置为toolbar:org.eclipse.ui.main.toolbar,所以这两个按钮就出现在了主工具栏中。如下图:


  在今天的示例中加入工具栏按钮可不是为了将工具栏。今天的主题是结构化数据。StructuredSelection也是结构化的数据。所以在这个示例中加入工具栏是为了展示用户选择的数据会对工具栏按钮的行为产生影响。从功能上讲,我们希望当用户选择一个科室的时候,添加病人的按钮可用,而选择多个科室或选择一个或多个病人的时候,添加病人工具栏不可用,希望当用户选择一个病人的时候,删除病人按钮可用,而当选择科室或多个病人的时候,删除按钮不可用。
  这样的需求在GUI编程中可以算是无处不在。Eclipse RCP是如何处理这个问题的呢?答案就是Workbench Core Expressions。下图是Eclipse帮助文档中关于Workbench Core Expressions的页面:


  然后,我们只需要对工具栏的按钮添加如下的Workbench Core Expressions即可:


  收工,今天就写到这里。这篇文章已经够长了,截图也有20多张了,所以实现那两个按钮的Handler我就不写了。理解了结构化数据和理解了IStructuredSelection后,理解Workbench Core Expression就很容易了,更详细的内容大家自己看帮助文档吧。

  写到这里,Eclipse RCP的大部分UI元素如窗口、菜单、工具栏、视图等等什么的都提到了,还有一个很重要的大件的UI building block就是编辑器了。等下次有时间,我再来详细讨论编辑器的编写。

评论

# re: Eclipse RCP详解(05):JFace和结构化数据  回复  更多评论   

2014-04-19 10:01 by bimbashi
期待博主更新,另外能否说一下windowbuilder,jfreechart等在RCP里面怎么用,他们与e4 tools的关系等。

# re: Eclipse RCP详解(05):JFace和结构化数据  回复  更多评论   

2014-12-21 01:32 by 当当爸
看到博主在博客园的Linux系列了,想留言需要注册就作罢,希望博主继续Eclipse RCP系列啊

# re: Eclipse RCP详解(05):JFace和结构化数据  回复  更多评论   

2014-12-21 20:00 by 当当爸
设置visibleWhen后,RCP启动后工具栏显示不出来,是否应该在handler处设置enableWhen,我这样设置后可以

# re: Eclipse RCP详解(05):JFace和结构化数据  回复  更多评论   

2014-12-21 20:16 by 当当爸
另外,想问一下博主,你的图片都是自己画的吗?用什么工具画的?

# re: Eclipse RCP详解(05):JFace和结构化数据[未登录]  回复  更多评论   

2014-12-22 21:35 by 京山游侠
@当当爸
是要继续Eclipse RCP系列的,中途因为准备硕士论文和参加答辩,然后从5月份又开始写Linux系列所以就停了。
至于画图,我用的Dia。在我的Linux系列中,有一篇就是专门介绍绘图软件的。

# re: Eclipse RCP详解(05):JFace和结构化数据  回复  更多评论   

2015-01-09 09:13 by 家电维修
博主的几篇Eclipse RCP系列文章都很精彩,继续支持博主。

只有注册用户登录后才能发表评论。


网站导航: