C# Dynamo Nodes - Dropdown input Node

Hi, my second main issue is creating drop down Node.
Currently, I am using separate nodes for each simple piece of data. Would be amazing to combine them together into one DropDown Node. So far no success at all.

/// </summary>  
public enum TSDZoneArray
{
    /// <summary>Dry Bulb Temperature</summary>
    DryBulbTemp = 1,
    /// <summary>MR Temperature</summary>
    MRTemp = 2,
    /// <summary>Resultant Temperature</summary>
    ResultantTemp = 3,
    /// <summary>Sensible Load</summary>
    SensibleLoad = 4,
    /// <summary>Heating Load</summary>
    HeatingLoad = 5,
    /// <summary>Cooling Load</summary>
    CoolingLoad = 6,
    /// <summary>Solar Gain</summary>
    SolarGain = 7,
    /// <summary>Lighting Gain</summary>
    LightingGain = 8,
    /// <summary>Inf Vent Gain</summary>
    InfVentGain = 9,
    /// <summary>Air Movement Gain</summary>
    AirMovementGain = 10,

}

1 Like

You need to use WPF and before you ask, the method of creating interfaces on nodes is quite ugly and a far cry from a few lines of code unfortunately. If you install the Dynamo nuget package in visual studio for WPF it will install all of the required libraries. The rest, I leave to you :wink:

1 Like

Some helpful resources. (Disclaimer: I do not know how to make nodes like this yet, still learning.)

2 Likes

I will actually argue to the contrary. It’s not that bad. I usually use Grasshopper for the comparison, and in this particular case the Dynamo methods for defining dropdowns are pretty decent. Here’s one of them adopted from Max’s recent contributions to DynamoRevit repo:

[NodeName("Select Rule Type")]
    [NodeCategory("Revit.Filter.RuleType")]
    [NodeDescription("FilterTypeSelectorDescription", typeof(DSRevitNodesUI.Properties.Resources))]
    [IsDesignScriptCompatible]
    public class RuleTypes : CustomGenericEnumerationDropDown
    {
        public RuleTypes() : base("RuleType", typeof(Revit.Filter.FilterRule.RuleType)) { }
    }

This inherits from this class:

    /// <summary>
    /// Generic UI Dropdown node baseclass for Enumerations.
    /// This class populates a dropdown with all enumeration values of the specified type.
    /// </summary>
    public abstract class CustomGenericEnumerationDropDown : RevitDropDownBase
    {
        /// <summary>
        /// Generic Enumeration Dropdown
        /// </summary>
        /// <param name="name">Node Name</param>
        /// <param name="enumerationType">Type of Enumeration to Display</param>
        public CustomGenericEnumerationDropDown(string name, Type enumerationType) : base(name) { this.EnumerationType = enumerationType; PopulateDropDownItems(); }

        /// <summary>
        /// Type of Enumeration
        /// </summary>
        private Type EnumerationType
        {
            get;
            set;
        }

        protected override CoreNodeModels.DSDropDownBase.SelectionState PopulateItemsCore(string currentSelection)
        {
            PopulateDropDownItems();
            return SelectionState.Done;
        }

        /// <summary>
        /// Populate Items in Dropdown menu
        /// </summary>
        public void PopulateDropDownItems()
        {
            if (this.EnumerationType != null)
            {
                // Clear the dropdown list
                Items.Clear();

                // Get all enumeration names and add them to the dropdown menu
                foreach (string name in Enum.GetNames(EnumerationType))
                {
                    Items.Add(new CoreNodeModels.DynamoDropDownItem(name, Enum.Parse(EnumerationType, name)));
                }

                Items = Items.OrderBy(x => x.Name).ToObservableCollection();
            }
        }

        /// <summary>
        /// Assign the selected Enumeration value to the output
        /// </summary>
        public override IEnumerable<AssociativeNode> BuildOutputAst(List<AssociativeNode> inputAstNodes)
        {
            // If the dropdown is still empty try to populate it again          
            if (Items.Count == 0 || Items.Count == -1)
            {
                if (this.EnumerationType != null && Enum.GetNames(this.EnumerationType).Length > 0)
                {
                    PopulateItems();
                }
            }

            // get the selected items name
            var stringNode = AstFactory.BuildStringNode((string)Items[SelectedIndex].Name);

            // assign the selected name to an actual enumeration value
            var assign = AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), stringNode);

            // return the enumeration value
            return new List<AssociativeNode> { assign };
        }
    }

If you have pasted the CustomGenericEnumerationDropDown into your code, then you can use that first method to define dropdowns by simply inheriting from it.

Cheers!

