Почему не удается получить n-й дочерний узел Node в прослушивателе ExplorerManager? - PullRequest
0 голосов
/ 26 октября 2011

У меня проблемы с API-интерфейсом узлов NetBeans.

У меня есть эта строка кода:

Node n = (new MyNode(X)).getChildren().getNodeAt(Y);

Вызов new MyNode(X) с тем же X всегда инициализирует MyNode одинаково, независимо от контекста.

Когда я помещаю его сам (скажем, в действие меню), он успешно получает Y th потомок, , но , если я помещаю его в событие, когда происходит другой материал Node / Children, возвращается null.

Реализация MyNode's Children является тривиальным подклассом Children.Keys, который приблизительно равен:

// 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, и я попытался пройти через это, но они настолько запутаны, что я еще ничего не понял.

IDE NetBeans 7.0.1 (сборка 201107282000) с последними подключаемыми модулями.

Редактировать: Подробнее

Строка со странным поведением находится внутри обработчика для изменения свойства selected-узлов ExplorerManager. Странно то, что он все еще не работает, когда экземпляр MyNode находится не в иерархии, которую использует ExplorerManager (это даже не тот же класс, что и узлы в ExplorerManager), и не используется ни для чего другого .

Доступ к узлам вместо базовой модели фактически необходим для моего варианта использования (мне нужно что-то сделать с помощью PropertySets), пример MyNode - просто более простой случай, в котором все еще есть проблема.

Ответы [ 2 ]

4 голосов
/ 26 октября 2011

Рекомендуется использовать org.openide.nodes.ChildFactory для создания дочерних узлов, если у вас нет особой необходимости использовать один из Children API.Но для общих случаев достаточно ChildFactory.

При использовании Nodes API следует иметь в виду, что только оболочка вашей модели, которая используется в сочетании с API-интерфейсом Explorer, делает его доступным для различных компонентов представления в платформе 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, который будет находиться в TopComponent, который реализует org.openide.explorer.ExplorerManager.Provider:

 // 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 - это уровень представления и используется в качестве адаптера между вашей моделью и вашимviews.

Когда это становится мощной техникой, используется один и тот же ChildFactory в разных и разных видах.Вы можете использовать приведенный выше код во многих TopComponents без каких-либо изменений.Вы также можете использовать FilterNode, если вам нужно изменить только часть представления узла, не касаясь исходного узла.

Изучение API узлов является одним из наиболее сложных аспектов изученияAPI платформы NetBeans, как вы, несомненно, обнаружили.Как только вы овладеете этим API, вы сможете использовать гораздо больше встроенных возможностей платформ.

Для получения дополнительной информации об API-узлах см. Следующие ресурсы:

1 голос
/ 26 октября 2011

Тимон Веенстра в списке рассылки для разработчиков платформы NetBeans решил это за меня .

Действия в explorerManager охраняются для обеспечения согласованности. слушатель выбора узла в диспетчере проводника, например, не может манипулировать тем же менеджером проводника во время обработки выбора изменилось событие, потому что для этого потребуется чтение для обновления записи. изменение будет наложено вето и умрет тихой смертью.

Добавляете ли вы корневой узел MyNode в менеджер проводника на инициализация или где-то еще в слушателе?

Моя проблемная строка находится в слушателе изменения выбора ExplorerManager. Я полагаю, что блокировка Children.MUTEX устанавливается ExplorerManager и не позволяет экземпляру Children.Keys заполнять его узлы ...?

В любом случае, я переместил свой доступ Node в EventQueue.invokeLater (...), так что он выполняется после завершения события с измененным выбором, и это исправило его.

...