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.
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
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.
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);
}
}
}