diff --git a/src/DynamoCoreWpf/DynamoCoreWpf.csproj b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
index 5de998ab136..22268a6b374 100644
--- a/src/DynamoCoreWpf/DynamoCoreWpf.csproj
+++ b/src/DynamoCoreWpf/DynamoCoreWpf.csproj
@@ -223,6 +223,7 @@
+
diff --git a/src/DynamoCoreWpf/NodeViewCustomization/NodeViewCustomizations/FunctionNodeViewCustomization.cs b/src/DynamoCoreWpf/NodeViewCustomization/NodeViewCustomizations/FunctionNodeViewCustomization.cs
index aeb01b0797e..d849381a392 100644
--- a/src/DynamoCoreWpf/NodeViewCustomization/NodeViewCustomizations/FunctionNodeViewCustomization.cs
+++ b/src/DynamoCoreWpf/NodeViewCustomization/NodeViewCustomizations/FunctionNodeViewCustomization.cs
@@ -19,8 +19,6 @@ public void CustomizeView(Function function, NodeView nodeView)
functionNodeModel = function;
dynamoViewModel = nodeView.ViewModel.DynamoViewModel;
- nodeView.MainContextMenu.Items.Add(new Separator());
-
// edit contents
var editItem = new MenuItem
{
diff --git a/src/DynamoCoreWpf/Utilities/NodeContextMenuBuilder.cs b/src/DynamoCoreWpf/Utilities/NodeContextMenuBuilder.cs
new file mode 100644
index 00000000000..ab079f433ff
--- /dev/null
+++ b/src/DynamoCoreWpf/Utilities/NodeContextMenuBuilder.cs
@@ -0,0 +1,453 @@
+using System.Collections;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using Dynamo.Controls;
+using Dynamo.Graph.Nodes;
+using Dynamo.ViewModels;
+
+namespace Dynamo.Wpf.Utilities
+{
+ ///
+ /// A static class containing methods for dynamically building a Node's Context Menu.
+ ///
+ internal static class NodeContextMenuBuilder
+ {
+ ///
+ /// A reference to the NodeViewModel whose ContextMenu is being built.
+ ///
+ internal static NodeViewModel NodeViewModel { get; set; }
+
+ ///
+ /// A reference to the ContextMenu to which items are being added.
+ ///
+ internal static ContextMenu ContextMenu { get; set; }
+
+ // Builds the node's Context Menu, including re-adding any injected MenuItems from
+ // the NodeViewCustomization process.
+ internal static void Build(ContextMenu contextMenu, NodeViewModel nodeViewModel, OrderedDictionary nodeViewCustomizationMenuItems)
+ {
+ ContextMenu = contextMenu;
+ NodeViewModel = nodeViewModel;
+
+ AddContextMenuItem(BuildDeleteMenuItem());
+ AddContextMenuItem(BuildGroupsMenuItem());
+ if (nodeViewModel.ShowsVisibilityToggles) AddContextMenuItem(BuildPreviewMenuItem());
+ AddContextMenuItem(BuildFreezeMenuItem());
+ AddContextMenuItem(BuildShowLabelsMenuItem());
+ AddContextMenuItem(BuildRenameMenuItem());
+ if (nodeViewModel.ArgumentLacing != LacingStrategy.Disabled) AddContextMenuItem(BuildLacingMenuItem());
+
+ //AddContextMenuItem(BuildDismissedAlertsMenuItem());
+
+ if (nodeViewCustomizationMenuItems.Count > 0)
+ {
+ AddContextMenuSeparator();
+ AddInjectedNodeViewCustomizationMenuItems(nodeViewCustomizationMenuItems);
+ }
+
+ if(NodeViewModel.IsInput || NodeViewModel.IsOutput) AddContextMenuSeparator();
+ if(NodeViewModel.IsInput) AddContextMenuItem(BuildIsInputMenuItem());
+ if(NodeViewModel.IsOutput) AddContextMenuItem(BuildIsOutputMenuItem());
+
+ AddContextMenuSeparator();
+ AddContextMenuItem(BuildHelpMenuItem());
+ }
+
+ ///
+ /// Adds items to a context menu.
+ ///
+ ///
+ internal static void AddContextMenuItem(MenuItem menuItem) => ContextMenu.Items.Add(menuItem);
+
+ ///
+ /// Adds a new separator a context menu.
+ ///
+ internal static void AddContextMenuSeparator() => ContextMenu.Items.Add(new Separator());
+
+ ///
+ /// Creates a new MenuItem in the node's Context Menu.
+ /// Using optional named arguments here as it makes the Binding syntax
+ /// much cleaner if all handled in the method body.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ internal static MenuItem CreateMenuItem
+ (
+ string name = null,
+ string header = null,
+ Binding command = null,
+ string commandParameter = null,
+ bool isCheckable = false,
+ Binding isChecked = null,
+ Binding visibility = null,
+ Binding isEnabled = null,
+ Binding itemsSource = null
+ )
+ {
+ MenuItem menuItem = new MenuItem { Header = header, IsCheckable = isCheckable };
+
+ if (!string.IsNullOrWhiteSpace(name)) menuItem.Name = name;
+ if (command != null) menuItem.SetBinding(MenuItem.CommandProperty, command);
+ if (commandParameter != null) menuItem.CommandParameter = commandParameter;
+ if (isChecked != null) menuItem.SetBinding(MenuItem.IsCheckedProperty, isChecked);
+ if (visibility != null) menuItem.SetBinding(UIElement.VisibilityProperty, visibility);
+ if (isEnabled != null) menuItem.SetBinding(UIElement.IsEnabledProperty, isEnabled);
+ if (itemsSource != null) menuItem.SetBinding(ItemsControl.ItemsSourceProperty, itemsSource);
+
+ return menuItem;
+ }
+
+ internal static MenuItem BuildDeleteMenuItem()
+ {
+ return CreateMenuItem
+ (
+ name: "deleteElem_cm",
+ header: Properties.Resources.ContextMenuDelete,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.DeleteCommand))
+ }
+ );
+ }
+
+ internal static MenuItem BuildGroupsMenuItem()
+ {
+ MenuItem groupsMenuItem = CreateMenuItem(header: Properties.Resources.ContextMenuGroups);
+
+ groupsMenuItem.Items.Add(CreateMenuItem
+ (
+ name: "createGroup_cm",
+ header: Properties.Resources.ContextCreateGroupFromSelection,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.CreateGroupCommand))
+ }
+ )
+ );
+ groupsMenuItem.Items.Add(CreateMenuItem
+ (
+ name: "unGroup_cm",
+ header: Properties.Resources.ContextUnGroupFromSelection,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.UngroupCommand))
+ }
+ )
+ );
+ groupsMenuItem.Items.Add(CreateMenuItem
+ (
+ name: "addtoGroup",
+ header: Properties.Resources.ContextAddGroupFromSelection,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.AddToGroupCommand))
+ }
+ )
+ );
+
+ return groupsMenuItem;
+ }
+
+ internal static MenuItem BuildPreviewMenuItem()
+ {
+ MenuItem previewMenuItem = CreateMenuItem
+ (
+ name: "isVisible_cm",
+ header: Properties.Resources.NodeContextMenuPreview,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.ToggleIsVisibleCommand))
+ },
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.IsVisible)),
+ Mode = BindingMode.OneWay
+ }
+ );
+
+ return previewMenuItem;
+ }
+
+ internal static MenuItem BuildFreezeMenuItem()
+ {
+ MenuItem freezeMenuItem = CreateMenuItem
+ (
+ name: "nodeIsFrozen",
+ header: Properties.Resources.NodesRunStatus,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.ToggleIsFrozenCommand))
+ },
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.IsFrozenExplicitly)),
+ Mode = BindingMode.OneWay
+ },
+ isEnabled: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.CanToggleFrozen)),
+ Mode = BindingMode.OneWay
+ }
+ );
+
+ return freezeMenuItem;
+ }
+
+ internal static MenuItem BuildShowLabelsMenuItem()
+ {
+ MenuItem showLabelsMenuItem = CreateMenuItem
+ (
+ name: "isDisplayLabelsEnabled_cm",
+ header: Properties.Resources.NodeContextMenuShowLabels,
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.IsDisplayingLabels)),
+ Mode = BindingMode.TwoWay
+ },
+ isEnabled: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.CanDisplayLabels))
+ }
+ );
+
+ return showLabelsMenuItem;
+ }
+
+ internal static MenuItem BuildRenameMenuItem()
+ {
+ MenuItem renameMenuItem = CreateMenuItem
+ (
+ name: "rename_cm",
+ header: Properties.Resources.NodeContextMenuRenameNode,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.RenameCommand))
+ }
+ );
+ return renameMenuItem;
+ }
+
+ internal static MenuItem BuildLacingMenuItem()
+ {
+ MenuItem lacingMenuItem = CreateMenuItem(header: Properties.Resources.ContextMenuLacing);
+
+ lacingMenuItem.Items.Add(CreateMenuItem
+ (
+ header: Properties.Resources.ContextMenuLacingAuto,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.SetLacingTypeCommand)),
+ },
+ commandParameter: "Auto",
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.ArgumentLacing)),
+ Converter = new EnumToBooleanConverter(),
+ ConverterParameter = "Auto",
+ Mode = BindingMode.OneWay
+ }
+ ));
+
+ lacingMenuItem.Items.Add(CreateMenuItem
+ (
+ header: Properties.Resources.ContextMenuLacingShortest,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.SetLacingTypeCommand)),
+ },
+ commandParameter: "Shortest",
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.ArgumentLacing)),
+ Converter = new EnumToBooleanConverter(),
+ ConverterParameter = "Shortest",
+ Mode = BindingMode.OneWay
+ }
+ ));
+
+ lacingMenuItem.Items.Add(CreateMenuItem
+ (
+ header: Properties.Resources.ContextMenuLacingLongest,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.SetLacingTypeCommand)),
+ },
+ commandParameter: "Longest",
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.ArgumentLacing)),
+ Converter = new EnumToBooleanConverter(),
+ ConverterParameter = "Longest",
+ Mode = BindingMode.OneWay
+ }
+ ));
+
+ lacingMenuItem.Items.Add(CreateMenuItem
+ (
+ header: Properties.Resources.ContextMenuLacingCrossProduct,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.SetLacingTypeCommand)),
+ },
+ commandParameter: "CrossProduct",
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.ArgumentLacing)),
+ Converter = new EnumToBooleanConverter(),
+ ConverterParameter = "CrossProduct",
+ Mode = BindingMode.OneWay
+ }
+ ));
+
+ return lacingMenuItem;
+ }
+
+ // To be connected in a future PR for Node Info States.
+ private static MenuItem BuildDismissedAlertsMenuItem()
+ {
+ //MenuItem dismissedAlertsMenuItem = CreateMenuItem
+ //(
+ // name: "dismissedAlerts",
+ // header: Wpf.Properties.Resources.NodeInformationalStateDismissedAlerts,
+ // itemsSource: new Binding
+ // {
+ // Source = viewModel,
+ // Path = new PropertyPath(nameof("DismissedAlerts"),
+ // UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
+ // }
+ //);
+ //DataTemplate itemTemplate = new DataTemplate(typeof(MenuItem));
+
+ //FrameworkElementFactory grid = new FrameworkElementFactory(typeof(Grid));
+ //grid.Name = "mainTemplateGrid";
+ //grid.SetValue(WidthProperty, 220.0);
+ //grid.SetValue(HeightProperty, 30.0);
+ //grid.SetValue(MarginProperty, new Thickness(-15,0,0,0));
+ //grid.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Left);
+ //grid.SetValue(VerticalAlignmentProperty, VerticalAlignment.Stretch);
+ //grid.SetValue(BackgroundProperty, new SolidColorBrush(Colors.Transparent));
+
+ //FrameworkElementFactory textBlock = new FrameworkElementFactory(typeof(TextBlock));
+ //textBlock.SetValue(HorizontalAlignmentProperty, HorizontalAlignment.Stretch);
+ //textBlock.SetValue(MarginProperty, new Thickness(15, 0, 0, 0));
+ //textBlock.SetValue(IsHitTestVisibleProperty, false);
+ //textBlock.SetValue(VerticalAlignmentProperty, VerticalAlignment.Center);
+ //textBlock.SetValue(ForegroundProperty, new SolidColorBrush(Color.FromRgb(238,238,238)));
+
+ //dismissedAlertsMenuItem.ItemTemplate = itemTemplate;
+
+ //AddContextMenuItem(dismissedAlertsMenuItem, insertionPoint++);
+
+ return null;
+ }
+
+ internal static void AddInjectedNodeViewCustomizationMenuItems(OrderedDictionary nodeViewCustomizationMenuItems)
+ {
+ foreach (DictionaryEntry keyValuePair in nodeViewCustomizationMenuItems)
+ {
+ ContextMenu.Items.Add(keyValuePair.Value);
+ }
+ }
+
+ internal static MenuItem BuildIsInputMenuItem()
+ {
+ return CreateMenuItem
+ (
+ name: "isInput_cm",
+ header: Properties.Resources.NodeContextMenuIsInput,
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.IsSetAsInput)),
+ Mode = BindingMode.TwoWay,
+ }
+ );
+ }
+
+ internal static MenuItem BuildIsOutputMenuItem()
+ {
+ return CreateMenuItem
+ (
+ name: "isOutput_cm",
+ header: Properties.Resources.NodeContextMenuIsOutput,
+ isCheckable: true,
+ isChecked: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.IsSetAsOutput)),
+ Mode = BindingMode.TwoWay,
+ }
+ );
+ }
+
+ internal static MenuItem BuildHelpMenuItem()
+ {
+ return CreateMenuItem
+ (
+ name: "help_cm",
+ header: Properties.Resources.NodeContextMenuHelp,
+ command: new Binding
+ {
+ Source = NodeViewModel,
+ Path = new PropertyPath(nameof(NodeViewModel.ShowHelpCommand))
+ }
+ );
+ }
+
+ ///
+ /// A list of the names of nodes' default menu items, which cannot be dynamically injected into its context menu.
+ ///
+ internal static readonly List NodeContextMenuDefaultItemNames = new List
+ {
+ Properties.Resources.ContextMenuDelete,
+ Properties.Resources.ContextMenuGroups,
+ Properties.Resources.NodeContextMenuPreview,
+ Properties.Resources.NodesRunStatus,
+ Properties.Resources.NodeContextMenuShowLabels,
+ Properties.Resources.NodeContextMenuRenameNode,
+ Properties.Resources.ContextMenuLacing,
+ //Wpf.Properties.Resources.NodeInformationalStateDismissedAlerts,
+ Properties.Resources.NodeContextMenuIsInput,
+ Properties.Resources.NodeContextMenuIsOutput,
+ Properties.Resources.NodeContextMenuHelp
+ };
+ }
+}
diff --git a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs
index 33dbf032e71..68d54466b42 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/NodeViewModel.cs
@@ -203,12 +203,7 @@ public bool IsSetAsOutput
}
}
- ///
- /// Used to determine whether the node's context menu display an Output/Input menu
- ///
- [JsonIgnore]
- public bool IsInputOrOutput => IsInput || IsOutput;
-
+
///
/// The Name of the nodemodel this view points to
/// this is the name of the node as it is displayed in the UI.
@@ -283,7 +278,7 @@ public bool IsCustomFunction
{
get { return nodeLogic.IsCustomFunction ? true : false; }
}
-
+
///
/// Element's left position is two-way bound to this value
///
diff --git a/src/DynamoCoreWpf/Views/Core/NodeView.xaml b/src/DynamoCoreWpf/Views/Core/NodeView.xaml
index d9564016027..9676379602a 100644
--- a/src/DynamoCoreWpf/Views/Core/NodeView.xaml
+++ b/src/DynamoCoreWpf/Views/Core/NodeView.xaml
@@ -13,7 +13,7 @@
MouseEnter="OnNodeViewMouseEnter"
MouseLeave="OnNodeViewMouseLeave"
MouseLeftButtonDown="topControl_MouseLeftButtonDown"
- MouseRightButtonDown="topControl_MouseRightButtonDown"
+ MouseRightButtonDown="DisplayNodeContextMenu"
PreviewMouseLeftButtonDown="OnPreviewMouseLeftButtonDown"
PreviewMouseMove="OnNodeViewMouseMove">
@@ -47,7 +47,10 @@
-
+
@@ -190,108 +193,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -560,7 +461,7 @@