Nodes extended from DefaultNodeSettingsPane copy/sare component values

My node extends DefaultNodeSettingsPane, and among other controls, it has a text box. I save and load the values to and from this textbox via these two methods (that I have overridden):

loadAdditionalSettingsFrom(final NodeSettingsRO settings, final PortObjectSpec specs)

saveAdditionalSettingsTo(final NodeSettingsWO settings)

I instantiate my setting in the constructor of my dialog class: myExtensionNodeDialog. I then attach this setting as the dialog component.

Node works fine to retrieve and save the textbox value when there are only one of these nodes in my workflow. However, if I drag a new instance of this node onto the canvas, the textbox field automatically copies the value from the textbox of the first node.

It appears as if there is a shared memory area being used? Is there something obvious I have missed in my implementation, for example, re-instantiation of the node, etc.?

Slight correction:
I use these two methods in my myExtensionNodeModel class to load and save settings to and from the textfield:
loadValidatedSettingsFrom
saveSettingsTo

loadAdditionalSettingsFrom and saveAdditionalSettings (in myExtensionNodeDialogclass) to overwrite values in another dropdown on the screen.

When you declare your settings model fields in the NodeModel, do you happen to declare them as static, e.g.

pulbic class MyNodeModel extends NodeModel {
    private static final SettingsModelString stringMdl = createMyStringModel();
    ...

   public MyNodeModel() {
      super(1,1);
   }
}

I’ve made this mistake a number of times in the past, and that causes this effect (And other seemingly random behaviours depending on which copy of the node last loaded the settings)

Steve

4 Likes

That was spot on, Steve. Thank you!

I followed the Developer Guide that suggests static references to settings members, didn’t realise it will result in settings sharing between multiple instances of nodes.

Your comment: “…And other seemingly random behaviours depending on which copy of the node last loaded the settings” is also correct. I also noticed a similar issue at run time also.

1 Like

Glad that fixed if - I think that’s a mistake in the developer guide - it does explain why I started doing it though if it is in there.

What you can use static members for though is the node setting key for a setting, and also a static factory method. I normally put these in the NodeDialog implementation, and use the key for the displayed component label too for clarity. The key can be private but the factory method needs to be visible to the NodeModel so needs to be at least package, e.g.:

public class MyNodeSettingsPane extends DefaultNodeSettingsPane {
    /** Model key also used for component label */
    private static final MODEL_KEY = "Input column name";
    
    public MyNodeSettingsPane() {
        addDialogComponent(new DialogComponentColumnNameSelection(
                createColumnNameModel(), MODEL_KEY, 0, StringValue.class));
    }

   //package-visible factory method for settings model
   static SettingsModelString createColumnNameModel() {
       return new SettingsModelString(MODEL_KEY, null);
   }
}

Then in the node model:

import static org.my.node.package.MyNodeSettingsPane.createColumnNameModel;

public class MyNodeModel {
    private final SettingsModelString columnNameModel = createColumnNameModel();
    ...
}

The other gotcha to watch out for is to make sure the version ends .qualifier - the default in the wizard used to just give 1.0.0, and then when you update your code and try to install it then the updates mysteriously dont work unless are rigorous about updating the actual version each time, which I have to admit I don’t do for our internal codebase.

Steve

1 Like

Thanks again, Steve!

My settings members and the creator methods are currently declared in Model class, I need to reorganize a bit.

On a separate topic, have you used the NodeDialogPane class to have more control over the screen elements, more like Java Swing style ability? If so, have you come across any official documentation?

1 Like

There’s no problem having them in the NodeModel instead (except in that case if you want to use the static constant String for both the settings key and dialog text you will need that to be package visible).

I’ve not used the NodeSettingsPane very much - the only documentation I’m aware of is the javadoc for that class itself, which you should be able to see in Eclipse, or on github at:

The one place I can think of that we used it (and this was mainly written for us by a contractor, Dave Morley) is in our original PDB Connector nodes. You can see the code for that here:

When I rewrote those nodes recently, I again used NodeSettingsPane, but went for a custom approach of creating new DialogComponent classes to create some quite complex Swing Panels and then adding them to the node dialog using the NodeDialog#getComponentPanel() method - if you want to look at that, then have a look at vernalis-knime-nodes/PdbConnector2QueryNodeDialog.java at master · vernalis/vernalis-knime-nodes · GitHub for the dialog pane. There are a small number of component panels in there, e.g. the ChemicalQueryPane, which do most of the actual work, and various custom DialogComponent and SettingsModel implementations in vernalis-knime-nodes/com.vernalis.knime.pdbconnector/src/com/vernalis/pdbconnector2/dialogcomponents at master · vernalis/vernalis-knime-nodes · GitHub. The reason for this was mainly to allow mixing and matching of existing KNIME components where possible in what was already a fairly hideous dialog!

