ZeroTouch run a method after node has been executed

Hello guys,

I created a node that export to excel. If I connect more worksheets and list it is getting automatically repeated. After final launch I would like to open Excel if “ShowExcel” == True but it opens excel after first writing the first sheet.
I have a class ExcelExport:
it returns WriteData(filePath, worksheet,data)

Hi @Deniz_Maral ,

Maybe you could create a dummy WriteToExcel node (set to OpenExcel = True) together with a “Passthrough/WaitFor” node. Then all the Writing has already happened before it opens the Excel.

No, it won’t work because ZT get laced in terms of list that you connected.

Maybe it will help to change the worksheet and data input to a list.

1 Like

Perhaps have one node to write, and a second node to open? This would allow the write to occur and then pass the file name onto the ‘open’ node.

1 Like

That was my workaround-solution but it would be nice to controll it directly in one node.

There are Methods/Events on the NodeModel that are called when the Node has begun and ended execution but I don’t think you can use these in a ZT context. I don’t know ZT Nodes very well so could be wrong.

However, you could create a very simple View Extension where you start watching any node of interest when the workspace is opened and then do whatever when it has been Executed. Here is a very simple VE to watch nodes based on their CreationName. This is untested, it’s more to show roughly how you would go about it…

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;

using Dynamo.Graph.Nodes;
using Dynamo.ViewModels;
using Dynamo.Wpf.Extensions;

public class NodeWatcherViewExtension : IViewExtension
{
    private DynamoNodeEventListener m_nodeWatcher;

    public string UniqueId => Guid.NewGuid().ToString();
    public string Name => GetType().Name;

    public void Startup(ViewStartupParams p) { }

    public void Loaded(ViewLoadedParams p)
    {
        m_nodeWatcher = new DynamoNodeEventListener(p, new List<string>() { "HelloWorld", "MyAwesomeNode" });
    }

    public void Dispose() { }
    public void Shutdown() { }
}

public class DynamoNodeEventListener
{
    /// <summary>
    /// A reference to the <see cref="DynamoViewModel"/>.
    /// </summary>
    private DynamoViewModel m_dynamoViewModel;

    private bool _isShowingStartPage = true;
    /// <summary>
    /// If the StartPage is being shown.
    /// </summary>
    /// <remarks>
    /// If false then a workspace is open.
    /// </remarks>
    public bool IsShowingStartPage
    {
        get => _isShowingStartPage;
        private set
        { 
            if (value != _isShowingStartPage)
            {
                _isShowingStartPage = value;
                OnStartPageVisibilityChanged(value);
            }
        }
    }

    /// <summary>
    /// List of Node Names to watch.
    /// </summary>
    private List<string> m_nodesToWatch = new List<string>();

    /// <summary>
    /// List of NodeModels actively being watched.
    /// </summary>
    private List<NodeModel> m_nodesBeingWatched = new List<NodeModel>();

    /// <summary>
    /// Construct an instance of this <see cref="DynamoNodeEventListener"/>
    /// </summary>
    /// <param name="loadedParams"><see cref="ViewLoadedParams"/> which can be found in the concrete <see cref="IViewExtension"/> class.</param>
    /// <param name="nodesToWatch">List of Node Names to watch when a Workspace is opened.</param>
    public DynamoNodeEventListener(ViewLoadedParams loadedParams, List<string> nodesToWatch)
    {
        // Get DynamoViewModel...
        m_dynamoViewModel = (DynamoViewModel)loadedParams.DynamoWindow.DataContext;

        // Listen to DynamoViewModel Property Changed events. We need this to know when a workspace is being opened/closed...
        m_dynamoViewModel.PropertyChanged += OnDynamoViewModelPropertyChanged;

        // Store list of node names to watch...
        if (nodesToWatch != null)
            m_nodesToWatch = nodesToWatch;
    }

