NetBeans Project Type Module Tutorial

Last reviewed on 2022-01-04

This tutorial demonstrates how to create a new project type in a NetBeans Platform application.

Before going further, make sure this is the tutorial you actually need!

You will also make use of these icons, which you can right-click here and download: projecttypes 72pics icon and projecttypes 72pics sub icon

For troubleshooting purposes, you are welcome to download the complete tutorial source code.

Introduction to Project Types

A project type is a NetBeans Platform term for a grouping of folders and files that is treated as a single unit. Treating related folders and files as a single unit makes working with them easier for the end user. One way in which a project type simplifies life for the user is that you are able to fill the Projects window only with those folders and files that the end user is most likely to work. For example, the Java project type in NetBeans IDE helps the end user to work with the folders and files belonging to a single Java application.

Our project type will be defined by the existence of a file named "customer.txt". The tutorial assumes you have available, on disk, multiple folders containing such a file, for example as illustrated below:

projecttypes 72pics result 1

As in the case of the folders named "customer1", "customer2", and "customer3" above, if a folder contains a file named "customer", with a "txt" extension, the NetBeans Platform will recognize the folder as a project. The user will be able to open the project into a NetBeans Platform application. The user will also be able to create new projects, via the New Projects window (Ctrl-Shift-N), which is where we will register some sample projects.

The following are the main NetBeans API classes we will be implementing in this tutorial:

Class Description

org.netbeans.spi.project.ProjectFactory

Determines when a folder or file is a valid project and then creates the implemention of org.netbeans.api.project.Project .

org.netbeans.api.project.Project

Represents the project.

org.netbeans.spi.project.ui.LogicalViewProvider

Provides the logical view for the project.

org.netbeans.api.project.ProjectInformation

Provides supplemental information for the project.

org.netbeans.spi.project.ActionProvider

Provides one or more actions for the project.

org.netbeans.spi.project.CopyOperationImplementation

Provides the Copy operation for the project.

org.netbeans.spi.project.DeleteOperationImplementation

Provides the Delete operation for the project.

Creating the Module Project

We begin by working through the New Module Project wizard. At the end of it, we will have a basic source structure, with some default files, that every NetBeans module requires.

  1. Choose File > New Project (Ctrl+Shift+N). Under Categories, select NetBeans Modules. Under Projects, select Module. Click Next.

  1. In the Name and Location panel, type CustomerProjectType in the Project Name field. Change the Project Location to any directory on your computer.

projecttypes 72pics proj 1

Click Next.

  1. In the Basic Module Configuration panel, type org.customer.project in Code Name Base.

projecttypes 72pics proj 2

Click Finish.

The IDE creates the CustomerProjectType project. The project contains all of your sources and project metadata, such as the project’s Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl-1) and its file structure in the Files window (Ctrl-2).

Setting Dependencies

We will need to make use of several NetBeans APIs. In this step, we select the modules that provide the NetBeans APIs that we will need.

  1. Right-click the project’s Libraries node and choose "Add Module Dependency". Select the following modules and click OK:

    • Common Annotations

    • Datasystems API

    • Dialogs API

    • File System API

    • Lookup API

    • Nodes API

    • Project API

    • Project UI API

    • UI Utilities API

    • Utilities API

  1. Expand the Libraries node and check that the following dependencies have been set in the previous step:

projecttypes 72pics proj 3

Creating the Project Factory

We start by implementing the org.netbeans.spi.project.ProjectFactory class.

  1. Create a Java class named CustomerProjectFactory .

  1. Change the default code to the following:

import java.io.IOException;
import org.netbeans.api.project.Project;
import org.netbeans.spi.project.ProjectFactory;
import org.netbeans.spi.project.ProjectState;
import org.openide.filesystems.FileObject;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service=ProjectFactory.class)
public class CustomerProjectFactory implements  ProjectFactory {

    public static final String PROJECT_FILE = "customer.txt";

