站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Eclipse 专题使用 XML: Eclipse 中的布局、属性和首选项

使用 XML: Eclipse 中的布局、属性和首选项

摘要: XM 最初就是在 developerWorks 的“使用 XML”专栏中开发的,它是一个简单的使用 XML 和 XSL 的发布框架。在这一部分中,Benoit Marchal 进一步讨论了用户界面考虑事项,其中包括如何为 XSL 发布插件管理 Eclipse 中的属性和首选项。

在几篇专栏文章之前,我就开始构建用于 XM 的用户界面。我选择了在 Eclipse 上构建用户界面,Eclipse 是 IBM 赠予开放源码社区的一个集成开发环境(IDE)。它旨在作为一个基本的框架,工具开发人员可以通过插件来扩展它。我在这个系列文章中的目标是编写这样一个用于 XSL 的插件。

Eclipse 最令人感兴趣的方面之一是,通过将同一类中最佳的插件组合起来,您就有可能(并且方便地)定制自己的工作环境。例如,在 XM 和 XML 编辑器之间存在明显的相互协作(请参阅 参考资料中的“Black Sun”站点)。 而且,正如本专栏文章所展示的,如果目前不支持您所选的工具,则可以不费力地添加一个插件来支持该工具。

用户界面考虑事项

用户界面始终是插件要考虑的问题。毕竟,插件旨在提供比 XM 长期以来所具有的原始命令行界面更为图形化的界面。

我之所以选择使用 Eclipse 是因为它在用户界面上具备的优势。Eclipse 有它自己的 GUI 库 — 标准窗口小部件库(standard widget library,SWT)— 它提供了一种本机的 Swing 替代。我广泛地使用过基于 Swing 的产品,总是发现它们不如本机应用程序。不知何故,就是觉得本机控件的 Swing 仿真不是很好。SWT 使用平台的控件,结果要好得多。

不过,令人吃惊的是,在插件中几乎没有代码来管理用户界面。插件大多注册不可见的服务,譬如在上一篇专栏文章“使用 Eclipse 和 XM 构建项目”(请参阅 参考资料以获取这篇文章的链接)中所讨论的构建器。这篇专栏文章将讨论一些首选项和属性,这使我有机会来研究 SWT。

我鼓励您下载这个项目的代码(请参阅 参考资料),并在您阅读本文时查看它。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

SWT 布局

SWT 具有您期望从现代库中获得的所有控件,譬如输入域、列表和按钮。但它也有树、工具栏、表以及其它控件。只要可能,SWT 就使用本机控件,所以最后的结果是,Java 应用程序和本机应用程序实际上没有区别。

AWT、Swing 和 SWT 具有许多相似之处。它们的库的主要区别在于其实现(Java 平台与本机平台)和命名约定。对于 SWT,控件是 窗口小部件。窗口小部件类似于 JDK 中的组件。在屏幕上,窗口小部件被安排在 组合(composite)(面板和窗口等)中。SWT 组合类似于 JDK 中的容器。

在本专栏文章中,我不想把过多时间花在 SWT 的一些细节之上。只要说窗口小部件在 org.eclipse.swt.widgets 包中就足够了。其中的类名是自解释的,所以您浏览该文档应该没有问题。

相反,我将把注意力放在布局上,它不同于 AWT 和 Swing 中的布局。清单 1 摘自 XMProjectPropertiesPage 类。我将在 首选项和属性中详细讨论该类,但现在让我们把注意力放在 buildGUI() 方法上。


清单 1. buildGUI() 方法
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