PS. @Thomas_Mahon it is correct that you can go all out custom WPF dropdown, and use a NodeModel route, but then yes, one would need to create all of the interaction logic, handle events, etc. It does get a little more cumbersome then. For this application its not necessary.

10 Likes

2 posts were split to a new topic: C# Dropdown

Thank you so much for your contribution. You save my life.

Below I contribute with a few modifications on CustomGenericEnumerationDropDown class in order to return the EnumerationType value as object, instead of a string.

using CoreNodeModels;
using Dynamo.Utilities;
using ProtoCore.AST.AssociativeAST;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NodeModels
{
    /// <summary>
    /// Generic UI Dropdown node baseclass for Enumerations.
    /// This class populates a dropdown with all enumeration values of the specified type.
    /// </summary>
    public abstract class CustomGenericEnumerationDropDown : DSDropDownBase
    {
        /// <summary>
        /// Generic Enumeration Dropdown
        /// </summary>
        /// <param name="name">Node Name</param>
        /// <param name="enumerationType">Type of Enumeration to Display</param>
        public CustomGenericEnumerationDropDown(string name, Type enumerationType, Func<string, object> function) 
        : base(name) 
        {
            this.EnumerationType = enumerationType; 
            this.Func = function;
            PopulateDropDownItems(); 
        }


        /// <summary>
        /// Type of Enumeration
        /// </summary>
        private Type EnumerationType
        {
            get;
            set;
        }

        /// <summary>
        /// Function to convert string to Enum Type value
        /// </summary>
        private Func<string, object> Func
        {
            get;
            set;
        }

    protected override CoreNodeModels.DSDropDownBase.SelectionState PopulateItemsCore(string currentSelection)
        {
            PopulateDropDownItems();
            return SelectionState.Restore;
        }

        /// <summary>
        /// Populate Items in Dropdown menu
        /// </summary>
        public void PopulateDropDownItems()
        {
            if (this.EnumerationType != null)
            {
                // Clear the dropdown list
                Items.Clear();

                // Get all enumeration names and add them to the dropdown menu
                foreach (string name in Enum.GetNames(EnumerationType))
                {
                    Items.Add(new CoreNodeModels.DynamoDropDownItem(name, Enum.Parse(EnumerationType, name)));
                }

                Items = Items.OrderBy(x => x.Name).ToObservableCollection();

                // Prevent node to raise a Warning when Dynamo script is in Automatic mode
                // by Selecting the list first item
                SelectedIndex = 0;
            }
        }

        /// <summary>
        /// Assign the selected Enumeration value to the output
        /// </summary>
        public override IEnumerable<AssociativeNode> BuildOutputAst(List<AssociativeNode> inputAstNodes)
        {
            // If the dropdown is still empty try to populate it again          
            if (Items.Count == 0 || Items.Count == -1)
            {
                if (this.EnumerationType != null && Enum.GetNames(this.EnumerationType).Length > 0)
                {
                    PopulateItems();
                }
            }

            // get the selected item name    
            var stringNode = AstFactory.BuildStringNode((string)Items[SelectedIndex].Name);
            var args = new List<AssociativeNode>();
            args.Add(stringNode);

            var functionCall = AstFactory.BuildFunctionCall(this.Func, args);

            // assign the selected name to an actual enumeration value
            var assign = AstFactory.BuildAssignment(GetAstIdentifierForOutputIndex(0), functionCall);

            // return the enumeration value
            return new List<AssociativeNode> { assign };
        }

    }
}

Here follow my derived class implementation.

using Dynamo.Graph.Nodes;
using onBIMNodes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NodeModels
{
    [NodeName("Elevation Types")]
    [NodeDescription("Elevation types to be used in GeoJSON geometry conversion to Dynamo")]
    [NodeCategory("onBIM Nodes.GIS")]
    [IsDesignScriptCompatible]
    public class ElevationTypesDropDown : CustomGenericEnumerationDropDown
    {
        public ElevationTypesDropDown() 
        : base(
            "Elevation Types", 
            typeof(onBIMNodes.GIS.ElevationType), 
            new Func<string, object>
            (NodeModelsFunctions.ElevationTypesDropDownFunctions.ParseElevationType)
        ) { }
    }
}

And Finally my function implementation

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.DesignScript.Runtime;
using onBIMNodes;

namespace NodeModelsFunctions
{
    [IsVisibleInDynamoLibrary(false)]
    public class ElevationTypesDropDownFunctions
    {
        public static object ParseElevationType(string value)
        {
            return Enum.Parse(typeof(onBIMNodes.GIS.ElevationType), value);
        }
    }
}

The final result

2 Likes