    *//Specifies when a project is a project, i.e.,
    //if "customer.txt" is present in a folder:*
    @Override
    public boolean isProject(FileObject projectDirectory) {
        return projectDirectory.getFileObject(PROJECT_FILE) != null;
    }

    *//Specifies when the project will be opened, i.e., if the project exists:*
    @Override
    public Project loadProject(FileObject dir,  ProjectState state) throws IOException {
        return isProject(dir) ? new CustomerProject(dir, state) : null;
    }

    @Override
    public void saveProject(final Project project) throws IOException, ClassCastException {
        // leave unimplemented for the moment
    }

}
The @ServiceProvider annotation used in the class signature above will cause a META-INF/services file to be created when the module is compiled. Within that folder, a file named after the fully qualified name of the interface will be found, containing the fully qualified name of the implementing class. That is the standard JDK mechanism, since JDK 6, for registering implementations of interfaces. That is how project types are registered in the NetBeans Plaform.

Instead of ProjectFactory , consider implementing the newer ProjectFactory2 . ProjectFactory2 is a performance correction to ProjectFactory , done in a compatible way. If you implement ProjectFactory2 , the project will not need to be loaded, which can take some time, especially in populating the Lookup, and the project icon appears fast in the Open Project dialog. If you implement only ProjectFactory , more memory is consumed and projects are loaded even if not used or opened in the end. The main effective place to see the difference visually is when you have many projects in a single folder. The pattern itself is fairly common in the Eclipse world, for example. Interfaces are extended as InterfaceExt, InterfaceExt2, InterfaceExt3, etc. The general idea is that typically you should always implement the last extension to the base interface. But the core codebase dealing with the interfaces can handle all of the variants.

Creating the Project

Next, we implement the org.netbeans.api.project.Project class.

  1. Create a Java class named CustomerProject .

  1. We’ll start with a simple skeleton implementation:

import org.netbeans.api.project.Project;
import org.netbeans.spi.project.ProjectState;
import org.openide.filesystems.FileObject;
import org.openide.util.Lookup;

public class CustomerProject implements  Project {

    CustomerProject(FileObject dir, ProjectState state) {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public FileObject getProjectDirectory() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Lookup getLookup() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

}

The getLookup method, in the code above, is the key to the NetBeans project infrastructure. When you create new features for a project type, such as its logical view, its popup actions, or its customizer, you register them in the project via its getLookup method.

  1. Let’s set up our project class so that we can start using it to register the project’s features. Fill out the class by setting fields and add code to the getLookup method to prepare it for the following sections.

import java.beans.PropertyChangeListener;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import org.netbeans.api.annotations.common.StaticResource;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.spi.project.ProjectState;
import org.openide.filesystems.FileObject;
import org.openide.util.ImageUtilities;
import org.openide.util.Lookup;
import org.openide.util.lookup.Lookups;

public class CustomerProject implements Project {

    private final FileObject projectDir;
    private final ProjectState state;
    private Lookup lkp;

    CustomerProject(FileObject dir, ProjectState state) {
        this.projectDir = dir;
        this.state = state;
    }

    @Override
    public FileObject getProjectDirectory() {
        return projectDir;
    }

    @Override
    public Lookup getLookup() {
        if (lkp == null) {
            lkp = Lookups.fixed(new Object[]{

            // register your features here

            });
        }
        return lkp;
    }

}
  1. Now let’s work on the features that we’d like our project to have. In each case, we define the feature and then we register the feature in the project’s Lookup.

Creating and Registering the Project Information

In this section, you register minimum NetBeans project support, that is, you create and register a class that provides an icon and a display name for the project.

  1. Put the icon.png file, referred to at the start of this tutorial, into the org.customer.project package.

  1. As an inner class of the CustomerProject class, define the project information as follows:

private final class Info implements  ProjectInformation {

@StaticResource()
    public static final String CUSTOMER_ICON = "org/customer/project/icon.png";