    /// <summary>
    /// Called when the <see cref="DynamoViewModel"/>.PropertyChanged event is triggered.
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private void OnDynamoViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        // Check if this was the ShowStartPage Property, if so then update the IsShowingStartPage value...
        if (e.PropertyName == nameof(m_dynamoViewModel.ShowStartPage))
            IsShowingStartPage = m_dynamoViewModel.ShowStartPage;
    }

    /// <summary>
    /// When <see cref="IsShowingStartPage"/> value changed.
    /// </summary>
    /// <param name="isVisible"></param>
    private void OnStartPageVisibilityChanged(bool isVisible)
    {
        // If the StartPage is Visible, then we want to unsubscibe events from any nodes being watched...
        if (isVisible)
        {
            if (m_nodesBeingWatched.Count > 0)
            {
                foreach (var nm in m_nodesBeingWatched)
                    UnsubscribeToNodeEvents(nm);

                m_nodesBeingWatched.Clear();
            }
        }
        // If the StartPage is not Visible, then we want to find and Subscibe events to any nodes that have a Creation Name in the Node Name List...
        else
        {
            if (m_nodesToWatch.Count == 0)
                return;

            var ws = m_dynamoViewModel.CurrentSpace;

            var foundWatchableNodes = ws.Nodes.Where(n => m_nodesToWatch.Any(x => string.Equals(x, n.CreationName, StringComparison.OrdinalIgnoreCase))).ToList();

            if (foundWatchableNodes == null || foundWatchableNodes.Count == 0)
                return;

            foreach (var n in foundWatchableNodes)
                WatchNode(n);
        }
    }

    /// <summary>
    /// Start Watching a given NodeModel.
    /// </summary>
    /// <param name="nm"></param>
    public void WatchNode(NodeModel nm)
    {
        if (m_nodesBeingWatched.Contains(nm))
            return;

        SubscribeToNodeEvents(nm);
        m_nodesBeingWatched.Add(nm);
    }

    /// <summary>
    /// Stop watching a given NodeModel.
    /// </summary>
    /// <param name="nm"></param>
    public void UnwatchNode(NodeModel nm)
    {
        if (!m_nodesBeingWatched.Contains(nm))
            return;

        UnsubscribeToNodeEvents(nm);
        m_nodesBeingWatched.Remove(nm);
    }

    /// <summary>
    /// Subscibe to <see cref="NodeModel"/> Events.
    /// </summary>
    /// <param name="nm"></param>
    private void SubscribeToNodeEvents(NodeModel nm)
    {
        nm.NodeExecutionBegin += OnNodeExecutionBegan;
        nm.NodeExecutionEnd += OnNodeExecutionEnded;
    }

    /// <summary>
    /// Unsubscribe to <see cref="NodeModel"/> Events.
    /// </summary>
    /// <param name="nm"></param>
    private void UnsubscribeToNodeEvents(NodeModel nm)
    {
        nm.NodeExecutionBegin -= OnNodeExecutionBegan;
        nm.NodeExecutionEnd -= OnNodeExecutionEnded;
    }

    /// <summary>
    /// When <see cref="NodeModel"/> has begun Executing.
    /// </summary>
    /// <param name="obj"></param>
    private void OnNodeExecutionBegan(NodeModel nm)
    {
        // Do your thing...
    }

    /// <summary>
    /// When <see cref="NodeModel"/> has been Executed.
    /// </summary>
    /// <param name="nm"></param>
    private void OnNodeExecutionEnded(NodeModel nm)
    {
        // Do your thing...
    }
}

Maybe this helps.

Cheers,
Dan

2 Likes

Thank you very much Daniel for your reply! I think I will create a custom node and passtrough the node and open it with standart program. It will affect performance but it is what it is… :slight_smile:

It would be very cool to place a button on ZT node to open file. That can be the solution but I don’t know how to do it.
@Thomas_Mahon do you have any suggestion on that? Thanks :slight_smile:

I tried that in the begining but then I have to change whole code because of looping.

No probs, check out Dynamo Unchained as this goes into a little more depth with WPF and binding…

2 Likes

Entering a list does indeed create a loop, but then you know exactly when the loop is finished. After that you can open the file.

A second node that @jacob.small suggest could work, but I assume it will open the file as many times as you create worksheets, unless you filter the results in advance (unique item or so).

1 Like

In my mind it would be a node which takes a list as the input - similar to what you discussed here, but I thought it might be easier to rewrite one node than a full code base (there is value in opening a document of any type after things are done after all).

The core extension idea is really interesting in a lot of ways though:

  • What if you could sync of save after finishing a run?
  • What if you could automatically select all created items after a run?
  • Could you start auto-executing other automations (have the code block open another DYN in Dynamo Player) by just placing a code block?

The creative application a of tech on the forum always amazes me.

@Deniz_Maral I can see one major pitfall with what you are trying to achieve: what happens if the user has multiple instances of your node all executing in the graph and exporting different sets of excel spread sheets. How would you know at what point in time when to show excel and for which node instance?

I’m guessing it will be impossible for you to know which sequence of inputs have executed and completed from one node instance over another since Dynamo executes via a DAG, and each execution pass will execute all your nodes each time. It seems more sensible to write your node to take a list input and then iterate it internally rather than allowing Dynamo to replicate so you have more control, then you can show excel after you’ve iterated the collection. Even with a list input the user can still input a single input into the node so you don’t loose anything by requiring a list as an input.

Lastly, a custom app or macro might be a more suitable option for your problem rather than Dynamo who’s behaviour makes what is otherwise an easy thing to do far more complicated.

4 Likes

Thank you very much for your reply, Thomas!
That is the point because Dynamo opens Excel after first loop. The sheets and data are in the file stored but you have to open it in read-only mode. I will try to change it to list input. I will then have to change all logic of code :slight_smile:
I will let you know, if it works.

I changed my code in that way and now it works :slight_smile:
Thank you to all!

1 Like

Glad it helped :slight_smile:

1 Like