GEF -- 学习一下调色板,我们不知道的东西(一)矩阵

应该说GEF最最吸引我的东西就是它的调色板,因为在没有接触GEF之前一直想自己写一个UML绘图软件,这个想法一直存在,但是苦于水平较洼,一直没有实现。虽然我也能用Canvs绘图,移动图片等等,但是我自忖我还没有那个能力。你知道承认自己能力的不足是多么的痛苦,但是我不得不承认,真的不行。直到有一天公司需要我们做一个工作流的程序,而其中一个重要的组成部分就是工作流的定义软件。当然,这种软件多的是,但是大多是不符合特定需求的,更何况那些软件都不是免费的午餐,我们没有必要花那些冤枉钱,所以我就上网找了找,于是我的GEF生涯开始了。
我的GEF开始于小日本的教程,不服不行,Eclipse把它作为了一个官方的链接,这是说明问题的。GEF入门的几乎所有知识都涵盖了,而且非常清楚,作为一个门外汉来说,我应该谢谢小日本。但是,随着我的学习的深入,我发现我的GEF程序和例子里的总是有那么一点不同,那就是调色板,为什么我的调色板是这样的,而例子里的调色板是那样的呢?我什么地方做的不对么?后来,我把相同的例子在不同版本的GEF下运行后发现,哈哈,原来是版本问题。
(左面是经典调色板界面)  (右面是现在的调色板界面)

GEF大约是在3.2之后将调色板实现的代码做了修改,所以现在的UI显示是这样的,我十分想说:蠢驴!为啥放着好好的经典界面不要了,偏要弄成这个样子。(我是一个守旧派!)当然,我只是私下想想,毕竟我是站在一个巨人的肩膀上。这让我想起了米老鼠和唐老鸭中的那个“三颗神豆”的故事,我可不想让巨人怒了。但是既然我更喜欢以前的调色板,为啥我不自己实现一个呢?So,let’t go!

但是在我们自己实现之前,我们必须知道调色板的工作原理,所以我们需要做的第一件事就是开始我们的源码之旅,而我自己随着单步跟踪的不断进行,我惊奇的发现我所不知道东西,而这些东西就是我今天要拿出来和大家分享的。如果有那位兄弟姐妹已经知道了这其中的秘密,请挥一挥衣袖,不带走一片云彩,如果不知道,请和我一起上路。

说到调色板,我们都知道在GEF中有两种调色板,一种是不能浮动的,一种是能浮动的;这两种调色板说白了就是两个EditorPart的子类,也就是说,如果我们想要实现调色板功能必须让我们的Editor继承这两个调色板类,GraphicalEditorWithFlyoutPalette或者GraphicalEditorWithPalette:
调色板类
今天我想讨论的就是能够浮动的那种,也就是GraphicalEditorWithFlyoutPalette;

那么好,既然GraphicalEditorWithFlyoutPalette是一个EditorPart,那么只要开发过RCP的人用脚后跟都知道,它必须实现createPartControl这个方法,也就是说它必须用一大堆控件来武装这个Editor,就像我们一般情况下使用View一样,否则的话,就是白板一个,什么也没有,更不要提调色板了;那么事实上是不是这样的呢?还是让我们来看看吧:

1 public void createPartControl(Composite parent) {
2     splitter = new FlyoutPaletteComposite(parent, SWT.NONE, getSite().getPage(), getPaletteViewerProvider(), getPalettePreferences());
3     super.createPartControl(splitter);
4     splitter.setGraphicalControl(getGraphicalControl());
5     if (page != null) {
6         splitter.setExternalViewer(page.getPaletteViewer());
7         page = null;
8     }
9 }

喔,的确如此,我的想法没有错。那么我们可以确定的一点是,秘密一定隐藏在那个叫splitter的FlyoutPaletteComposite类对象中了;那么这个类又是什么样子呢?
FlyoutPaletteComposite
啊,原来不过是一个Composite呀,和我们用的SWT的Button原来都是一家的,那就好办了,至少我们还是对Composite有一些了解的,这个类说出大天也不过就是一个自定义的Composite控件,不论他有多么复杂,也不过就是一个Composite;
那既然他是一个Composite,总是需要构造函数的吧,那构造函数里又有什么呢?

 1 public FlyoutPaletteComposite(Composite parent, int style, IWorkbenchPage page, PaletteViewerProvider pvProvider, FlyoutPreferences preferences) {
 2     super(parent, style & SWT.BORDER);
 3     provider = pvProvider;
 4     prefs = preferences;
 5     sash = createSash();
 6     paletteContainer = createPaletteContainer();
 7     hookIntoWorkbench(page.getWorkbenchWindow());
 8 
 9     // Initialize the state properly
10     if (prefs.getPaletteWidth() <= 0) {
11         prefs.setPaletteWidth(DEFAULT_PALETTE_SIZE);
12     }
13     setPaletteWidth(prefs.getPaletteWidth());
14     setDockLocation(prefs.getDockLocation());
15     updateState(page);
16 
17     addListener(SWT.Resize, new Listener() {
18         public void handleEvent(Event event) {
19             Rectangle area = getClientArea();
20             if (area.width > minWidth)
21                 layout(true);
22         }
23     });
24 
25     listeners.addPropertyChangeListener(new PropertyChangeListener() {
26         public void propertyChange(PropertyChangeEvent evt) {
27             String property = evt.getPropertyName();
28             if (property.equals(PROPERTY_PALETTE_WIDTH))
29             prefs.setPaletteWidth(paletteWidth);
30             else if (property.equals(PROPERTY_DOCK_LOCATION))
31                 prefs.setDockLocation(dock);
32             else if (property.equals(PROPERTY_STATE))
33                 if (paletteState == STATE_COLLAPSED || paletteState == STATE_PINNED_OPEN)
34                     prefs.setPaletteState(paletteState);
35         }
36     });
37 }

构造函数可是够长的,但是现在还不是我们一句一句讲的时候,我们现在需要关心的是那两句标出来的语句,由这两句话我们可以知道在这个Composite中,又创建了两个东西,一个是Sash,一个是PaletteContainer,那么这两个对象又是什么呢?尤其是那个Sash,你们说面熟不面熟,SWT中不就有Sash么?可是别忙,让我们再好好看看:
FlyoutPaletteComposite子类 
以上是FlyoutPaletteComposite类结构的部分节选,哦?那个Sash竟然是一个内部类,不是我们熟知的那个SWT Sash;可想而知,那个所谓的Container一定也是内部类了,回答是:当然,它的类型是PaletteComposite;这就说明,我们想要研究的那个叫splitter的Composite又另外使用了两个Composite:
Sash paletteComposite

照理说,我们在使用一个Composite的时候,如果这个Composite存在内嵌的其他组件的时候,一般情况下是要定义父类Composite的Layout的,那么这个FlyoutPaletteComposite类对象是不是也定义了布局了呢?答案是没有明确定义,那当它显示的时候怎么能知道如何布置上述的两个内嵌的组件呢?别忙,让我们看看它是不是实现了Layout方法,嘿嘿,还真有。那么这个问题就解决了。这个可以浮动的调色板组件是通过Layout方法来布置上述两个内嵌组件的;当然,我们没有必要把这一大堆代码抄在这里,谁有兴趣,可以自己去查。总之,我们知道了针对这两个组件的Layout方法;

那么接下来,我们就应该看看Sash和PaletteComposite这两个对象是干嘛的了。其实从字面上我们大体上能够有一些了解,那就是前者是负责分割EditorPart窗体的,而后者是用来包含调色板的;(由此,我还有一个小小的心得,那就是以后自己写程序一定要让定义的类可以望文生义,这样既方便了自己,也方便了别人,这可不是一件小事,大家还别笑,真的有人写Class1、Class2的,那叫一个吊,就是天王老子也弄不明白到底那些类都是干什么飞机的。)
和以上的方法一样,我们发现Sash里面包含了一个名字叫button的ButtonCanvas,当然ButtonCanvas是Canvas的一个子类,这个类在Sash中是用来在调色板隐藏的时候显示的小箭头按钮;在Sash里,最重要的是一个被称为SashDragManager的内部类,这个内部类完成了Sash这个控件的左右移动,而通过为Sash组件“着色”我发现这个内部控件仅仅是分割调色板和工作区的那一个“竖”控件;这是和SWT中的那个Sash完全不同的,那个Sash是要定义左右或者上下两个部分的;由此可见程序的实现在于功能的需求,看似相同的东西,其实内部结构可能并不相同;需要学习的东西实在是太多了;

和Sash差不多,PaletteComposite也包含了两个内部组件:一个被称为button,一个被称为title;顾名思义,我们可以知道前一个肯定是显示在调色板上的那个可以点击,用来打开/收缩调色板的那个小箭头按钮,而后一个一定是实现在调色板上的那个字符串,是不是呢?是的。
说到这里,看客们一定皱起眉头狐疑起来了,那底下的那些调色板工具呢?他们在哪里呢?其实一开始我也很狐疑,究竟开发GEF这厮把这些调色板工具弄到哪里了呢?不研究不知道,一研究吓一跳,他们竟然把她,唉!

让我们回头再好好看看,在FlyoutPaletteComposite类中还有一个对象叫:private PaletteViewer pViewer;这个Viewer做了些什么见不得人的勾当呢?呵呵,这个勾当就是在PaletteComposite的layout方法里:

1 pCtrl.setBounds(area);

那么这个pCtrl又是啥呢,一句话,他就是那个pViewer底层的控件,pCtrl一旦被设置Bounds,也就是被布局,那么pViewer所代表的那些调色板工具就被显示出来了,而且是显示在PaletteComposite里;怎么样,够简单的吧。

继续!那PaletteViewer又是啥?这还真的不太好描述,可是如果我们看看他的构造函数也许能够给我们一些启发: 

1 public PaletteViewer() {
2     EditDomain domain = new EditDomain();
3     domain.setDefaultTool(new PaletteSelectionTool());
4     domain.loadDefaultTool();
5     setEditDomain(domain);
6     setKeyHandler(new PaletteViewerKeyHandler(this));
7     setEditPartFactory(new PaletteEditPartFactory());
8 }

 看到最后一行了么,是不是似曾相识呢?他也要设置一个EditPartFactory;那这个工厂又能干什么呢?

 1 public EditPart createEditPart(EditPart parentEditPart, Object model) {
 2     if (model instanceof PaletteRoot)
 3         return createMainPaletteEditPart(parentEditPart, model);
 4     if (model instanceof PaletteStack)
 5         return createStackEditPart(parentEditPart, model);
 6     if (model instanceof PaletteContainer) {
 7         Object type = ((PaletteContainer)model).getType();
 8         if (PaletteDrawer.PALETTE_TYPE_DRAWER.equals(type))
 9             return createDrawerEditPart(parentEditPart, model);
10         if (PaletteGroup.PALETTE_TYPE_GROUP.equals(type)
11                 || PaletteContainer.PALETTE_TYPE_UNKNOWN.equals(type))
12             return createGroupEditPart(parentEditPart, model);
13         if (PaletteToolbar.PALETTE_TYPE_TOOLBAR_GROUP.equals(type))
14             return createToolbarEditPart(parentEditPart, model);
15     }
16     if (model instanceof PaletteTemplateEntry)
17         return createTemplateEditPart(parentEditPart, model);
18     if (model instanceof PaletteSeparator)
19         return createSeparatorEditPart(parentEditPart, model);
20     if (model instanceof PaletteEntry)
21         return createEntryEditPart(parentEditPart, model);
22     return null;
23 }

至此,我们明白了调色板Composite中下面部分专门显示调色板工具的那个pCtrl是通过pViewer来管理所有工具的,而每一个工具,比如Connection,或者我们自己定义的那些图形元素,在调色板里,也是按照MVC的基本概念,以GEF框架的基本实现形式来创建的;例如,看见上面的那个Seperator了么,他是我们用来分割两个Drawer或者是Group什么的,事实上,他是一个Model,类名字叫PaletteSeparator,与之对应的Controller是什么呢,是SeparatorEditPart,那么我们用脚后跟想一下,这个EditPart是不是会有一个createFigure方法呢,是的:

1 protected IFigure createFigure() {
2     return new SeparatorFigure();
3 }

哈,让你还拽!不过如此。通过这个小例子,我们知道,每一次我们在自定义的Editor中创建Palette时,每一个工具的创建,实际上都是创建了一个Model,这个模型在GEF内部对应了一个EditPart,每一个EditPart都会创建自己的IFigure,而这些IFigure最终将会被显示在调色板上;由于我们给出了每一个工具Model的基本信息,包括图标、描述、构造类等等,所以他们被创建时,也就按照我们的给定值显示出来了;只是由于我们不能直接访问这些内部类,所以我们才被那几个巨人玩弄于鼓掌之间;所以我们现在要做的就是革命! 

posted on 2010-08-18 15:56  wayne.wang  阅读(1642)  评论(0编辑  收藏  举报

导航