    @Override
    public Icon getIcon() {
        return new ImageIcon(ImageUtilities.loadImage(CUSTOMER_ICON));
    }

    @Override
    public String getName() {
        return getProjectDirectory().getName();
    }

    @Override
    public String getDisplayName() {
        return getName();
    }

    @Override
    public void addPropertyChangeListener(PropertyChangeListener pcl) {
        //do nothing, won't change
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener pcl) {
        //do nothing, won't change
    }

    @Override
    public Project getProject() {
        return CustomerProject.this;
    }

}
  1. Now register the ProjectInformation in the Lookup of the project as follows:

@Override
public Lookup getLookup() {
    if (lkp == null) {
        lkp = Lookups.fixed(new Object[]{

            *new Info(),*

        });
    }
    return lkp;
}
  1. Run the module. Your application starts up and your module is installed into it. Go to File | Open Project and, when you browse to folders containing a "customer.txt" file, notice that the folders are recognized as projects and show the icon you defined in the ProjectInformation class above:

projecttypes 72pics result 3

When you open a project, notice that all the folders and files in the project are shown in the Projects window and that, when you right-click on the project, several default popup actions are shown:

projecttypes 72pics result 2

Now that you can open folders as projects into your application, let’s work on the project’s logical view. The logical view is displayed in the Projects window. The Projects window typically only shows the most important files or folders that the user should work with, together with the related display names, icons, and popup actions.

Creating and Registering the Project Logical View

In this section, you define the logical view of your project, as shown in the Projects window of your application.

  1. As an inner class of the CustomerProject class, define the project logical view as follows:

class CustomerProjectLogicalView implements  LogicalViewProvider {

@StaticResource()
    public static final String CUSTOMER_ICON = "org/customer/project/icon.png";

    private final CustomerProject project;

    public CustomerProjectLogicalView(CustomerProject project) {
        this.project = project;
    }

    @Override
    public Node createLogicalView() {
        try {
            //Obtain the project directory's node:
            FileObject projectDirectory = project.getProjectDirectory();
            DataFolder projectFolder = DataFolder.findFolder(projectDirectory);
            Node nodeOfProjectFolder = projectFolder.getNodeDelegate();
            //Decorate the project directory's node:
            return new ProjectNode(nodeOfProjectFolder, project);
        } catch (DataObjectNotFoundException donfe) {
            Exceptions.printStackTrace(donfe);
            //Fallback-the directory couldn't be created -
            //read-only filesystem or something evil happened
            return new AbstractNode(Children.LEAF);
        }
    }

    private final class ProjectNode extends FilterNode {

        final CustomerProject project;

        public ProjectNode(Node node, CustomerProject project)
            throws DataObjectNotFoundException {
            super(node,
                    new FilterNode.Children(node),
                    new ProxyLookup(
                    new Lookup[]{
                        Lookups.singleton(project),
                        node.getLookup()
                    }));
            this.project = project;
        }

        @Override
        public Action[] getActions(boolean arg0) {
            return new Action[]{
                        CommonProjectActions.newFileAction(),
                        CommonProjectActions.copyProjectAction(),
                        CommonProjectActions.deleteProjectAction(),
                        CommonProjectActions.closeProjectAction()
                    };
        }

        @Override
        public Image getIcon(int type) {
            return ImageUtilities.loadImage(CUSTOMER_ICON);
        }

        @Override
        public Image getOpenedIcon(int type) {
            return getIcon(type);
        }

        @Override
        public String getDisplayName() {
            return project.getProjectDirectory().getName();
        }

    }

    @Override
    public Node findPath(Node root, Object target) {
        //leave unimplemented for now
        return null;
    }

}

Many project actions are available for you to use, as you can see from the code completion:

projecttypes 72pics proj 4
  1. As before, register the feature in the Lookup of the project:

@Override
public Lookup getLookup() {
    if (lkp == null) {
        lkp = Lookups.fixed(new Object[]{
                new Info(),
                *new CustomerProjectLogicalView(this),*
        });
    }
    return lkp;
}
  1. Run the module again and open a customer project again. You should see the following:

projecttypes 72pics result 4

The project node now shows the display name, icon, and popup actions that you defined.

Creating and Registering the Project Node Children

In this section, you learn how to define which folders and files should be displayed in the logical view, that is, the Projects window. Currently, you are showing all folders and files because the children of the project node are defined by FilterNode.Children(node) , which means "display all the children of the node".

