我的NetBeans节点API有问题。
我有这行代码:
Node n = (new MyNode(X)).getChildren().getNodeAt(Y);
使用相同的X
调用new MyNode(X)
总是以相同的方式初始化MyNode,与上下文无关。
当我把它自己(说,在一个菜单动作),它成功地获得Y
的子,,但如果我把它放在一个事件中,其他节点/子的东西发生,它返回null
。
MyNode的Children实现是Children的一个平凡子类。键,大约是:
// Node
import org.openide.nodes.AbstractNode;
class MyNode extends AbstractNode {
MyNode(MyKey key) {
super(new MyNodeChildren(key));
}
}
// Children
import java.util.Collections;
import org.openide.nodes.Children;
import org.openide.nodes.Node;
public class MyNodeChildren extends Children.Keys<MyKey> {
MyKey parentKey;
MyNodeChildren(MyKey parentKey) {
super(true); // use lazy behavior
this.parentKey = parentKey;
}
@Override
protected Node[] createNodes(MyKey key) {
return new Node[] {new MyNode(key)};
}
@Override
protected void addNotify() {
setKeys(this.parentKey.getChildrenKeys());
}
@Override
protected void removeNotify() {
setKeys(Collections.EMPTY_SET);
}
}
// MyKey is trivial.
我认为这与Children.Keys
的懒惰行为有关。我有API的源代码,我已经试着逐步完成它,但是它们太令人困惑了,我还没有弄清楚任何东西。
NetBeans IDE 7.0.1 (Build 201107282000)与最新的插件。
Edit:更多细节
有奇怪行为的那行是在ExplorerManager选定节点属性更改的处理程序中。奇怪的是,当MyNode实例不在ExplorerManager正在使用的层次结构中(它甚至不是与ExplorerManager中的节点相同的类),并且不用于其他任何东西时,它仍然不起作用。
访问节点而不是底层模型对于我的用例来说实际上是必要的(我需要用PropertySets做一些事情),MyNode示例只是一个更简单的案例,仍然存在问题。
建议使用org.openide.nodes.ChildFactory
来创建子节点,除非您特别需要使用Children
api之一。但是对于一般情况,ChildFactory
是足够的。
在使用Nodes API时要记住的一件事是,它只是一个包装模型的表示层,并且与Explorer API一起使用,使其可用于NetBeans平台中的各种视图组件,例如org.openide.explorer.view.BeanTreeView
。
使用一个名为MyModel
的模型,它可能看起来像:
public class MyModel {
private String title;
private List<MyChild> children;
public MyModel(List<MyChild> children) {
this.children = children;
}
public String getTitle() {
return title;
}
public List<MyChild> getChildren() {
return Collections.unmodifiableList(children);
}
}
您可以创建一个ChildFactory<MyModel>
,它将负责创建您的节点:
public class MyChildFactory extends ChildFactory<MyModel> {
private List<MyModel> myModels;
public MyChildFactory(List<MyModel> myModels) {
this.myModels = myModels;
}
protected boolean createKeys(List<MyModel> toPopulate) {
return toPopulate.addAll(myModels);
}
protected Node createNodeForKey(MyModel myModel) {
return new MyNode(myModel);
}
protected void removeNotify() {
this.myModels= null;
}
}
然后,实现MyNode
,这是表示层并包装MyModel
:
public class MyNode extends AbstractNode {
public MyNode(MyModel myModel) {
this(myModel, new InstanceContent());
}
private MyNode(MyModel myModel, InstanceContent content) {
super(Children.create(
new MyChildrenChildFactory(myModel.getChildren()), true),
new AbstractLookup(content)); // add a Lookup
// add myModel to the lookup so you can retrieve it latter
content.add(myModel);
// set the name used in the presentation
setName(myModel.getTitle());
// set the icon used in the presentation
setIconBaseWithExtension("com/my/resouces/icon.png");
}
}
现在是MyChildrenChildFactory
它和MyChildFactory
非常相似除了它取一个List<MyChild>
然后创建MyChildNode
:
public class MyChildFactory extends ChildFactory<MyChild> {
private List<MyChild> myChildren;
public MyChildFactory(List<MyChild> myChildren) {
this.myChildren = myChildren;
}
protected boolean createKeys(List<MyChild> toPopulate) {
return toPopulate.addAll(myChildren);
}
protected Node createNodeForKey(MyChild myChild) {
return new MyChildNode(myChild);
}
protected void removeNotify() {
this.myChildren = null;
}
}
然后是MyChildNode
的实现,它与MyNode
非常相似:
public class MyChildNode extends AbstractNode {
public MyChildNode(MyChild myChild) {
// no children and another way to add a Lookup
super(Children.LEAF, Lookups.singleton(myChild));
// set the name used in the presentation
setName(myChild.getTitle());
// set the icon used in the presentation
setIconBaseWithExtension("com/my/resouces/child_icon.png");
}
}
我们需要儿童模型,MyChild
,它与MyModel
非常相似:
public class MyChild {
private String title;
public String getTitle() {
return title;
}
}
最后将其全部使用,例如BeanTreeView
将驻留在实现org.openide.explorer.ExplorerManager.Provider
的TopComponent
中:
// somewhere in your TopComponent's initialization code:
List<MyModel> myModels = ...
// defined as a property in you TC
explorerManager = new ExplorerManager();
// this is the important bit and we're using true
// to tell it to create the children asynchronously
Children children = Children.create(new MyChildFactory(myModels), true);
explorerManager.setRootContext(new AbstractNode(children));
请注意,您不需要触摸BeanTreeView
,实际上它可以是平台中包含的任何视图组件。这是创建节点的推荐方法,正如我所述,节点的使用是作为平台中包含的各种组件中使用的表示层。
如果你需要得到一个孩子,你可以使用ExplorerManager
,你可以从TopComponent
中检索使用方法ExplorerManager.Provier.getExplorerManager()
,这是由于你的TopComponent
实现了ExplorerManager.Provider
,实际上是视图组件本身获得节点的方式:
ExplorerManager explorerManager = ...
// the AbstractNode from above
Node rootContext = explorerManager.getRootContext();
// the MyNode(s) from above
Children children = rootContext.getChildren().getNodes(true);
// looking up the MyModel that we added to the lookup in the MyNode
MyModel myModel = nodes[0].getLookup().lookup(MyModel.class);
但是,您必须意识到使用Children.getNodes(true)
方法获取节点将导致创建所有节点及其子节点;由于我们告诉工厂我们希望它异步创建子元素,所以没有创建子元素。这不是推荐的访问数据的方式,相反,您应该保留对List<MyModel>
的引用,并尽可能使用它。Children.getNodes(boolean)
的文档:
…一般来说,如果您试图通过调用此方法获得有用的数据,那么您可能做错了什么。通常,您应该向一些底层模型询问信息,而不是向子节点询问。
同样,您必须记住,Nodes API是一个表示层,用作模型和视图之间的适配器。
当在不同的和不同的视图中使用相同的ChildFactory
时,这成为一个强大的技术。您可以在许多TopComponents
中重用上述代码,而无需进行任何修改。如果您只需要更改节点表示的一部分,而不必触及原始节点,您也可以使用FilterNode
。
有关node API的更多信息,请参阅以下参考资料:
- NetBeans Nodes API教程
- 关于node API的伟大介绍,作者:Antonio Vieiro
- 第5部分:节点API和资源管理器属性表API/Geertjan Wielenga
- JavaDocs for Nodes API
NetBeans平台开发者邮件列表上的Timon Veenstra为我解决了这个问题。
保护explorerManager上的操作以确保一致性。一个例如,资源管理器上的节点选择侦听器不能在处理选择时操作相同的资源管理器已更改事件,因为这将需要从读到写的升级。的更改将被否决,然后无声地死去。
是否将MyNode根节点添加到资源管理器初始化,还是在侦听器的其他地方?
我的问题行是在一个ExplorerManager选择更改监听器。我猜是孩子们。互斥锁正在被ExplorerManager设置并阻止子线程。键实例从填充其节点…?
无论如何,我将我的节点访问移动到EventQueue.invokeLater(…)中,因此它在选择更改事件完成后执行,并且修复了它。