5.5 - NodeView Collector

This extra chapter gives a compiled solution on how to be able to track and map NodeView instances to their corresponding NodeModel.

Extension Methods

These extension methods are used to find some particular type of elements on a window's WPF Visual Tree.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;

namespace DiagnosticToolkit.Utilities
{
    //https://github.com/mjkkirschner/DynamoSamples/blob/extensionWorkshop/src/NodeUISampleViewExtension/NodeUISampleViewExtension.cs
    public static class WindowExtensions
    {
        ///https://stackoverflow.com/questions/10279092/how-to-get-children-of-a-wpf-container-by-type
        public static T GetChildOfType<T>(this DependencyObject depObj) where T : DependencyObject
        {
            if (depObj == null) return null;

            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
            {
                var child = VisualTreeHelper.GetChild(depObj, i);

                var result = (child as T) ?? GetChildOfType<T>(child);
                if (result != null) return result;
            }
            return null;
        }

        //https://stackoverflow.com/questions/974598/find-all-controls-in-wpf-window-by-type/978352
        public static IEnumerable<T> FindVisualChildren<T>(this DependencyObject depObj) where T : DependencyObject
        {
            if (depObj != null)
            {
                for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
                {
                    DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
                    if (child != null && child is T)
                    {
                        yield return (T)child;
                    }

                    foreach (T childOfChild in FindVisualChildren<T>(child))
                    {
                        yield return childOfChild;
                    }
                }
            }
        }
    }
}

NodeView Collector

Using the above window's extension methods and a clever register/unregister of events, we can inspect Dynamo's UI tree to collect and map all NodeView instances to their corresponding NodeModel element.

using Dynamo.Controls;
using Dynamo.Wpf.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Dynamo.Graph.Nodes;
using System.Windows;
using Dynamo.ViewModels;

namespace DiagnosticToolkit.Utilities
{
    public class NodeViewCollector
    {
        private Window dynamoWindow;
        private Guid lastGuidAdded { get; set; }
        private Dictionary<Guid, NodeView> collector = new Dictionary<Guid, NodeView>();

        public NodeViewCollector(ViewLoadedParams parameters)
        {
            dynamoWindow = parameters.DynamoWindow;
            parameters.CurrentWorkspaceModel.NodeAdded += OnNodeAdded;
            parameters.CurrentWorkspaceModel.NodeRemoved += OnNodeRemoved;

            //parameters.CurrentWorkspaceChanged += OnCurrentWorkspaceChanged;
            (parameters.DynamoWindow.DataContext as DynamoViewModel).Model.WorkspaceAdded += OnCurrentWorkspaceChanged;

            this.GetAllOnCurrentWindow();
        }

        private void OnCurrentWorkspaceChanged(Dynamo.Graph.Workspaces.IWorkspaceModel workspaceModel)
        {
            workspaceModel.NodeAdded += OnNodeAdded;
            workspaceModel.NodeRemoved += OnNodeRemoved;

            this.ResetCollector();
            this.GetAllOnCurrentWindow();
        }

        #region Private Methods
        private void GetAllOnCurrentWindow()
        {
            var nodeViews = this.dynamoWindow.FindVisualChildren<NodeView>().ToList();
            foreach (var nodeView in nodeViews)
            {
                NodeModel model = nodeView.ViewModel.NodeModel;
                this.collector.Add(model.GUID, nodeView);

            }
        }

        private void ResetCollector()
        {
            this.collector = new Dictionary<Guid, NodeView>();
        }

        private void OnNodeRemoved(NodeModel nodeModel)
        {
            this.collector.Remove(nodeModel.GUID);
        }

        private void OnNodeAdded(NodeModel nodeModel)
        {
            lastGuidAdded = nodeModel.GUID;
            dynamoWindow.LayoutUpdated += DynamoWindow_LayoutUpdated;
        }

        private void DynamoWindow_LayoutUpdated(object sender, EventArgs e)
        {
            var nodeViews = this.dynamoWindow.FindVisualChildren<NodeView>().ToList();
            nodeViews.Reverse();
            foreach (var nodeView in nodeViews)
            {
                NodeModel model = nodeView.ViewModel.NodeModel;
                if (model.GUID == this.lastGuidAdded)
                {
                    this.collector.Add(model.GUID, nodeView);
                    break;
                }
            }

            dynamoWindow.LayoutUpdated -= DynamoWindow_LayoutUpdated;
        }
        #endregion

        #region Public Methods

        public List<NodeView> NodeViews
        {
            get => collector.Values.ToList();
        }

        public NodeView Get(Guid guid)
        {
            NodeView nodeView;
            if(this.collector.TryGetValue(guid, out nodeView))
            {
                return nodeView;
            }

            return null;
        }

        #endregion
    }
}

results matching ""

    No results matching ""