  1. Change the constructor of the ProjectNode as follows:

public ProjectNode(Node node, CustomerProject project)
    throws DataObjectNotFoundException {
    super(node, NodeFactorySupport.createCompositeChildren(
                    project,
                    "Projects/org-customer-project/Nodes"),*
            // new FilterNode.Children(node),
            new ProxyLookup(
            new Lookup[]{
                Lookups.singleton(project),
                node.getLookup()
            }));
    this.project = project;
}
  1. Register the project in its own Lookup:

@Override
public Lookup getLookup() {
    if (lkp == null) {
        lkp = Lookups.fixed(new Object[]{
               *this,*
               new Info(),
               new CustomerProjectLogicalView(this),});
    }
    return lkp;
}
  1. Create a new Java class TextsNodeFactory in a new package org.customer.project.nodes as follows, while taking special note of the @NodeFactory.Registration annotation:

package org.customer.project.nodes;

import java.util.ArrayList;
import java.util.List;
import javax.swing.event.ChangeListener;
import org.customer.project.CustomerProject;
import org.netbeans.api.project.Project;
import org.netbeans.spi.project.ui.support.NodeFactory;
import org.netbeans.spi.project.ui.support.NodeList;
import org.openide.filesystems.FileObject;
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.Exceptions;

@NodeFactory.Registration(projectType = "org-customer-project", position = 10)
public class TextsNodeFactory implements  NodeFactory {

    @Override
    public NodeList<?> createNodes(Project project) {
        CustomerProject p = project.getLookup().lookup(CustomerProject.class);
        assert p != null;
        return new TextsNodeList(p);
    }

    private class TextsNodeList implements NodeList<Node> {

        CustomerProject project;

        public TextsNodeList(CustomerProject project) {
            this.project = project;
        }

        @Override
        public List<Node> keys() {
            FileObject textsFolder =
                project.getProjectDirectory().getFileObject("texts");
            List<Node> result = new ArrayList<Node>();
            if (textsFolder != null) {
                for (FileObject textsFolderFile : textsFolder.getChildren()) {
                    try {
                        result.add(DataObject.find(textsFolderFile).getNodeDelegate());
                    } catch (DataObjectNotFoundException ex) {
                        Exceptions.printStackTrace(ex);
                    }
                }
            }
            return result;
        }

        @Override
        public Node node(Node node) {
            return new FilterNode(node);
        }

        @Override
        public void addNotify() {
        }

        @Override
        public void removeNotify() {
        }

        @Override
        public void addChangeListener(ChangeListener cl) {
        }

        @Override
        public void removeChangeListener(ChangeListener cl) {
        }

    }

}
  1. Run the module again and open a customer project again. Make sure the project has a subfolder named "texts", with some content. You should see the following, that is, the content of the "texts" folder is shown in the Projects window, which exists to provide a logical view, while the Files window shows the complete folder structure:

projecttypes 72pics text folder 1

An important point to realize in this section is that the @NodeFactory.Registration annotation can be used to register new child nodes of the customer project node, either within the current module or via external modules. In this way, the logical view of your project is extensible, that is, logical views can be pluggable, if an extension point is created as part of its definition, as shown in step 1 of this section.

Creating and Registering the Project Customizer

In this section, you learn how to create a pluggable customizer. When the user right-clicks the project node, they will see a Properties menu item. When they click it, the customizer will open. The categories in the customizer can be contributed by external modules, that is, the customizer will be created to be extensible.