If you just want to ‘tweak’ the layout of the KNIME DialogComponents in DefaultNodeSettingsPane there are a couple of things you can do:

  1. We have a class DialogComponentGroup which allows grouping some components (similar to the addNewGroup() method, but with nesting)
    You can see an example of this here:

Which results in:

image

The ‘Kernel Options’ box is created via the standard createNewGroup("Kernel Options") call further back in the code. The ‘Values Column Bandwidth’ box is added within that group, wrapping a DialogComponentButtonGroup and a DialogComponentNumberEdit. (Actually, you can use this without adding a second component to fix the problem with truncated titles in the standard DialogComponentButtonGroup, if you create a DialogComponentGroup with the title, and a title-less DialogComponentButtonGroup)

  1. You can access the Swing component panel. For example this code snippet creates a dialog with a number of DialogComponentBoolean inputs, all left-aligned, and all the same width in a number of columns (defined by the variable numCols):
    setHorizontalPlacement(numCols > 1);
    int count = 0;
    DialogComponentBoolean[] resultColumnDialogComps =
            new DialogComponentBoolean[resultColumns.size()]; //resultColumns is a list
    int maxWidth = 0;

    // Build the boolean dialog components
    // NB we do some trickery to left align within the component and
    // make them all the same width...
    for (int i = 0; i < resultColumnDialogComps.length; i++) {
        // ResultColumn is a simple holding class with result column name and a SettingsModelBoolean
        ResultColumn<?, ?> rCol = resultColumns.get(i);
        resultColumnDialogComps[i] =
                 new DialogComponentBoolean(rCol.getSettingsModel(), rCol.getColumnName());

        // Get the Swing JPanel from the DialogComponent and change layout
        JPanel componentPanel = resultColumnDialogComps[i].getComponentPanel();
        componentPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

        // We need to find the width of the widest component
        maxWidth = Math.max(maxWidth, componentPanel.getPreferredSize().width);
    }

    // Now we need to add the components in rows, with each row containing 'numCols' components
    for (DialogComponentBoolean dlgComp : resultColumnDialogComps) {
        JPanel componentPanel = dlgComp.getComponentPanel();

        // Now we set the preferred width for all components to the widest
        componentPanel.setPreferredSize(
                new Dimension(maxWidth, componentPanel.getPreferredSize().height));

        // And add the component to the node dialog        
        addDialogComponent(dlgComp);
        if (numCols > 1 && ++count % numCols == 0) {
            // Start a new row of components in the dialog if needed:
            setHorizontalPlacement(false);
            setHorizontalPlacement(true);
        }
    }

    // Finally make sure we are back to the standard vertical layout for any further components
    setHorizontalPlacement(false);

The result is something along the lines of:

Sorry, that has turned into a very long answer - hope that helps!

Steve

2 Likes

Hi Steve,

I am glad I asked that question. Very helpful indeed!

My use case is a table with two columns: the first column has static labels and the second column has dropdowns that get filled based on the upstream node supplying information into my node. Also, the number of rows in the table will be generated based on the information coming from the previous node.

I need to read your response more thoroughly but it seems I will need to use NodeDialog class for more flexibility.

We will start to make these changes in a few weeks, I might be in touch again to bounce some ideas.

Once again, many thanks for your generous help!

Kind regards,
Shri

1 Like

DialogComponentStringSelection might be able to do what you want. If you create all the components as in my second example, and then for each one you can get the JLabel for the static label and the JComboBox for the dropdown with

 DialogComponentStringSelection diaC = ...
JLabel label = (JLabel)diaC.getComponentPanel().getComponent(0);
JComboBox combo = (JComboBox)diaC.getComponentPanel().getComponent(1);

Then you can sort out their sizes as I showed for the simpler case - find the maximum width of both the label and the dropdown, and set each one to that value

Steve

2 Likes

Seems Steve just started writing documentation :sweat_smile:

2 Likes

OK, here is the full documentation:

  1. Are you sure you can’t use DefaultNodeSettingsPane?
  2. Are you really sure you can’t use DefaultNodeSettingsPane?

:slight_smile:

In all seriousness, DefaultNodeSettingsPane makes thing so much easier handling all the load/save stuff invisibly that I find in most cases it is simpler to use some of the above ‘hacks’ if you need to tweak things.

Steve

2 Likes

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.