本文来源:https://netbeans.apache.org/tutorials/60/nbm-selection-1.html
如果新手入门,建议先去看Netbeans入门
Selection介绍
对于任何非平凡的应用程序,“Selection”都是重要的概念。NetBeans具有两个基本的选择概念Topcomponent的Lookup和activied node。在这里,您将只处理选择的Lookup部分-稍后的教程将介绍更多高级的操作。
选择用于使诸如上下文相关的动作(取决于显示的内容是启用还是禁用的动作)以及诸如IDE中的Property Sheet或Navigator components组件之类的palette窗口成为可能,它们分别显示所选内容的某些方面。
基本上,每个TopComponent
对象都有一袋可以放入东西的对象,以及可以查询其他代码的对象。那个对象包就是它的Lookup —本质上是一个Map,其中键是类对象,值是扩展或实现键类的对象。使该方法非常有用的是使用这种机制来分离提供某些对象的组件和使用这些对象的组件的能力,因此它们可以在单独的模块中实现,或者可以提供旧对象的新编辑器,并且系统的其余部分将继续透明地工作。
创建模块套件和项目
本教程的示例将包含一个模块套件中包含的三个模块。
首先创建module suite以包含所有三个modules:
1、右键新建项目,在类别下选择NetBeans模块,在projects选择Module Suite,点击下一步
项目名称输入SelectionSuite,点击finish
2、在该应用下创建模块,项目名称输入MyAPI
下一步
3、code name base(代码基地)输入org.myorg.myapi,Module Dispaly Name(模块显示名称)输入My API
4、创建另外两个模块,遵循上述相同步骤,名称使用MyEditor和MyViewer
创建api并设置依赖项
1、在My API模块,右键点击org.myorg.myapi包,新建一个java class,命名为APIObject
package org.myorg.myapi;
import java.util.Date;
public final class APIObject {
private final Date date = new Date();
private static int count = 0;
private final int index;
public APIObject() {
index = count++;
}
public Date getDate() {
return date;
}
public int getIndex() {
return index;
}
public String toString() {
return index + " - " + date;
}
}
2、让api模块公开org.myorg.myapi包,使其他模块可以看到其中的class,右键项目—选择属性—API版本
勾选org.myorg.api复选框,点击OK
现在,建立一些模块之间的依赖关系。让其他2个模块使用APIObject类。依次右键项目属性,在Libararies中Add dependency(添加依赖),输入My API,选中点击OK
创建Viewer Component
现在,您将创建一个单例组件,该组件将跟踪APIObject
全局选择项中是否有可用项(即,如果焦点显示TopComponent
在其Lookup中)。如果有一个,它将显示一些有关它的数据。这种事情的一个常见用例是创建主视图/详细视图。
singleton component是一个组件,例如NetBeans IDE中的“项目”窗口,或者“属性表”或“导航器”,即系统中只有一个组件。“窗口组件”向导将自动生成创建此类单例组件所需的所有代码-您只需使用表单设计器或编写代码来提供单例组件的内容。
1、右键单击该org.myorg.myviewer
软件包,然后选择“新建”>“其他”。
2、在出现的对话框中,选择Module Development > window,下一步
3、在window Postition(窗口位置)选择navigator,作为查看器组件,在复选框中点击启动打开
4、点击下一步输入类名称:MyViewer
5、在MyViewerTopComponent | Design 添加2个label
5、点击source切换到代码编辑器,实现LookupListener
public class MyViewerTopComponent extends TopComponent implements LookupListener {
右键fix import(修复导入)可以实现import的引入
Alt+Enter 可以给出错误提示
6、您现在有了一个实现的类LookupListener
。现在它需要监听些什么。在您的情况下,有一个方便的全局Lookup对象,它可以简单地代理具有焦点的任何组件的Lookup -可以使用Utilities.actionsGlobalContext()
得到call的焦点
。因此,您不必简单地跟踪哪个组件具有焦点,而是可以简单地侦听此全局选择查找,该查找将在焦点发生更改时触发适当的更改。编辑源代码,使其包含以下方法,在关闭MyViewer的时候,清除监听焦点事件。
private Lookup.Result result = null;
public void componentOpened() {
Lookup.Template tpl = new Lookup.Template (APIObject.class);
result = Utilities.actionsGlobalContext().lookup(tpl);
result.addLookupListener (this);
}
public void componentClosed() {
result.removeLookupListener (this);
result = null;
}
public void resultChanged(LookupEvent lookupEvent) {
Lookup.Result r = (Lookup.Result) lookupEvent.getSource();
Collection c = r.allInstances();
if (!c.isEmpty()) {
APIObject o = (APIObject) c.iterator().next();
jLabel1.setText (Integer.toString(o.getIndex()));
jLabel2.setText (o.getDate().toString());
} else {
jLabel1.setText("[no selection]");
jLabel2.setText ("");
}
}
componentOpened()
每当窗口系统使该组件可见时,就会调用;
componentClosed()
每当用户单击其选项卡上的X按钮以将其关闭时,就会调用。因此,无论何时显示组件,您都希望它跟踪选择—这就是上面的代码所做的。该
resultChanged()
方法是您对的实现LookupListener
。每当选定的APIObject
更改发生时,它将更新您放在表单上的两个`JLabel`。
lookupEvent.getSource()是获取当前焦点的object组件对象
创建Editor Component
现在,您需要一些东西来实际提供的实例APIObject
,此代码才有用。
你可以再次使用“window”模板,但是该模板是用于创建单例组件,而不是创建许多组件的模板。
因此,创建一个TopComponent
没有模板的子类,并创建一个将打开其他子类的操作。
1、在My Editor模块上添加3个依赖项,右键点击My Editor项目,选择属性,在项目属性的库页面上,点击添加依赖项,输入TopComponent。选择对Window System API的依赖关系。对Lookups
(Utilities API)执行相同的操作。
2、右键单击org.myorg.myeditor项目中的package,然后选择新建> JPanel From
3、命名为MyEditor,并完成向导。
4、表单编辑器打开后,将两个JTextField放在表单上,一个放在另一个之上。在属性表上的enable属性(复选框)设置为false
。
5、点击编辑器中的Source按钮切换到代码编辑器。
如果org.openide.util.NbBundle.getMessage报错,搜索NbBundle,把依赖添加进去即可。
6、将继承的类javax.swing.JPanel改为TopComponent
public class MyEditor extends TopComponent {
7、将以下代码添加到的构造函数中MyEditor
public MyEditor() {
initComponents();
APIObject obj = new APIObject();
associateLookup(Lookups.singleton(obj));
jTextField1.setText("APIObject #" + obj.getIndex());
jTextField2.setText("Created: " + obj.getDate());
setDisplayName("MyEditor " + obj.getIndex());
}
如果出现找不到的类,继续查找依赖导入。
该行将associateLookup (Lookups.singleton (obj));
创建一个Lookup的单例对象并创建一个APIObject的实例,并将APIObject设置为查找MyEditor.getLookup()
返回的对象。
MyEditor
构造方法有2个意思:
1、初始化了一个
APIObject对象。
2、设置了MyEditor.
getLookup()的返回值,值是APIObject对象。
以下代码就是解释这段文字意思
MyEditor t2 = (MyEditor)WindowManager.getDefault().findTopComponent("MyEditor");
APIObject api = t2.getLookup().lookup(APIObject.class);
System.out.println(t2);
//以上代码等同于resultChanged里面获取值的方法
打开Editor Components
创建一个MyEditor打开组件的方法,显示内容
1、右键点击
org.myorg.myeditor的package,新建 > 其他
2、在对话框中选择,模块开发 > Action,下一步
3、选择默认设置(always enabled),下一步
4、在GUI Registration page,点击下一步,接受默认设置。
5、为Action命名OpenEditorAction,显示名称设置为Open Editor,点击完成
6、打开OpenEditorAction代码编辑器,该类实现了ActionListener接口,将以下代码添加到performAction()方法
@Override
public void actionPerformed(ActionEvent e) {
MyEditor editor = new MyEditor();
editor.open();
editor.requestActive();
}
如果editor.open()报错,搜索依赖HelpCtx导入。
运行代码
右键点击SelectionSuite项目,她拥有三个模块,点击Run(运行)。打开IDE后,选择File(文件)>Open Editor,可以打开刚才创建的editor,不同的选项卡,内容不同。
如果在非Editor的地方单击,左下角window文本变为[No Selection]
如果没有看到MyViewer Window,在菜单栏Window--选择MyViewer打开显示。
那么,重点是什么?
刚刚证明了多模块开发的方式:
MyViewer模块对MyEditor模块一无所知,任意一个模块都可以自己运行,这意味着他们可以独立开发和交付;并且类似MyEditor不同类型的editor模块都可以这样做,viewer模块只要替换提供APIOject的实例,就可以正常运行
如果有一个比较复杂的项目,Myeditor是图形编辑器,而APIObject代表编辑的模型,你可以替换MyEditor的编辑器,模型可以与新编辑器工作,还可以使用Netbaenas创建的java文件运行,并且他们可以在不同的Netbeans版本中运行(比如表单编辑器),所有与Java文件兼容的组件和Action仍然有效。
这就是Netbeans与Java一起工作的方式,在这种情况下,
editor的Lookup可以使用DataObject
而像导航器和属性表这样的组件,则需要查找TopComponent
这种方法的重要之处在于,数据模型对象可能是现有的有效代码,不应该更改这些代码,而直接集成到Netbeans中。通过数据模型的API保留在单独的模块中,可以将Netbeans集成与核心业务与逻辑分开。
快速更改选定的对象
为了真正证明这种方法的强大,您将再进一步,在编辑器组件中添加一个按钮,动态更新APIObject
1、MyEditor 在编辑器打开设计,添加一个JButton
2、按钮更名为替换
3、双击按钮,会自动添加按钮的Action事件
4、在类定义的开头,添加一行
private final InstanceContent content = new InstanceContent();
InstanceContent是一个类,它允许我们修改一个Lookup的内容 (特别是AbstractLookup实例)
5、将之前添加到构造函数的所有行复制到剪贴板,然后删除他们,添加以下一行
public MyEditor() {
initComponents();
associateLookup (new AbstractLookup (content));
// APIObject obj = new APIObject();
// associateLookup(Lookups.singleton(obj));
// jTextField1.setText("APIObject #" + obj.getIndex());
// jTextField2.setText("Created: " + obj.getDate());
// setDisplayName("MyEditor " + obj.getIndex());
}
6、修改按钮点击事件代码
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
APIObject obj = new APIObject();
jTextField1.setText("APIObject #" + obj.getIndex());
jTextField2.setText("Created: " + obj.getDate());
setDisplayName("MyEditor " + obj.getIndex());
content.set(Collections.singleton(obj), null);
}
7、右键编辑器,修复导入。
再次右键点击SelectionSuite项目,运行。当点击“替换”按钮时,MyViewer组件才会更新
提供多个对象
这对解耦是一件好事,但不是从组件中提供这个对象有点像Map
只包含一个键和一个值的对象吗?答案是,是的,就是这样。当您从多个API提供多个对象时,该技术变得更加强大。
例如,在NetBeans中提供上下文相关的动作是很常见的。一个典型的例子是SaveAction
NetBeans的Actions API 的内置函数。该操作实际上所做的是,它只是侦听SaveCookie
在全局上下文中存在的某些内容,就像查看器窗口侦听的方式一样APIObject
。如果SaveCookie
出现a (当文件内容被修改但尚未保存时,编辑者通常在其查找中添加一个),则该操作将被启用,因此Save工具栏按钮和菜单项将被启用。调用“保存”操作时,它会调用SaveCookie.save()
,从而导致SaveCookie
消失,因此“保存”操作将被禁用,直到出现新的操作为止。
因此,在实践中,组件的组件中的查找并不是提供单个对象,不同的附加component和不同的action会对被编辑的对象不同。
这些方面可以划分接口,附加的component和action依赖这些接口,并监听它们。
值得注意的杂项
如果打开三个MyEditor的实例窗口,然后关闭并重启Netbeans,最终会MyEditor在重启新启动时神奇的出现三个实例。默认情况下,编辑器会在关闭时序列化到磁盘,并在重新启动时恢复。
如果不喜欢出现这种情况,则有2个其他选择。覆盖以下方法,MyEditor使编辑器重启时永远不会重新打开
public int getPersistenceType() {
return PERSISTENCE_NEVER;
}
如果要保留已打开的组件,而放弃已关闭的组件,请返回PERSISTENCE_ONLY_OPENED,默认值(考虑到向后兼容性)是PERSISTENCE_ALWAYS,这意味着即使是已经关闭的editor也不会永久保留,并在重启时重新加载。
但是请注意,序列化到磁盘的部分是组件在主窗口中的位置。所以单例的topcomponent,比如property sheet(属性表),或者viewer component(视图器组件),应该使用PERSISTENCE_ALWAYS ,否则如果他们被用户关闭一次,下次打开它们时,他们将会出现在editor区域,而不是他们应该出现的位置。
可选清理
默认情况下,模块模板假定您将要使用该layer.xml
文件来安装对象。对于My API模块,实际上不使用它。因此,为了稍微缩短启动时间而要做的一件有礼貌的事情如下:
1、选中My API项目,按Ctrl+2,进入Files窗口,
2、展开,进入manifest.mf文件
3、删除以下行
OpenIDE-Module-Layer: org/myorg/myapi/layer.xml
4、然后删除对应的package下的这个文件
在Netbeans v8后,这个文件已经默认不创建了
下一步
您可能已经注意到一些组件具有更细力度的选择逻辑,设置涉及多个选择,在下一篇教程中,将介绍如何使用Node API来处理这个问题。