  1. Register the customizer action in the logical view of the project, as follows:

@Override
public Action[] getActions(boolean arg0) {
    return new Action[]{
                CommonProjectActions.newFileAction(),
                CommonProjectActions.copyProjectAction(),
                CommonProjectActions.deleteProjectAction(),
                *CommonProjectActions.customizeProjectAction(),*
                CommonProjectActions.closeProjectAction()
            };
}
  1. Run the module and right-click the project node. You should see that the Properties popup menu item is present, but disabled:

projecttypes 72pics customizer 1
  1. Register a skeleton customizer in the Lookup of the project:

@Override
public Lookup getLookup() {
    if (lkp == null) {
        lkp = Lookups.fixed(new Object[]{
                    this,
                    new Info(),
                    new CustomerProjectLogicalView(this),
                    *new CustomizerProvider() {
                        @Override
                        public void showCustomizer() {
                            JOptionPane.showMessageDialog(
                                    null,
                                    "customizer for " +
                                    getProjectDirectory().getName());
                        }
                    },*
        });
    }
    return lkp;
}
  1. Run the module again and right-click the project node. You should see that the Properties popup menu item is now enabled:

projecttypes 72pics customizer 2

Click the menu item and you should see your JOptionPane :

projecttypes 72pics customizer 3
  1. Now we create the infrastructure for our pluggable Project Properties window:

package org.customer.project;

import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.spi.project.ui.CustomizerProvider;
import org.netbeans.spi.project.ui.support.ProjectCustomizer;
import org.openide.awt.StatusDisplayer;
import org.openide.util.lookup.Lookups;

public class CustomerCustomizerProvider implements  CustomizerProvider {

    public final CustomerProject project;

    public static final String CUSTOMIZER_FOLDER_PATH =
            "Projects/org-customer-project/Customizer";

    public CustomerCustomizerProvider(CustomerProject project) {
        this.project = project;
    }

    @Override
    public void showCustomizer() {
        Dialog dialog =  ProjectCustomizer.createCustomizerDialog(
                //Path to layer folder:
                CUSTOMIZER_FOLDER_PATH,
                //Lookup, which must contain, at least, the Project:
                Lookups.fixed(project),
                //Preselected category:
                "",
                //OK button listener:
                new OKOptionListener(),
                //HelpCtx for Help button of dialog:
                null);
        dialog.setTitle(ProjectUtils.getInformation(project).getDisplayName());
        dialog.setVisible(true);
    }

    private class OKOptionListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            StatusDisplayer.getDefault().setStatusText("OK button clicked for "
                    + project.getProjectDirectory().getName() + " customizer!");
        }

    }

}
  1. Next, rewrite the project’s Lookup so that the above class is created within it:

@Override
public Lookup getLookup() {
    if (lkp == null) {
        lkp = Lookups.fixed(new Object[]{
               this,
               new Info(),
               new CustomerProjectLogicalView(this),
               *new CustomerCustomizerProvider(this)*
        });
    }
    return lkp;
}
  1. In a new package org.customer.project.panels , create a new Java class named GeneralCustomerProperties , with this content:

package org.customer.project.panels;

import javax.swing.JComponent;
import javax.swing.JPanel;
import org.netbeans.spi.project.ui.support.ProjectCustomizer;
import org.netbeans.spi.project.ui.support.ProjectCustomizer.Category;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;

public class GeneralCustomerProperties
    implements  ProjectCustomizer.CompositeCategoryProvider {

    private static final String GENERAL = "General";

    @ProjectCustomizer.CompositeCategoryProvider.Registration(
            projectType = "org-customer-project", position = 10)
    public static GeneralCustomerProperties createGeneral() {
        return new GeneralCustomerProperties();
    }

    @NbBundle.Messages("LBL_Config_General=General")
    @Override
    public Category createCategory(Lookup lkp) {
        return ProjectCustomizer.Category.create(
                GENERAL,
                Bundle.LBL_Config_General(),
                null);
    }

    @Override
    public JComponent createComponent(Category category, Lookup lkp) {
        return new JPanel();
    }

}

