主页 > 社区 > 项目 > platform

NetBeans IDE 5.0 FeedReader 教程

下载链接:

欢迎使用 NetBeans IDE 5.0 FeedReader 教程。在本教程中构建的 FeedReader 应用程序是一个简单的 RSS/Atom feed 浏览器,它是在 Mozilla Firefox 的 Sage 插件之后建立的模型。它呈现 feed 树,其子节点表示可以在浏览器中打开的单个 feed 条目。例如,在 IDE 的内部浏览器中打开来自 PlanetNetBeans RSS feed 的 feed 条目。

 

目录

简介

使用应用程序

设置应用程序

创建 FeedReader 窗口

试用应用程序

向应用程序中添加代码

标记应用程序

分发应用程序

简介

开始对 FeedReader 应用程序编码之前,最好熟悉一些在 NetBeans IDE 中进行应用程序开发方面经常使用的术语。在这个过程中,您将大概了解您要创建的应用程序,找到您将学习的内容并进行所需的设置。

关于常用术语

本教程假设您已经掌握了完全构建到 NetBeans 中的基础架构的基本概念。需要理解的概念没有您想象的那么多。需要熟悉的常用术语如下:

  • NetBeans Platform。 应用程序框架,它提供大多数应用程序所需要的全部内容,没有任何多余内容。NetBeans Platform 提供了应用程序的常用要求,如标准菜单、工具栏、文档管理和设置、完全开箱即用。“在 NetBeans 上”构建应用程序意味着,不是从头开始编写应用程序,而只是提供 NetBeans Platform 尚不具有的应用程序部分。并且排除您不需要的 NetBeans Platform 部分。开发周期结束时,您将您的应用程序与 NetBeans Platform 一起打包,但是使用您自己的可执行文件和程序启动画面(splash screen)。这样做不但可以为您节省很多时间和精力,而且还会使应用程序更强健、更可靠。
  • System Filesystem。 包含从已注册模块的 layer.xml 配置文件中构建的 NetBeans 配置信息的常规注册表。NetBeans 将各种配置信息存储在 System Filesystem 中。例如,System Filesystem 包含名为 Menu 的文件夹,该文件夹包含具有名称(如 FileEdit)的子文件夹。这些子文件夹包含代表 Java 类的文件,这些类执行在 IDE 的“File”和“Edit”菜单中出现的操作。
  • 插件模块 (Plug-in Module)。 一组提供具有特定功能的应用程序的 Java 类。例如,在本教程中构建的插件模块中的 Java 类所提供的功能是 RSS/Atom Feed Reader。Java 类使用 manifest.mf 文件声明模块和 layer.xml 配置文件,以在 System Filesystem 中注册它们的功能。

顺便提一下:在 NetBeans 术语中,“plug-in”是一个形容词,而“module”是一个名词。它们之间没有语义的差别。

  • NetBeans API。插件模块编写者和应用程序编写者都可以使用的公共接口和类。它们分为具体的 API,涉及不同类型的功能。Java 源包及其子包的内容和行为是 API,正如 API 参考文档中所指定的。要获得全部 NetBeans API 列表,请单击此处
  • Module Suite。 一组部署在一起相互依赖的 plug-in module。IDE 帮助您标记该套件,例如,您可以添加程序启动画面,并且您可以指定不想让应用程序提供的一部分 NetBeans 平台。

关于 FeedReader 应用程序

编写 FeedReader 应用程序时,您将利用很多 NetBeans 基础架构。您利用的第一个基础架构是 System Filesystem。正如前文指出的,System Filesystem 由配置数据组成:它是通过写入用户设置目录的系统中所有插件模块的配置文件(每个文件都作为“layer.xml”文件存储在磁盘上)构建的。

System Filesystem 对识别文件使用相同的基础架构,识别文件是用来识别磁盘上的用户文件的文件。也就是说,您可以在 IDE 的配置数据内显示文件夹视图,这就像将文件夹显示在磁盘上一样简单。这样您就可以使用构建到 NetBeans 中的所有管道(plumbing),用于查看文件和显示树等等。实际上,您在 IDE 中看到的很多视图都使用相同的技术。例如,Favorites 窗口是 System Filesystem 中的文件夹视图,它包含到磁盘上文件的链接。Runtime 窗口的内容还是 System Filesystem 中的文件夹视图,这就是插件模块能够向它添加节点的原因。由于它使用的机理与识别磁盘上文件的机理相同,因此,文件夹中的对象可以具有您选择的任何图标和显示名称。

您使用的另一个基础架构是 Nodes API。Nodes API 是 TreeNode 的概括,但是 Nodes 可以显示在各种查看器组件中,而不仅仅是显示在树中。Nodes 通常表示 DataObject。DataObject 基本上是一个已进行分析的文件,换句话说,就是一个了解它在文件中的含义以及该文件所代表的含义,并且可以使用它执行某些操作的 Java 对象。Nodes 向 DataObjects 添加与用户交互的功能,如操作、本地化显示名称和图标。

因此,使用向导生成一些基本模板之后,您将使用 layer.xml 文件在 System Filesystem 中为 RSS feed 对象创建一个文件夹(创建 RssFeeds 文件夹)。接下来,您将提供类似于 IDE Projects 窗口或 Files 窗口的文件夹视图,方法是在生成的其中一个文件上构建(延伸 Feed Window)。该试图作为 RSS feed 对象文件夹中的根。然后您获得表示该文件夹及其 Node 的 DataObject。您将该节点包在 FilterNode 中(创建 RssFeeds 文件夹)。FilterNode 是一个可以充当另一个节点的包装的节点;默认情况下,它的行为与另一个节点完全相同,但是您可以重写它上面的方法来更改其行为,以便为它提供自己的图标、显示名称和操作。然后通过每个节点的子节点执行相同的操作,您还可以包装这些子节点。

