X

Geertjan's Blog

  • August 15, 2015

Complex Properties Windows on the NetBeans Platform (Part 1)

Geertjan Wielenga
Product Manager

When you have complex requirements for your Properties windows, I'd recommend that you stop forcing your requirements into the default Properties window and create your own instead.

Below, you see a node hierarchy and you also see a Properties window. The screenshot on the left shows the default Properties window provided by the NetBeans Platform, while the one on the right shows a Properties window I made myself.

There's quite a few differences. Notice how neat the second one is, just as professional as the first. Especially notice that I have two properties ("Has Water" and "Has Life") next to each other in my own Properties window, which is something that the default Properties window is unable to do. The Properties window I created also looks a bit different and could be customized a lot further, i.e., right now every property is displayed in its own panel, except for boolean properties. Since each row is a JPanel and not a row in a JTable or something in the default Properties window, I have much more control over the rendering of my custom Properties window than I have over the default Properties window provided by the NetBeans Platform.

And, the cool, thing is, I didn't create any of the widgets you see above, i.e., the drop-down list, the checkboxes, and the color picker, myself—the most important component here is org.openide.explorer.propertysheet.PropertyPanel.

The structure of this solution is as follows:


The most interesting part is the 'PlanetPropertiesTopComponent', which looks like this:

public class PlanetPropertiesTopComponent extends TopComponent
implements LookupListener {
private Lookup.Result<Node> nodeResult;
public PlanetPropertiesTopComponent() {
setName(Bundle.CTL_PlanetPropertiesTopComponent());
}
@Override
public void resultChanged(LookupEvent le) {
if (!nodeResult.allInstances().isEmpty()) {
rewritePanel();
}
}
private void rewritePanel() {
removeAll();
setLayout(new BorderLayout());
JPanel mainPanel = new JPanel(new MigLayout());
add(mainPanel, BorderLayout.CENTER);
Node node
= nodeResult.allInstances().iterator().next();
String displayName = node.getDisplayName();
setDisplayName(displayName + " - Properties");
List<Property> booleanProperties = new ArrayList<Property>();
Property<?>[] props = node.getPropertySets()[0].getProperties();
for (Node.Property property : props) {
JPanel ppJPanel = new SinglePropertyJPanel(property);
if (property.getValueType().equals(boolean.class)) {
booleanProperties.add(property);
} else {
mainPanel.add(
ppJPanel,
"dock north, wrap, growx, pushx");
}
}
mainPanel.add(
new MultiPropertyJPanel(booleanProperties),
"wrap, growx, pushx");
repaint();
validate();
}
@Override
public void componentOpened() {
nodeResult
= Utilities.actionsGlobalContext().lookupResult(Node.class);
nodeResult.addLookupListener(this);
}
@Override
public void componentClosed() {
nodeResult.removeLookupListener(this);
}
}

Notice that we listen for "Node", which is org.openide.nodes.Node. When a new Node is selected, the content of the Properties window is recreated, using the properties that are found in the Node, hence the related PropertyPanels are used automatically.

To get the "SinglePropertyJPanel" and "MultiPropertyJPanel" exactly right, I used the Matisse GUI Builder. Here's the "SinglePropertyJPanel":

And here's the "MultiPropertyJPanel":


In each case, there's a JLabel and a PropertyPanel in the GUI Builder for a JPanel. The PropertyPanel can be added to the Component Palette, by right-clicking inside of it, choosing Palette Manager, and then clicking "Add from JAR". Then browse to your installation of NetBeans IDE (or the NetBeans Platform) and browse to 'platform/modules/org-openide-explorer.jar', at which point you can choose 'PropertyPanel', which will then be added to the Component Palette. From there, drag it onto your JPanel in the GUI Builder.

The constructor of "SinglePropertyJPanel" is as follows:

public class SinglePropertyJPanel extends JPanel {
public SinglePropertyJPanel(Property property) {
initComponents();
propertyName.setText(property.getDisplayName());
propertyPanel.setProperty(property);
setBackground(Color.WHITE);
setBorder(new LineBorder(Color.GRAY));
}

And here is the constructor of the "MultiPropertyPanel", which currently handles two properties:

public class MultiPropertyJPanel extends JPanel {
public MultiPropertyJPanel(List<Property> properties) {
initComponents();
for (int i = 0; i < properties.size(); i++) {
Property property = properties.get(i);
if (i == 1) {
jLabel1.setText(property.getDisplayName());
propertyPanel1.setProperty(property);
} else {
jLabel2.setText(property.getDisplayName());
propertyPanel2.setProperty(property);
}
}
setBackground(Color.WHITE);
setBorder(new LineBorder(Color.BLACK, 1));
}

Also, here's a neat trick in the PlanetNode, or any other Node, for removing all the elipsis buttons next to string properties:

@Override
public PropertySet[] getPropertySets() {
PropertySet[] original = super.getPropertySets();
for (PropertySet ps : original) {
Property<?>[] properties = ps.getProperties();
for (Property<?> property : properties) {
if (property.getValueType().equals(String.class)) {
property.setValue("suppressCustomEditor", true);
}
}
}
return original;
}

Now see part 2!

Join the discussion

Comments ( 2 )
  • Jimbo Tuesday, August 18, 2015

    Interesting article. Little comment: don't use "repaint(); validate();". Just use "revalidate()". As a rule of thumbs with Swing:

    * Avoid to use validate(). It is not threadsafe and can lead to unexpected results.

    * Stick to revalidate() when you add/remove components

    * Use only repaint() when you really know your custom painting should be called again because some values it uses have changed.


  • Geertjan Wednesday, August 19, 2015

    Many thanks for these Swing tips!


Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.