Note the usage of the @ProjectCustomizer.CompositeCategoryProvider.Registration annotation above. Using that annotation, you can register new panels in the Project Properties dialog, via the extension point you created in step 5 above. In this way, each panel can be contributed by external modules. For another example, see Adding New Tabs to the Project Properties Dialog in NetBeans IDE.

  1. Run the module again and right-click the project node. When you click the Properties menu item, you should see the Project Properties dialog, with one category:

projecttypes 72pics customizer 4

When you click the OK button, you will see a message in the status bar. The message is provided by the OKOptionListener you defined above.

You now have the start of a project customizer.

Creating and Registering the Project Subprojects

In this section, you learn how to create new project types that are nested within other project types:

projecttypes 72pics sub proj 2

Above, you can see that the "customer3" project has several folders. One of those folders is named "reports", containing subfolders, each of which contains a file named "report.xml". In the instructions that follow, you will create a new project type for folders containing a file named "report.xml", while also being shown how to register those projects as subprojects of the customer project.

  1. Following the instructions at the start of this tutorial, create a new ProjectFactory that recognizes folders containing a file named "report.xml" as a project of type ReportsSubProject . Define a ProjectInformation and a ProjectLogicalView for your ReportsSubProject .

  1. Create a SubprojectProvider that looks inside the customer project’s "reports" folder for projects of your type:

public class ReportsSubprojectProvider implements  SubprojectProvider {

    private final CustomerProject project;

    public ReportsSubprojectProvider(CustomerProject project) {
        this.project = project;
    }

    @Override
    public Set<? extends Project> getSubprojects() {
        return loadProjects(project.getProjectDirectory());
    }

    private Set loadProjects(FileObject dir) {
        Set newProjects = new HashSet();
        FileObject reportsFolder = dir.getFileObject("reports");
        if (reportsFolder != null) {
            for (FileObject childFolder : reportsFolder.getChildren()) {
                try {
                    Project subp = ProjectManager.getDefault().
                        findProject(childFolder);
                    if (subp != null && subp instanceof ReportsSubProject) {
                        newProjects.add((ReportsSubProject) subp);
                    }
                } catch (IOException ex) {
                    Exceptions.printStackTrace(ex);
                } catch (IllegalArgumentException ex) {
                    Exceptions.printStackTrace(ex);
                }
            }
        }
        return Collections.unmodifiableSet(newProjects);
    }

    @Override
    public void addChangeListener(ChangeListener cl) {
    }

    @Override
    public void removeChangeListener(ChangeListener cl) {
    }

}
  1. Register the SubprojectProvider in the customer project’s Lookup:

@Override
public Lookup getLookup() {
    if (lkp == null) {
        lkp = Lookups.fixed(new Object[]{
                    this,
                    new Info(),
                    new CustomerProjectLogicalView(this),
                    new CustomerCustomizerProvider(this),
                    *new ReportsSubprojectProvider(this)*
                });
    }
    return lkp;
}
  1. Similar to the TextsNodeFactory that you created earlier in this tutorial, create a new Java class ReportsSubProjectNodeFactory as follows, while again taking special note of the @NodeFactory.Registration annotation, which registers the NodeFactory into the logical view of the customer project:

@NodeFactory.Registration(projectType = "org-customer-project", position = 20)
public class ReportsSubProjectNodeFactory implements NodeFactory {

@StaticResource()
    public static final String SUB_ICON = "org/customer/project/sub/icon.png";

    @Override
    public NodeList<?> createNodes(Project project) {
        ReportsSubprojectProvider rsp = project.getLookup().
            lookup(ReportsSubprojectProvider.class);
        assert rsp != null;
        return new ReportsNodeList(rsp.getSubprojects());
    }

    private class ReportsNodeList implements NodeList<Project> {

        Set<? extends Project> subprojects;

        public ReportsNodeList(Set<? extends Project> subprojects) {
            this.subprojects = subprojects;
        }