接下来,您将在根节点上创建 Add Feed 操作。用户添加 RSS feed 时,您所执行的操作非常简单:创建一个新 Feed 对象(实际上只是一个包含 URL 的对象创建 Feed 对象),然后将该 Feed 对象作为 RSSFeeds 文件夹中的文件进行序列化。由于您使用 NetBeans 内置的基础架构查看文件(因为您只是获得该文件夹的标准节点,这样可以注意添加或移除文件的时间),新添加 feed 的节点瞬间就会出现在用户界面中。以这种方式使用 System Filesystem 意味着,您不需要为了退出时保存 RSS feed 的列表而编写任何代码!当用户创建 feed 时保存 feed,而数据将自动继续保存到磁盘。因此,基本上您只是将 Feed POJOs 丢入文件夹中,偶然显示该文件夹的视图。系统帮助完成所有其他操作。

关于本教程

本教程要教您以下内容:

  • 使用向导和 NetBeans IDE 5.0 提供的其他设施,在 NetBeans Platform 上创建应用程序。
  • 使用 NetBeans IDE 5.0 中的向导,创建框架窗口控制组件和框架菜单项。
  • 使用 NetBeans Nodes API 为 feed 和 feed 条目创建节点。
  • 在 NetBeans System Filesystem 中注册应用程序。
  • 用项目(如您自己的程序启动画面和标题栏)标记应用程序。
  • 提供应用程序的分发。

安装完软件之后,本教程可以在 60 分钟之内完成。

关于资源

开始之前,需要在您的计算机上安装以下资源:

  • NetBeans IDE 5.0(下载
  • Java Standard Development Kit (JDK) 1.4.2(下载)或 5.0(下载
  • Rss and atOM utilitiEs(下载
  • Rome Fetcher(下载
  • JDom(下载
  • FeedReader 图标和程序启动画面(下载)。您可以将图标和程序启动画面放置在您的文件系统中的任何位置。在本教程的后面部分,将向您介绍如何将它们包含在您的 NetBeans 项目文件夹中。

使用应用程序

开始编写应用程序之前,您可能想使自己熟悉一下最终产品。幸运的是,FeedReader 应用程序是一个正式的 NetBeans 示例,该应用程序与 IDE 捆绑在一起,您可以将其从 New Project 向导中拉出。

安装应用程序

采用以下步骤来安装示例:

  1. 启动 IDE。
  2. 选择 File > New Project (Ctrl-Shift-N),展开 Samples,选择 NetBeans Plug-in Modules,然后选择 FeedReader。单击 Next。
  3. 将该项目命名为您喜欢的名称,选择要存储项目的位置,然后单击 Finish。IDE 便打开了 FeedReader 示例。
  4. 右键单击 FeedReader Application 项目节点,然后选择 Run Project。将启动该应用程序。在安装过程中,注意到将显示程序启动画面。

介绍应用程序

FeedReader 应用程序显示 RSS/Atom Feeds 窗口,其中包含名为 RSS/Atom Feeds 的节点。

  1. 右键单击 RSS/Atom Feeds 节点,选择 Add,然后输入 RSS/Atom feed 的 URL。例如,输入 NetBeans.org RSS feed (http://www.netbeans.org/rss-091.xml) 或 PlanetNetBeans RSS feed (http://www.planetnetbeans.org/rss20.xml)
  2. 单击 OK。为 feed 添加了一个节点;它的子节点是 feed 条目。正常情况下,feed 条目是 feed 可用的文章或主题。
  3. 重复该过程并添加更多 feed。
  4. 双击 feed 条目以在 IDE 的默认浏览器中打开它。

富客户端应用程序提供的其他功能:

  • 右键单击一个节点,然后选择 Add Folder 创建一个新文件夹,可以使用该文件夹组织 feed。
  • 右键单击一个 feed,然后选择 Delete 可删除一个 feed。
  • 右键单击一个 feed 条目,然后选择 Open 可在编辑器窗格中打开它。

介绍源

FeedReader 示例由主文件(Java 类)和支持文件组成。

主文件:

  • Feed.java
    封装一个 URL 及其关联的 Rome feed。
  • FeedAction.java
    定义在 Window 菜单中出现的操作,标签为 Open Feed Window。它将打开 Feed Window。
  • FeedTopComponent.java
    定义在 Window 菜单中出现的操作,标签为 Open Feed Window。它将打开 Feed Window。
  • RssNode.java
    RSS feeds 文件夹及其子文件夹的节点类。注意,您主要代理 System Filesystem 中的 RssFeeds 文件夹的实际 DataNode/DataFolder。这将为您免费提供很多功能,如删除、侦听更改的功能等等。因此当您添加新的 RSS feed 时,您所要做的只是创建一个新的 Feed 对象并将其序列化到该文件夹中。

支持文件:

  • build.xml
    为常用任务(如构建和运行应用程序)提供 Ant 目标。
  • Bundle.properties
    本地化键值对。
  • FeedTopComponentSettings.xml
    指定 FeedReader 应用程序的所有接口。
  • FeedTopComponentWstcref.xml
    指定参考组件。
  • layer.xml
    在 System Filesystem 中注册文件夹和文件。将向您显示如何使用 System Filesystem Browser 来使用该文件。
  • project.xml
    声明项目元数据,如模块依存关系。将向您显示如何使用 Project Properties 对话框来使用该文件。
  • rss16.gif
    应用程序的菜单项以及在其 Help > About 对话框中显示的图标。
  • splash.gif
    程序启动画面。

设置应用程序

在 NetBeans IDE 中,在 NetBeans 上构建应用程序开始生成很多文件,这些文件将作为您应用程序的基础。例如,IDE 提供 Module Project 向导、Module Suite Project 向导和 Library Wrapper Module Project 向导,这些向导设置在 NetBeans Platform 上构建的插件模块和应用程序所需的所有基本文件。

  • 模块套件项目。一个项目,组合了模块项目和彼此有依存关系的库包装模块项目,并允许您作为一个单元来部署它们。
  • 库包装模块项目。一个项目,将库 JAR 文件放置在它的类路径上,并从模块中导出一些或所有 JAR 文件的包作为公共包。
  • 模块项目。一个项目,用于实现在 NetBeans Platform 上构建的插件模块或应用程序的功能、业务逻辑和用户界面。

创建模块套件项目

  1. 选择 File > New Project (Ctrl-Shift-N)。在 Categories 下,选择 NetBeans Plug-in Modules。在 Projects 下,选择 Module Suite Project。单击 Next。
  2. 在 Name and Location 面板中,在 Project Name 中键入 feedreader-suite。将 Project Location 更改为您计算机上的任何目录,如 c:\mymodules。单击 Finish。

IDE 创建 feedreader-suite 项目。该项目将包含模块项目以及您将在以下小节中创建的库包装模块项目。

该项目在 IDE 中打开。您可以在 Projects 窗口 (Ctrl-1) 中查看其逻辑结构并在 Files 窗口 (Ctrl-2) 中查看其文件结构。

包装库

您可以将整个 FeedReader 应用程序打包到单个插件模块中。但是,应用程序需要 Rome、Rome Fetcher 和 JDom 库:

  • Rome。读取 RSS feed 和 Atom feed,使用非常简单的 API。
  • Rome Fetcher。允许通过 HTTP 检索 feed。
  • JDom。是一个 XML 分析 API。FeedReader 需要它的唯一原因是 Rome 库使用它。

随后,如果您想用使用这些库的更多模块扩展 FeedReader 应用程序,对它们来说,最好只依赖库模块,而不是整个 FeedReader。此外,还可以自动加载库模块,这意味着 NetBeans 将只在需要时加载。在此之前,运行时它将不占用任何内存。

  1. 选择 File > New Project (Ctrl-Shift-N)。在 Categories 下,选择 NetBeans Plug-in Modules。在 Projects 下,选择 Library Wrapper Module Project。单击 Next。
  2. 在 Select Library 面板中,浏览到下载 JDom 的文件夹,然后选择 jdom.jarLICENSE.txt。单击 Next。
  3. 在 Name and Location 面板中,接受所有默认值。注意到库包装模块项目将位于模块套件项目内。您还可以将它放置在其他位置,但是出于版本控制的目的,最好将其放置在模块套件项目内。还注意到在 Add to Module Suite 下拉列表中已选中 feedreader-suite 模块套件项目。单击 Next。
  4. 在 Basic Module Configuration 面板中,接受所有默认值。单击 Finish。新的库包装模块项目在 IDE 中打开并在 Projects 窗口中显示。
  5. 为 Rome 创建库包装模块项目。接受所有默认值。
  6. 为 Rome Fetcher 创建库包装模块项目。

现在,您已拥有一个包含 JDom JAR 文件的库包装模块项目,以及包含 Rome 和 Rome Fetcher JAR 文件的两个其他库包装模块项目。

创建模块项目

  1. 选择 File > New Project (Ctrl-Shift-N)。在 Categories 下,选择 NetBeans Plug-in Modules。在 Projects 下,选择 Module Project。单击 Next。
  2. 在 Name and Location 面板中,在 Project Name 中键入 FeedReader。接受所有默认值。单击 Next。
  3. 在 Basic Module Configuration 面板中,用 myorg 替换 Code Name Base 中的 yourorghere,从而整个 Code Name Base 为 org.myorg.feedreader。离开本地化包和 XML 层的位置,以便它们存储在名为 org/myorg/feedreader 的包中。单击 Finish。

IDE 创建 FeedReader 项目。该项目包含所有模块的源和项目元数据,如该项目的 Ant 构建脚本。该项目在 IDE 中打开。您可以在 Projects 窗口 (Ctrl-1) 中查看其逻辑结构并在 Files 窗口 (Ctrl-2) 中查看其文件结构。现在 Projects 窗口应该显示以下内容:

右键单击 feedreader-suite 项目节点,选择 Properties,然后单击 Project Properties 对话框中的 Sources。该面板显示创建它们的项目时添加到模块套件的模块。您应该看到在 Suite Modules 列表中列出了 FeedReaderjdomromerome-fetcher

创建 FeedReader 窗口

本部分中,您使用 Window Component 向导生成文件,这些文件创建自定义窗口控制组件以及调用它的操作。该向导还将该操作注册为 layer.xml 配置文件中的菜单项,并添加条目以便序列化窗口控制组件。在完成该部分之后,向您介绍如何试用 Window Component 向导为您生成的文件。

  1. 右键单击 FeedReader 项目节点,然后选择 New > File/Folder。在 Categories 下,选择 NetBeans Module Development。在 File Types 下,选择 Window Component。单击 Next。
  2. 在 Basic Settings 面板中,从下拉列表中选择 explorer,然后单击 Open on Application Start。单击 Next。
  3. 在 Name and Location 面板中,键入 Feed 作为 Class Name Prefix,并浏览到您保存 rss16.gif 的位置。GIF 文件将显示在调用该操作的菜单项中。
  4. 单击 Finish。

IDE 创建以下新文件:

  • FeedAction.java. 定义在 Window 菜单中出现的操作,标签为 Open Feed Window,图像为 rss16.gif。它将打开 Feed Window。
  • FeedTopComponent.java. 定义 Feed Window。
  • FeedTopComponentSettings.xml. 指定 org.myorg.feedreader 富客户端应用程序的所有接口。方便查找实例,无需实例化每个实例。无需加载类或创建对象,从而提高了性能。已在 layer.xml 文件的 Windows2/Components 文件夹中注册。
  • FeedTopComponentWstcref.xml. 指定参考组件。使组件属于多个模式。已在 layer.xml 文件的 Windows2/Modes 文件夹中注册。

IDE 修改以下现有文件:

  • project.xml. 已经添加两个模块依存关系 Utilities API (单击此处即可获得 Javadoc)和 Window System API (单击此处即可获得 Javadoc)。
  • Bundle.properties. 已经添加三个键值对:

    CTL_FeedAction。
    本地化在 FeedAction.java 中定义的菜单项的标签。
    CTL_FeedTopComponent。
    本地化 FeedTopComponent.java 的标签。
    HINT_FeedTopComponent。 本地化 FeedTopComponent.java 的工具提示。

  • 最后,三个注册条目已经添加到 layer.xml 文件中:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN"
			"http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<filesystem>
	<folder name="Actions">
		<folder name="Window">
			<file name="org-myorg-feedreader-FeedAction.instance"/>
		</folder>
	</folder>
	<folder name="Menu">
		<folder name="Window">
			<file name="FeedAction.shadow">
				<attr name="originalFile" stringvalue="Actions/Window/org-myorg-feedreader-FeedAction.instance"/>
			</file>
		</folder>
	</folder>
	<folder name="Windows2">
		<folder name="Components">
			<file name="FeedTopComponent.settings" url="FeedTopComponentSettings.xml"/>
		</folder>
		<folder name="Modes">
			<folder name="explorer">
				<file name="FeedTopComponent.wstcref" url="FeedTopComponentWstcref.xml"/
			</folder>
		</folder>
	</folder>
</filesystem>

这是 layer.xml 文件中的条目所执行的操作:

  • <Actions>
    在 Window 文件夹中将该操作注册为一个操作。
  • <Menu>
    在 Window 菜单中将该操作注册为一个菜单项。
  • <Windows2>
    注册 FeedTopComponentSettings.xml,该文件用于查找窗口控制组件。在“editor”窗口中注册组件参考文件 FeedTopComponentWstcref.xml。要将组件停靠在不同的窗口中,请将“explorer”手动更改为以下值之一:

    commonpalette|
    editor
    properties
    leftSlidingSide
    rightSlidingSide\
    bottomSlidingSide
    navigator
    debugger
    output

试用应用程序

无需键入一行代码,您就可以运行应用程序。试用就是将该应用程序部署到 NetBeans IDE 安装的另一个实例,然后察看空 Feed Window 能否正确显示。

IDE 使用 Ant 构建脚本构建和安装应用程序。构建脚本是创建模块项目时为您创建的。

  1. 在 Projects 窗口中,右键单击 feedreader-suite 项目,然后选择 Run。在 NetBeans IDE 安装的另一个实例中构建和安装应用程序。IDE 打开并显示新的 Feed Window。
  2. 在 IDE 的 Window 菜单中,您应该看到新的菜单项。
  3. 注意窗口的标签附近。您应该看到出现提示。
  4. 单击 Feed Window 标签右侧的小叉号。该窗口关闭。
  5. 选择 Window > Open Feed Window 可重新打开该窗口。
  6. 从主菜单中,选择 File > Exit 退出 IDE。

向应用程序中添加代码

为您的应用程序打好基础之后,就可以添加您自己的代码了。实现此操作之前,需要指定该应用程序的依存关系。然后,您使用 New File 向导和 Source Editor 来创建和编写主类的代码,这些主类将在介绍源部分中进行介绍。

指定应用程序的依存关系

您需要将几个属于 NetBeans API 的类设为子类。每个类都将被声明为依存关系。使用 Project Properties 对话框来执行此操作。

  1. 在 Projects 窗口中,右键单击 FeedReader 项目,然后选择 Properties。在 Project Properties 对话框中,单击 Libraries。注意到一些 API 已经被声明为 Module Dependencies。利用 Window Component 向导来完成此操作。
  2. 单击 Libraries 面板顶部的 Add...。
  3. 添加以下 API:

    Actions API
    Datasystems API
    Dialogs API
    Explorer and Property Sheet API
    File System API
    Nodes API
    rome
    rome-fetcher

  4. 单击 OK,退出 Project Properties 对话框。
  5. 或者,在 Projects 窗口中,展开 IDE 中的 FeedReader 模块项目,展开 Important Files 节点,双击 Project Metadata 在 IDE 中打开它。注意到您所选择的 API 已经被声明为依存关系。
  6. 下一步,您需要使 Rome 依赖于 JDom。在 Projects 窗口中,右键单击 Rome 库包装模块项目,然后选择 Properties。在 Project Properties 对话框中,单击 Libraries,然后单击 Module Dependencies 列表旁边的 Add。添加 JDom。单击 OK,退出 Project Properties 对话框。
  7. 最后,由于 Rome Fetcher 依赖于 Rome 和 JDom,因此您需要使 Rome Fetcher 依赖于 Rome。由于 Rome 已经依赖于 JDom,因此,您不需要使 Rome Fetcher 依赖于 JDom。
  8. 或者,在 Projects 窗口中,展开 IDE 中的 Rome 模块,展开 Important Files 节点,双击 Project Metadata 在 IDE 中打开它。注意到 Rome JAR 位于类路径上,JDom 是一个模块依存关系,并且它们的包是公开显示的。在 Rome Fetcher 的 Project Metadata 文件中,您应该看到相同结果。

创建 RssFeeds 文件夹

您将使用 System Filesystem Browser 为 RSS feed 对象添加文件夹。之后,您将向 Window Component 向导为您创建的 FeedTopComponent.java 中添加代码,以查看该文件夹的内容。

  1. 在 Projects 窗口中,展开 FeedReader 项目节点,展开 Important Files 节点,然后展开 XML Layer 节点。将打开 System Filesystem Browser。显示以下代码:

    <this layer>。
    当前插件模块提供的文件夹
    <this layer in context>。 所有文件夹都可以在 System Filesystem 中使用。

  2. 右键单击 <this layer> 节点,然后选择 New > Folder。
  3. 在 New Folder 对话框中,键入 RssFeeds。单击 OK。双击 layer.xml 文件的节点,在 Source Editor 中打开它。注意已经添加了这个条目:

    <folder name="RssFeeds"/>

创建 Feed 对象

接下来您将创建一个简单的 POJO,它封装了一个 URL 及其关联的 Rome feed。

  1. 右键单击 FeedReader 项目节点,选择 New > File/Folder,然后在 Java Class 类别中选择 Java Class 文件类型。单击 Next。
  2. 将该类命名为 Feed,然后在 Package 下拉列表中选择 org.myorg.feedreader。单击 Finish。
  3. 在 Source Editor 中,用以下内容替换默认的 Feed 类:
public class Feed implements Serializable {
	private static FeedFetcher s_feedFetcher = new HttpURLFeedFetcher( HashMapFeedInfoCache.getInstance());
	private transient SyndFeed m_syndFeed;
	private URL m_url;
	private String m_name;

	protected Feed() {
	}

	public Feed(String str) throws MalformedURLException
	{
		m_url = new URL(str);
		m_name = str;
	}
		
	public URL getURL()
	{
		return m_url;
	}
		
	public SyndFeed getSyndFeed() throws IOException {
		if (m_syndFeed == null) {
			try {
				m_syndFeed = s_feedFetcher.retrieveFeed(m_url);
				if (m_syndFeed.getTitle() != null)
					m_name = m_syndFeed.getTitle();
			} catch(Exception ex) {
				throw new IOException(ex.getMessage());
			}
		}
		return m_syndFeed;
	}

	public String toString(){
		return m_name;
	}

}

很多代码都标有下划线,原因是您没有声明它们的包。下一步将声明这些包。

采用以下步骤重新格式化该文件并声明其依存关系:

  1. 按 Ctrl-Shift-F 格式化该代码。
  2. 按 Alt-Shift-F,选择 java.net.URL,单击 OK,IDE 将以下 import 语句添加到包语句下面:

    import com.sun.syndication.feed.synd.SyndFeed;
    import com.sun.syndication.fetcher.FeedFetcher;
    import com.sun.syndication.fetcher.impl.HashMapFeedInfoCache;
    import com.sun.syndication.fetcher.impl.HttpURLFeedFetcher;
    import java.io.IOException;
    import java.io.Serializable;
    import java.net.MalformedURLException;
    import java.net.URL;

现在所有红色下划线都已消失。如果没有消失,则继续本教程,直到解决该问题为止。

延伸 Feed Window

  1. 双击 FeedTopComponent.java,以便它在 Source Editor 中打开。
  2. 在类声明的结尾键入 implements ExplorerManager.Provider
  3. 在行中按 Alt-Enter 并单击建议。IDE 为所需的包 org.openide.explorer.ExplorerManager 添加 import 语句。
  4. 再次按 Alt-Enter 并单击建议。IDE 执行抽象方法 getExplorerManager()
  5. 在新的 getExplorerManager() 方法正文中键入 return manager;。在行中按 Alt-Enter,并让 IDE 为您创建一个名为 manager 的字段。用下面这个语句替换默认的定义:

    private final ExplorerManager manager = new ExplorerManager();

  6. 就在上一步中该字段声明下面,声明该语句:

    private final BeanTreeView view = new BeanTreeView();

  7. 最后,将以下代码添加到构造方法的结尾:
setLayout(new BorderLayout());
add(view, BorderLayout.CENTER);
view.setRootVisible(true);
try {
	manager.setRootContext(new RssNode.RootRssNode());
} catch (DataObjectNotFoundException ex) {
	ErrorManager.getDefault().notify(ex);
}
ActionMap map = getActionMap();
map.put("delete", ExplorerUtils.actionDelete(manager, true));
associateLookup(ExplorerUtils.createLookup(manager, map));

现在,很多代码都标有下划线,原因是您没有声明它们的关联包。下一步将声明这些包。

采用以下步骤重新格式化该文件并声明其依存关系:

  1. 按 Ctrl-Shift-F 格式化该代码。
  2. 按 Alt-Shift-F,选择 org.openide.ErrorManager,单击 OK,IDE 将几个 import 语句添加到包语句下面。现在,导入语句的完整列表应该如下所示:

    import java.awt.BorderLayout;
    import java.io.Serializable;
    import javax.swing.ActionMap;
    import org.openide.ErrorManager;
    import org.openide.explorer.ExplorerManager;
    import org.openide.explorer.ExplorerUtils;
    import org.openide.explorer.view.BeanTreeView;
    import org.openide.loaders.DataObjectNotFoundException;
    import org.openide.util.NbBundle;
    import org.openide.util.RequestProcessor;
    import org.openide.util.Utilities;
    import org.openide.windows.TopComponent;

  3. 注意到行 manager.setRootContext(new RssNode.RootRssNode()); 仍然标有红色下划线,原因是您尚未创建 RssNode.java 。下一小节您将创建它。现在所有其他的红色下划线已消失。如果没有消失,则继续本教程,直到解决该问题为止。

创建 RssNode 类

  1. org.myorg.feedreader 包中创建 RssNode.java
  2. 用以下语句替换默认的类:
class RssNode extends FilterNode {

	/** Declaring the children of the root RSS node */
	public RssNode(Node folderNode) throws DataObjectNotFoundException { 
		super(folderNode, new RssFolderChildren(folderNode));
	}
	/** Declaring the Add Feed action and Add Folder action */
	public Action[] getActions(boolean popup) {
		DataFolder df = (DataFolder)getLookup().lookup(DataFolder.class);
		return new Action[] { new AddRssAction(df),
			new AddFolderAction(df) };
	}

	/** Getting the root node */
	public static class RootRssNode extends RssNode {
		public RootRssNode() throws DataObjectNotFoundException {
			super(DataObject.find(
				Repository.getDefault().getDefaultFileSystem()
				.getRoot().getFileObject("RssFeeds")).getNodeDelegate());
		}
		public String getDisplayName() {
			return NbBundle.getMessage(RssNode.class, "FN_title");
		}
	}

	/** Getting the children of the root node */
	private static class RssFolderChildren extends FilterNode.Children {
		RssFolderChildren(Node rssFolderNode) {
			super(rssFolderNode);
		}
		protected Node[] createNodes(Object key) {
			Node n = (Node) key;
			try {
				if (n.getLookup().lookup(DataFolder.class) != null) {
					return new Node[] { new RssNode(n) };
				} else {
					Feed feed = getFeed(n);
					if (feed != null) {
						return new Node[] { new OneFeedNode(n, feed.getSyndFeed()) };
					} else {
						// best effort
						return new Node[] { new FilterNode(n) };
					}
				}
			} catch (IOException ioe) {
				ErrorManager.getDefault().notify(ioe);
			} catch (IntrospectionException exc) {
				ErrorManager.getDefault().notify(exc);
			}
			// Some other type of Node (gotta do something)
			return new Node[] { new FilterNode(n) };
		}
	}

	/** Getting the feed node and wrapping it in a FilterNode */
	private static class OneFeedNode extends FilterNode {
		OneFeedNode(Node feedFileNode, SyndFeed feed) throws IOException, IntrospectionException {
			super(feedFileNode,
				new FeedChildren(feed),
				new ProxyLookup(new Lookup[] {
			Lookups.fixed(new Object[] { feed  }),
					feedFileNode.getLookup() }));
		}
		
		public String getDisplayName() {
			SyndFeed feed = (SyndFeed) getLookup().lookup(SyndFeed.class);
			return  feed.getTitle();
		}

		public Image getIcon(int type) {
			return Utilities.loadImage("org/myorg/feedreader/rss16.gif");
		}

		public Image getOpenedIcon(int type) {
			return getIcon(0);
		} 
		public Action[] getActions(boolean context) {
			return new Action[] { SystemAction.get(DeleteAction.class) };
		}
	}
	
	/** Defining the children of a feed node */
	private static class FeedChildren extends Children.Keys {
		private final SyndFeed feed;
		public FeedChildren(SyndFeed feed) {
			this.feed = feed;
		}
		
		protected void addNotify() {
			setKeys(feed.getEntries());
		}
		
		public Node[] createNodes(Object key) {
			try {
				return new Node[] { new EntryBeanNode((SyndEntry) key) };
			} catch (final IntrospectionException ex) {
				ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ex);
				//Should never happen - no reason for it to fail abov
				return new Node[] { new AbstractNode(Children.LEAF) {
					public String getHtmlDisplayName() {
						return "<font color='red'>" + ex.getMessage() + "</font>";
					}
				}};
			}
		}
	}

	/** Wrapping the children in a FilterNode */
	private static class EntryBeanNode extends FilterNode {
		private SyndEntry entry;
		public EntryBeanNode(SyndEntry entry) throws IntrospectionException {
			super(new BeanNode(entry), Children.LEAF, Lookups.fixed(new Object[] { entry, new EntryOpenCookie(entry) }));
			this.entry = entry;
		}
		
		/** Using HtmlDisplayName ensures any HTML in RSS entry titles are             
		/**properly handled, escaped, entities resolved, etc. */
		public String getHtmlDisplayName() {
			return entry.getTitle();
		}

		/** Making a tooltip out of the entry's description */
		public String getShortDescription() {
			return entry.getDescription().getValue();
		}

		/** Providing the Open action on a feed entry */
		public Action[] getActions(boolean popup) {
			return new Action[] { SystemAction.get(OpenAction.class) };
		}

		public Action getPreferredAction() {
			return (SystemAction) getActions(false) [0];
		}

      }
	
	/** Specifying what should happen when the user invokes the Open action */
	private static class EntryOpenCookie implements OpenCookie {
		private final SyndEntry entry;
		EntryOpenCookie(SyndEntry entry) {
			this.entry = entry;
		}

		public void open() {
			try {
				URLDisplayer.getDefault().showURL(new URL(entry.getUri()));
			} catch (MalformedURLException mue)       {
				ErrorManager.getDefault().notify(mue);
			}
		}
      }

	/** Looking up a feed */
	private static Feed getFeed(Node node) {
		InstanceCookie ck = (InstanceCookie) node.getCookie(InstanceCookie.class);
		if (ck == null) {
			throw new IllegalStateException("Bogus file in feeds folder: " + node.getLookup().lookup(FileObject.class));
		}
		try {
			return (Feed) ck.instanceCreate();
		} catch (ClassNotFoundException ex) {
			ErrorManager.getDefault().notify(ex);
		} catch (IOException ex) {
			ErrorManager.getDefault().notify(ex);
		}
		return null;
	}

	/** Creating an action for adding a folder to organize feeds into groups */
	private static class AddFolderAction extends AbstractAction {
		private DataFolder folder;
		public AddFolderAction(DataFolder df) {
			folder = df;
			putValue(Action.NAME, NbBundle.getMessage(RssNode.class, "FN_addfolderbutton"));
		}
		public void actionPerformed(ActionEvent ae) {
			NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
				NbBundle.getMessage(RssNode.class, "FN_askfolder_msg"), //NOI18N
				NbBundle.getMessage(RssNode.class, "FN_askfolder_title"), //NOI18N
				NotifyDescriptor.OK_CANCEL_OPTION,
				NotifyDescriptor.PLAIN_MESSAGE);
			Object result = DialogDisplayer.getDefault().notify(nd);
				if (result.equals(NotifyDescriptor.OK_OPTION)) {
					final String folderString = nd.getInputText();
					try {
						DataFolder.create(folder, folderString);
					} catch (IOException ex) {
						ErrorManager.getDefault().notify(ex);
					}
				}
		}
	}

	/** Creating an action for adding a feed */
	private static class AddRssAction extends AbstractAction {
		private DataFolder folder;
		public AddRssAction(DataFolder df) {
			folder = df;
			putValue(Action.NAME, NbBundle.getMessage(RssNode.class, "FN_addbutton"));
		}
		public void actionPerformed(ActionEvent ae) {
			NotifyDescriptor.InputLine nd = new NotifyDescriptor.InputLine(
				NbBundle.getMessage(RssNode.class, "FN_askurl_msg"), //NOI18N
				NbBundle.getMessage(RssNode.class, "FN_askurl_title"), //NOI18N
				NotifyDescriptor.OK_CANCEL_OPTION,
				NotifyDescriptor.PLAIN_MESSAGE);
			Object result = DialogDisplayer.getDefault().notify(nd);
			if (result.equals(NotifyDescriptor.OK_OPTION)) {
				final String urlString = nd.getInputText();
				try {
					Feed f = new Feed(urlString);
					FileObject fld = folder.getPrimaryFile();
					String baseName = "RssFeed"; //NOI18N
					int ix = 1;
					while (fld.getFileObject(baseName + ix, "ser") != null) {
						ix++;
					}
					FileLock lock = null;
					try {
						FileObject writeTo = fld.createData(baseName + ix, "ser");
						lock = writeTo.lock();
						ObjectOutputStream str = new ObjectOutputStream(writeTo.getOutputStream(lock));
						str.writeObject(f);
					} catch (IOException ioe) {
						ErrorManager.getDefault().notify(ioe);
					} finally {
						if (lock != null)
							lock.releaseLock();
					}
				} catch (MalformedURLException ex) {
					IllegalArgumentException iae = new IllegalArgumentException(NbBundle.getMessage(RssNode.class,
						"FN_askurl_err", urlString), ex); //NOI18N
					throw iae;
				}
			}
		}
	}
}

很多代码都标有下划线,原因是您没有声明它们的包。下一步将声明这些包。现在,采用以下步骤重新格式化该文件并声明其依存关系:

  1. 按 Ctrl-Shift-F 格式化该代码。
  2. 按 Alt-Shift-F 并选择以下 import 语句:

    org.openide.actions.OpenAction
    javax.swing.Action
    org.openide.nodes.Children
    org.openide.filesystems.FileLock
    java.awt.Image
    org.openide.nodes.Node
    org.openide.ErrorManager
    org.openide.util.Utilities
    java.net.URL
    org.openide.filesystems.Repository
    java.beans.IntrospectionException

  3. 单击 OK,IDE 便将很多 import 语句添加到包语句下面。现在所有红色下划线都已消失。如果没有消失,则继续本教程,直到解决该问题为止。

本地化 RssNode 类

  1. 打开 FeedReader 模块的 Bundle.properties 文件。
  2. 添加以下键值对:

    FN_title=RSS/Atom Feeds
    FN_addbutton=Add
    FN_askurl_title=New Feed
    FN_askurl_msg=Enter the URL of an RSS/Atom Feed
    FN_askurl_err=Invalid URL: {0}|
    FN_addfolderbutton=Add Folder
    FN_askfolder_msg=Enter the folder name
    FN_askfolder_title=New Folder

下面是对新键值对的解释,这些键值对本地化 RssNode.java 中定义的字符串:

  • FN_title。 本地化 Feed Window 中最高节点的标签。

用于添加 feed 的用户界面的本地化:

  • FN_addbutton。 本地化出现在最高节点的弹出菜单中 Add 菜单项的标签。
  • FN_askurl_title. 本地化 New Feed 对话框的标题。
  • FN_askurl_msg。 本地化 New Feed 对话框中出现的消息。
  • FN_askurl_err。 本地化当 URL 无效时显示的错误字符串。

用于添加文件夹的用户界面的本地化:

  • FN_addfolderbutton。 本地化出现在最高节点的弹出菜单中 Add Folder 菜单项的标签。
  • FN_askfolder_msg。 本地化 Add Folder 对话框中出现的消息。
  • FN_askfolder_title。 本地化 Add Folder 对话框的标题。

标记应用程序

现在到了开发周期的结尾部分,包装应用程序时注意以下问题:

  • 该应用程序的可执行文件的名称是什么?
  • 当启动我的应用程序时用户应该看到什么?进度条?还是程序启动画面?还是两者都有?
  • 当我的应用程序启动时,标题栏中应该显示什么?
  • 我真的需要 NetBeans 平台默认情况下提供的所有菜单和工具栏吗?

这些问题都与标记以及对 NetBeans Platform 上构建的应用程序进行个性化的活动有关。IDE 在模块套件项目的 Project Properties 对话框中提供了一个面板,用来帮助您进行标记。

  1. 右键单击 feedreader-suite 项目节点(不是 FeedReader 项目节点),然后选择 Properties。在 Project Properties 对话框中,单击 Application。
  2. 在 Application 面板中,单击 Create Standalone Application。IDE 提示您排除与 IDE 有关的模块。单击 Exclude,因为 FeedReader 应用程序不需要与 IDE 有关的模块,比如在 NetBeans IDE 中提供 Source Editor 的那些模块。
  3. 在 Branding Name 中,键入 feedreader。在应用程序标题中,键入 Feed Reader Application。标记名称中的值设置可执行文件的名称,而应用程序标题中的值设置应用程序的标题栏。
  4. 单击 Browse 浏览到 rss16.gif 图标。该图标将显示在 Help > About 对话框中。
  5. 在 Splash Screen 面板中,单击 Browse 浏览到 splash.gif。或者,更改进度条的颜色和文本大小。如果您不希望出现进度条,则取消选择 Enabled。
  6. 单击 OK。
    FeedReader Application 项目中便创建了 branding 文件夹。在 Files 窗口中可以看到该文件夹 (Ctrl-2)。
  7. 在 Files 窗口中,展开 FeedReader Application 项目节点。然后继续扩展节点,直到您发现这个节点:
    branding/modules/org-netbeans-core-window.jar/org/netbeans/core/windows
  8. 右键单击节点并选择 New > File/Folder,然后在 Other 类别中选择 Folder。单击 Next 并将该文件夹命名为 resources。单击 Finish。
  9. 右键单击新的 resources 节点,选择 New > File/Folder,然后在 XML 类别中选择 XML Document。单击 Next。将该文件命名为 layer。单击 Next,然后单击 Finish。用以下内容替换新 layer.xml 文件的内容:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE filesystem PUBLIC "-//NetBeans//DTD Filesystem 1.1//EN" "http://www.netbeans.org/dtds/filesystem-1_1.dtd">
<!--
This is a `branding' layer.  It gets merged with the layer file it's branding.
In this case, it's just hiding menu items and toolbars we don't want.
-->
<filesystem>

	<!-- hide unused toolbars -->
	<folder name="Toolbars">
		<folder name="File_hidden"/>
		<folder name="Edit_hidden"/>
	</folder>

	<folder name="Menu">
		<folder name="File">
			<file name="org-openide-actions-SaveAction.instance_hidden"/>
			<file name="org-openide-actions-SaveAllAction.instance_hidden"/>
			<file name="org-netbeans-core-actions-RefreshAllFilesystemsAction.instance_hidden"/>            
			<file name="org-openide-actions-PageSetupAction.instance_hidden"/>
			<file name="org-openide-actions-PrintAction.instance_hidden"/>
		</folder>
		<folder name="Edit_hidden"/>
		<folder name="Tools_hidden"/>
	</folder>

</filesystem>

分发应用程序

IDE 使用 Ant 构建脚本创建应用程序的分发。构建脚本是创建项目时为您创建的。

  1. 在 Projects 窗口中,右键单击 FeedReader Application 项目节点,然后选择 Build ZIP Distribution。Output 窗口向您显示创建 ZIP 分发的位置。
  2. 在您的文件系统中,在项目目录中的 dist 文件夹中查找 feedreader.zip 分发。对其进行解压缩。启动您将在 bin 文件夹中找到的应用程序。启动过程中会显示程序启动画面。当应用程序已启动时,请转到 Help > About 对话框,并注意到您在标记应用程序部分中指定的图标和程序启动画面。

当应用程序启动并运行时,FeedReader 应用程序显示 RSS/Atom Feeds 窗口,其中包含名为 RSS/Atom Feeds 的节点。请参阅使用应用程序以获得详细信息。

祝贺您!您已经完成了 FeedReader 教程。