private Control buildUI(Composite parent)
{
   Composite composite = new Composite(parent,SWT.NULL);
   RowLayout rowLayout = new RowLayout();
   rowLayout.type = SWT.VERTICAL;
   rowLayout.wrap = false;
   composite.setLayout(rowLayout);
   hasNature = new Button(composite,SWT.CHECK);
   hasNature.setText(Resources.getString("eclipse.hasnature"));
   hasNature.addSelectionListener(new SelectionListener()
   {
      public void widgetDefaultSelected(SelectionEvent e)
      {
         group.setEnabled(hasNature.getSelection());
      }
        
      public void widgetSelected(SelectionEvent e)
      {
         group.setEnabled(hasNature.getSelection());
      }
   });
   group = new Group(composite,SWT.NONE);
   group.setText(Resources.getString("eclipse.properties"));
   GridLayout gridLayout = new GridLayout();
   gridLayout.numColumns = 2;
   group.setLayout(gridLayout);
   Label label = new Label(group,SWT.RIGHT);
   label.setText(Resources.getString("eclipse.src"));
   srcText = new Text(group,SWT.SINGLE);
   GridData gridData = new GridData();
   gridData.widthHint = 150;
   srcText.setLayoutData(gridData);
   label = new Label(group,SWT.RIGHT);
   label.setText(Resources.getString("eclipse.rules"));
   rulesText = new Text(group,SWT.LEFT);
   gridData = new GridData();
   gridData.horizontalAlignment = GridData.FILL;
   gridData.grabExcessHorizontalSpace = true;
   rulesText.setLayoutData(gridData);
   label = new Label(group,SWT.RIGHT);
   label.setText(Resources.getString("eclipse.publish"));
   publishText = new Text(group,SWT.LEFT);
   gridData = new GridData();
   publishText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL
                                          | GridData.GRAB_HORIZONTAL));
   isBuild = new Button(group,SWT.CHECK);
   isBuild.setText(Resources.getString("eclipse.build"));
   gridData = new GridData();
   gridData.horizontalSpan = 2;
   isBuild.setLayoutData(gridData);
   return composite;
}
</td></tr></table>

类似于 AWT 和 Swing,SWT 使用 布局来控制组合中窗口小部件的大小和位置。SWT 布局几乎与 JDK 布局一样,但它们使用不同的类 — 同样,概念类似,但实现不同。

SWT 在 org.eclipse.swt.layout 包中提出了四种布局:

清单 1 使用 RowLayoutGridLayout 。 图 1 显示了清单 1 所创建的表单。它包含一个复选框和一个由四个其它窗口小部件所组成的组。 RowLayout 将复选框和组对齐。这里不能选择用 FillLayout ,因为复选框和组的大小不一样( FillLayout 要求其中所有的窗口小部件的大小都一样)。




初始化 RowLayout 与设置 RowLayout 的几个属性一样容易: type 用于在竖直列方向和水平行方向进行切换,而 wrap 控制布局是否能创建多个行。然后,用 setLayout() 方法将该布局分配给组合。

组(group)是在窗口小部件周围绘制一个边框的组合。在 清单 1 中,组使用了 GridLayout 类。为了初始化 GridLayout ,您只要设置列数。注意: GridBagLayout 通过常量(如 REMAINDERRELATIVE )间接地指定行数。

要分别指定每个单元的属性, GridLayout 使用了 GridData 类。 GridData 类似于 JDK 中的 GridBagConstraints ,但属性名称不同。注意 SWT 和 JDK 之间的主要差别:使用 SWT,您可以在窗口小部件上设置 GridData (用 setLayoutData() 方法),而不是在布局上设置。

清单 1 展示了 SWT 与 JDK 之间的另一个区别:组合没有容器中的 add() 方法。相反,窗口小部件可以在其构造函数中引用它们的父代。

虽然这两个 API 存在不同,但如果您已经使用过 AWT 或 Swing,那么学习 SWT 对您来说很容易。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

首选项和属性

首选项和属性是紧密相关的。这两者都控制环境中的配置和选项。首选项用于配置选项,而配置选项应用于每个项目中的环境。一个典型示例是用颜色来突出显示编辑器中的语法。

用 Eclipse 的术语来讲,属性附属于资源(请参阅 参考资料以获取“Creating a project”的链接,这篇文章讨论了 Eclipse 资源)。例如,属性可以控制如何构建一个给定的项目。

XM 插件将需要首选项和属性。我们的项目已经使用属性来设置源、发布和规则目录,以及控制是构建项目还是制作项目。这些属性源自老的命令行界面。在“Creating a project”中,我介绍了一个向导,由它来初始化属性,但到目前为止,还没有一个关于界面的小窍门来修改这些属性。

自从我开始使用项目性质(nature)(请参阅 参考资料以获取以前文章的链接)以来,我一直在搜索一种选项来将 XM 性质分配给任何项目。最终,我得出结论,性质可以被视为特殊的属性。

至于首选项,控制台需要一个机制来过滤消息。当处理大项目时,重新构建会生成众多消息。由于难以区分错误和进展消息,因此我想给用户一个选项来控制显示哪些消息。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

项目属性

让我们从项目属性开始。实现涉及两个类: ProjectPropertiesXMProjectPropertiesPageProjectProperties 是一个实用程序类。Eclipse 不需要这个类,但我发现它十分方便。 ProjectProperties 负责保存和读取由 Eclipse 提供的 IProject 的属性。通过将该代码单独封装在一个类中,更易于确保在整个插件中以一致的方式对待属性(特别是缺省值)。(请参阅 参考资料以获取关于下载这个类的代码的信息。)

XMProjectPropertiesPage 是 Eclipse 特有的类。它将页面添加到导航器的属性对话框,您可以对导航器中的资源单击鼠标右键来访问该对话框。 XMProjectPropertiesPage 被附加到 org.eclipse.ui.propertyPages 扩展点。

清单 2 摘自 plugin.xml,这个文档是声明该扩展的插件清单。要特别注意 objectClass 属性,它指定了对哪个对象应用该属性。导航器所识别的任何对象都可能是该域的候选对象,从一般的 IResource 到更为具体的元素(如 IProjectIFolder )。为了更为具体,使用弹出菜单所引入的 标记。


清单 2. 摘自 plugin.xml 的一段代码
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>


   
   

</td></tr></table>

在 清单 2 中, objectClass 被设置为 IProject 对象。这意味着,如果用户单击项目,就能看到属性页。然而,文件和目录将不显示该属性页。

XMProjectPropertiesPage 继承了 PropertyPage ,并覆盖 createContents() ,如 清单 3 所示。 createContents() 方法通过调用 buildUI() (我在前面的 SWT 布局中介绍过)并在 ProjectProperties 对象的帮助下读取属性来初始化属性页。


清单 3. createContents() 方法
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

public Control createContents(Composite parent)
{
   Control control = buildUI(parent);
      try
   {
      IAdaptable adaptable = getElement();
      if(adaptable instanceof IProject)
      {
         properties = new ProjectProperties((IProject)adaptable);
         readProperties();
      }
   }
   catch(CoreException x)
   {
      ErrorDialog.openError(
         PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
                               Resources.getString("eclipse.dialogtitle"),
         Resources.getString("eclipse.propertyerror"),
                               PluginTools.makeStatus(x));
   }
   return control;
}
</td></tr></table>

PropertyPage 还定义了当用户按 OKCancelApplyDefault 按钮时的回调方法。 XMProjectPropertiesPage 覆盖了其中一些方法以保存新的属性值。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

作为属性的项目性质

严格地讲,项目性质 是属性。正如我 以前所讨论的,Eclipse 使用项目性质来确定用哪个插件来管理项目。项目性质的功能之一是控制菜单项、构建器以及其它事物。属性页还包括表示性质的复选框。

我之所以将项目视为属性是因为我想给用户一个机会以将 XM 添加到任何已有的项目中。其基本原理是 Eclipse 可以让您处理与所熟悉的环境完全不同的项目和语言。您可以在早上编写 Java 代码,在下午早些时候编写 PHP 代码,然后在下午晚些时候用 XM 对网站进行更新。

在现实世界中,一个项目常涉及两个或多个这样的工具。例如,在 Pineapplesoft,我们的大多数网站都采用了 XSL(通过 XM)和 PHP。一个项目有双重性质,这似乎很合乎逻辑。从用户的角度来看,在一个项目上启用 XM 性质和设置项目属性是同时发生的,因此,这似乎比在同一屏幕上提供所有这些选项要更合乎逻辑。

当测试该特性时,我认识到我没有正确地实现项目的性质。事实证明,您必须将项目性质声明为一个扩展点。这个扩展点必须实现 IProjectNature 接口,并提供一些方法来配置该项目和取消该项目的配置。

这个扩展点在 XMProjectNature 类中,如 清单 4 所示。当性质被添加到项目时,该框架调用 configure() 方法。当除去性质时,它调用 deconfigure() 方法。 configure() 方法应该确保该性质正常工作,这涉及到注册合适的构建器。 deconfigure() 方法执行与此相反的工作。 configure() 中的代码最初是在项目向导中出现;我只不过必须将它移到了新的地方。

清单 4. XMProjectNature 类
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

package org.ananas.xm.eclipse;

import org.eclipse.core.runtime.*;
import org.eclipse.core.resources.*;

public class XMProjectNature
   implements IProjectNature, PluginConstants
{
   private IProject project;
  
   public void configure()
         throws CoreException
   {
      IProjectDescription description = project.getDescription();
      if(!hasBuildSpec(description.getBuildSpec()))
      {
         ICommand[] old = description.getBuildSpec(),
                    specs = new ICommand[old.length + 1];
         System.arraycopy(old,0,specs,0,old.length);
         ICommand command = description.newCommand();
         command.setBuilderName(BUILDER_ID);
         specs[old.length] = command;
         description.setBuildSpec(specs);
         project.setDescription(description,new NullProgressMonitor());
      }
   }

   public void deconfigure()
         throws CoreException
   {
      IProjectDescription description = project.getDescription();
      int count = getBuildSpecCount(description.getBuildSpec());
      if(count != 0)
      {
         ICommand[] old = description.getBuildSpec(),
                    specs = new ICommand[old.length - count];
         int i = 0,
             j = 0;
         while(i < old.length)
         {
            if(!old[i].getBuilderName().equals(BUILDER_ID))
               specs[i] = old[j++];
            i++;
         }
         description.setBuildSpec(specs);
         project.setDescription(description,new NullProgressMonitor());
      }
   }
  
   public IProject getProject()
   {
      return project;
   }
  
   public void setProject(IProject project)
   {
      this.project = project;
   }

   private boolean hasBuildSpec(ICommand[] commands)
   {
      return getBuildSpecCount(commands) != 0;
   }
  
   private int getBuildSpecCount(ICommand[] commands)
   {
      int count = 0;
      for(int i = 0;i < commands.length;i++)
         if(commands[i].getBuilderName().equals(BUILDER_ID))
            count++;
      return count;
   }
}
</td></tr></table>

清单 5 是插件清单中的声明。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

清单 5. 摘自 plugin.xml 的另一段代码 <table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>



   
      
   

</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

XM 首选项

首选项类似于属性,但它们与插件相关,而与资源无关。

遗憾的是,Eclipse 存储首选项的方式不同于存储属性的方式。对于首选项,Eclipse 使用首选项存储。必须用每个首选项的缺省值来初始化该存储(并且,事实上,如果首选项的值不是缺省值,则该存储只记录一条首选项)。

该存储在插件类中被初始化,这个类初始化对该插件而言是全局的对象。大多数插件都有一个插件类(如果您使用 Eclipse 向导来生成新的插件,那么向导会自动生成一个),但不知何故,XM 没有这个插件类。

通过将一个类属性添加到清单中的 标记来声明插件类,如下所示:

<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>


</td></tr></table>

对于这个项目,插件类是 XMPluginXMPlugin 继承了 AbstractUIPlugin 。当框架装入这个插件时,会创建该插件类的实例,使它有机会初始化全局对象。

实质上插件并不能试图实例化插件类本身。内存中包含的这个插件类的实例从来不应该超过一个。为了使这一点更方便,插件类实现单模式(singleton pattern):它在私有类变量中存储一个自身的引用。通过 getDefault() 方法,可以访问该共享实例。

对我们来说,最有用的方法是 initializeDefaultPreferences() ,它初始化首选项存储中的缺省值,如下面所示:

<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

protected void initializeDefaultPreferences(IPreferenceStore store)
{
   store.setDefault(LEVEL_PREFERENCE_NAME,LEVEL_ALL);
}
</td></tr></table>

XMPreferencesPage 中实现了首选项页(在首选项页上,用户可以更改首选项值),如 清单 6 所示。首选项页类似于属性页,但 Eclipse 提供了 FieldEditorPreferencePage 类,它是一个特殊的类,该类进一步简化了首选项页。


清单 6. XMPreferencesPage 类
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

package org.ananas.xm.eclipse;

import org.ananas.xm.*;
import org.eclipse.ui.*;
import org.eclipse.jface.preference.*;

public class XMPreferencesPage
   extends FieldEditorPreferencePage
   implements PluginConstants, IWorkbenchPreferencePage
{
   public XMPreferencesPage()
   {
      super(GRID);         
      IPreferenceStore store =
         XMPlugin.getDefault().getPreferenceStore();
      setPreferenceStore(store);      
   }
  
   protected void createFieldEditors()
   {
      RadioGroupFieldEditor levelEditor =
         new RadioGroupFieldEditor(
            LEVEL_PREFERENCE_NAME,
            Resources.getString("eclipse.level"),
            1,
            new String[][]
            {
               { Resources.getString("eclipse.level.all"), LEVEL_ALL },
               { Resources.getString("eclipse.level.info"), LEVEL_INFO },
               { Resources.getString("eclipse.level.warning"), LEVEL_WARNING },
               { Resources.getString("eclipse.level.error"), LEVEL_ERROR },
            },
            getFieldEditorParent());
      addField(levelEditor);                            
   }

   public void init(IWorkbench workbench)
   {
   }
}
</td></tr></table>

FieldEditorPreferencePage 使用域编辑器,而不是常规的窗口小部件。域编辑器只是一个与首选项相关的窗口小部件。只需在 createFieldEditors() 方法中实例化域编辑器,而框架会负责装入和保存首选项。

最后,我更新了控制台以读取首选项,进而相应地显示消息。如 清单 7所示,控制台直接从插件类检索首选项存储。控制台还注册属性更改侦听器。当首选项发生变化(例如,通过首选项页做了更改)时,属性存储就通知侦听器。


清单 7. initializeDefaultPreferences() 方法
<table bgcolor="#eeeeee" width="100%" cellpadding="5" cellspacing="0" border="1"><tr><td>

IPreferenceStore store = XMPlugin.getDefault().getPreferenceStore();
store.addPropertyChangeListener(new IPropertyChangeListener()
{
   public void propertyChange(PropertyChangeEvent e)
   {
      setLevel((String)e.getNewValue());
   }
});
setLevel(store.getString(LEVEL_PREFERENCE_NAME));
</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

结束语

我已经在我先前的专栏文章中充满激情地讲解了 Eclipse,但这个环境仍然让我兴奋。它是功能强大且灵活的框架。而且,对于工具开发人员来说,它很容易上手。正如在本专栏文章中多次提到的,可以很 容易地增强 Eclipse。在 Pineapplesoft,我们现在将 Eclipse 和 XM 应用到了所有的 Web 发布项目。


<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>


<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>

关于作者<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td colspan="3"></td></tr><tr align="left" valign="top"><td></td><td></td><td width="100%">

Benoit Marchal 是一位比利时顾问。他是 XML by Example 及其它 XML 书籍的作者。您可以就 XML 项目方面的问题向 Benoit 寻求帮助 — 可以通过 bmarchal@pineapplesoft.com 与他联系。

</td></tr></table>

<table border="0" cellspacing="0" cellpadding="0" width="100%"><tr><td>
</td></tr></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tr align="right"><td>
<table border="0" cellpadding="0" cellspacing="0"><tr><td valign="middle">
</td><td valign="top" align="right"></td></tr></table></td></tr></table>


↑返回目录
前一篇: 将 ActiveX 控件集成到 SWT 应用程序
后一篇: 使用 XML: 创建项目