        @Override
        public List<Project> keys() {
            List<Project> result = new ArrayList<Project>();
            for (Project oneReportSubProject : subprojects) {
                result.add(oneReportSubProject);
            }
            return result;
        }

        @Override
        public Node node(Project node) {
            FilterNode fn = null;
            try {
                fn = new FilterNode(DataObject.find(node.
                        getProjectDirectory()).getNodeDelegate()){
                    @Override
                    public Image getIcon(int type) {
                        return ImageUtilities.loadImage(SUB_ICON);
                    }
                    @Override
                    public Image getOpenedIcon(int type) {
                        return ImageUtilities.loadImage(SUB_ICON);
                    }
                };
            } catch (DataObjectNotFoundException ex) {
                Exceptions.printStackTrace(ex);
            }
            return fn;
        }

        @Override
        public void addNotify() {
        }

        @Override
        public void removeNotify() {
        }

        @Override
        public void addChangeListener(ChangeListener cl) {
        }

        @Override
        public void removeChangeListener(ChangeListener cl) {
        }

    }

}

Above, reference is made to an icon. Use your own, 16x16 pixels in size, or use one of the two shown at the top of this tutorial.

  1. Run the module again, go to the Open Project dialog, and notice that subprojects are recognized and that you can open them:

projecttypes 72pics sub proj 1

Also, when you’ve selected a customer project in the Open Project dialog, the Open Project dialog lets you open the subprojects, too:

projecttypes 72pics sub proj 3

Using the instructions in this subsection, you can create a richly structured and deeply nested project hierarchy, because each subproject can provide its own subprojects, too. For further information on this topic, see this blog entry, this blog entry, and this blog entry.

In this section, you have defined the basic infrastructure of a new type of project in your NetBeans Platform application.

Registering the Project Type as Project Sample

In this section, we create some project samples that make use of our project type. We also register these project samples in the New Project window of our application.

  1. Run the module that you created in this tutorial. A new instance of your NetBeans Platform application starts up, with your project type installed via your module. If you’re creating the project type for NetBeans IDE, continue to the next step.

If you’re creating the project type for some other application on the NetBeans Platform, you will need to include the apisupport modules from NetBeans IDE in your application, temporarily, to complete the steps that follow.

  1. Open the sample projects you created in the previous step, which you’re now able to do since you have installed a module providing your project type.

projecttypes 72pics proj temp 0
  1. Also open the module itself. Create a new subpackage, named "samples", as shown below. Then right-click the package and choose New | Other | Module Development, and select Project Template as shown below:

projecttypes 72pics proj temp 1
  1. Use the New Project Template wizard to register your first sample project:

projecttypes 72pics proj temp 2

Click Next. Specify the name of the template, the display text, and the package where the template should be registered:

projecttypes 72pics proj temp 3
  1. Once you have completed the wizard, use it again to register other customer projects as samples.

  1. Check that the module you’re developing now looks something like this in the Projects window:

projecttypes 72pics proj temp 4

You have now used the New Project Template wizard to register some project samples in your application. Also notice that you have some ZIP files containing your sample projects, created by the Project Template wizard, together with several classes from the NetBeans Wizard API. For further information, refer to the NetBeans Project Sample Module Tutorial.

  1. After closing the second instance of the IDE with the installed module, close and reopen the module in the original IDE before trying to run it. The reason for this is that the nbproject\private\platform-private.properties is changed by the second instance of the IDE to point to the testuserdir of the module, when the module is opened. Reopening the module fixes this problem.

  1. Run your module again and go to File | New Project. You should see your new project samples, together with any other project samples registered in the application:

projecttypes 72pics proj temp 5

Complete the wizard. At the end of the wizard, the ZIP file is unzipped and the new project is created.

You now have support for a new type of project, including a set of samples that your users can use to create skeleton projects of your type.

Next Steps

For more information about creating and developing NetBeans modules, see the following resources: