5.4 - Events
On this final section, we'll learn what Events are by creating a simple WPF ViewExtension that tracks changes on Dynamo's canvas.
An event in C# is an occurrence of an action that can be acted upon. Events can be raised by an user when he/she clicks, moves the mouse, presses a key, or programmatically by other means, like for example tracking stock market prices, and event can be automatically raised when the price reaches a predefined value.
Tracking events on Dynamo
This example is taken from the Dynamo Developer Workshop from AU Vegas 2019
Let's now explore another very useful functionality available via the ViewStartupParams
while making use of the MVVM pattern and data binding.
With ViewStartupParams.CurrentWorkspace
we can access several events:
viewLoadedParams.CurrentWorkspaceChanged
viewLoadedParams.NotificationRecieved
viewLoadedParams.CurrentWorkspaceModel.NodeAdded
viewLoadedParams.CurrentWorkspaceModel.NodeRemoved
viewLoadedParams.CurrentWorkspaceModel.ConnectorAdded
viewLoadedParams.CurrentWorkspaceModel.ConnectorDeleted
The View
Let's edit our ViewExtension to make use of them. Create a NodeTracker.xaml
window which will contain the list of our tracked events. This will be our View.
<Window x:Class="HelloDynamo.NodeTracker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:HelloDynamo"
mc:Ignorable="d"
Title="NodeTracker" Height="450" Width="400">
<Grid>
<ListView ItemsSource="{Binding Actions}"></ListView>
</Grid>
</Window>
Note: On
xmlns:local
, make sure to use the namespace yourIViewExtension
is declared.
Where NodeTracker.xaml.cs
looks like:
using System.Windows;
namespace HelloDynamo
{
/// <summary>
/// Tracks and displays events for node/connector added/removed
/// </summary>
public partial class NodeTracker : Window
{
public NodeTracker()
{
InitializeComponent();
this.Closing += NodeTracker_Closing;
}
private void NodeTracker_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
//hide window instead of closing it, so we keep tracking in the background
e.Cancel = true;
this.Hide();
}
}
}
The ViewModel
Now let's crate a ViewModel that will bind to the window and will keep track of the changes to the CurrentWorkspaceModel
, create a new class NodeTrackerViewModel.cs
:
using System;
using System.Collections.ObjectModel;
using Dynamo.Core;
using Dynamo.Extensions;
namespace HelloDynamo
{
public class NodeTrackerViewModel: NotificationObject, IDisposable
{
private ReadyParams readyParams;
private ObservableCollection<string> _actions = new ObservableCollection<string> ();
public ObservableCollection<string> Actions
{
get
{
return _actions;
}
set
{
_actions = value;
RaisePropertyChanged("Actions");
}
}
public NodeTrackerViewModel(ReadyParams p)
{
readyParams = p;
RegisterEvents();
}
private void RegisterEvents()
{
//subscribing to dynamo events
readyParams.CurrentWorkspaceModel.NodeAdded += CurrentWorkspaceModel_NodeAdded;
readyParams.CurrentWorkspaceModel.NodeRemoved += CurrentWorkspaceModel_NodeRemoved;
readyParams.CurrentWorkspaceModel.ConnectorAdded += CurrentWorkspaceModel_ConnectorAdded;
readyParams.CurrentWorkspaceModel.ConnectorDeleted += CurrentWorkspaceModel_ConnectorDeleted;
//making sure the binding is updated when elements are added and removed
Actions.CollectionChanged += Actions_CollectionChanged;
}
private void UnregisterEvents()
{
readyParams.CurrentWorkspaceModel.NodeAdded -= CurrentWorkspaceModel_NodeAdded;
readyParams.CurrentWorkspaceModel.NodeRemoved -= CurrentWorkspaceModel_NodeRemoved;
readyParams.CurrentWorkspaceModel.ConnectorAdded -= CurrentWorkspaceModel_ConnectorAdded;
readyParams.CurrentWorkspaceModel.ConnectorDeleted -= CurrentWorkspaceModel_ConnectorDeleted;
}
private void Actions_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
RaisePropertyChanged("Actions");
}
private void CurrentWorkspaceModel_ConnectorDeleted(Dynamo.Graph.Connectors.ConnectorModel obj)
{
Actions.Add($"Connector between port {obj.Start.Name} and {obj.End.Name} deleted");
}
private void CurrentWorkspaceModel_ConnectorAdded(Dynamo.Graph.Connectors.ConnectorModel obj)
{
Actions.Add($"Connector between port {obj.Start.Name} and {obj.End.Name} added");
}
private void CurrentWorkspaceModel_NodeRemoved(Dynamo.Graph.Nodes.NodeModel obj)
{
Actions.Add($"Node {obj.Name} deleted");
}
private void CurrentWorkspaceModel_NodeAdded(Dynamo.Graph.Nodes.NodeModel obj)
{
Actions.Add($"Node {obj.Name} created");
}
public void Dispose()
{
UnregisterEvents();
}
}
}
Each time a node or connector is added or removed we log that event by adding a string to our Actions
collection, in this case an ObservableCollection
so that we can bind it to the ListView
in the NodeTracker
window.
The Model
Finally let's edit ViewExtensionExample.cs
to start the tracking and to open the window when the user clicks on a menu item:
using Dynamo.Wpf.Extensions;
using System;
using System.Windows;
using System.Windows.Controls;
namespace HelloDynamo
{
/// <summary>
/// Dynamo View Extension that can control both the Dynamo application and its UI (menus, view, canvas, nodes).
/// </summary>
public class ViewExtensionExample : IViewExtension
{
public string UniqueId => "5E85F38F-0A19-4F24-9E18-96845764780C";
public string Name => "Hello Dynamo View Extension";
private MenuItem extensionMenu;
private ViewLoadedParams viewLoadedParams;
private NodeTracker _nodeTracker = null;
public void Startup(ViewStartupParams vsp) { }
public void Loaded(ViewLoadedParams vlp)
{
viewLoadedParams = vlp;
//instanciating the window and setting the datacontext to bind it to the viewmodel
var viewModel = new NodeTrackerViewModel(viewLoadedParams);
_nodeTracker = new NodeTracker
{
Owner = viewLoadedParams.DynamoWindow,
DataContext = viewModel
};
MakeMenuItems();
}
public void MakeMenuItems()
{
extensionMenu = new MenuItem { Header = "AU Workshop" };
var sayHelloMenuItem = new MenuItem { Header = "Say Hello" };
sayHelloMenuItem.Click += (sender, args) =>
{
MessageBox.Show("Hello " + Environment.UserName);
};
//new menu item for our node tracker
var nodeTrackerMenuItem = new MenuItem { Header = "Node Tracker" };
nodeTrackerMenuItem.Click += (sender, args) =>
{
_nodeTracker.Show();
};
extensionMenu.Items.Add(sayHelloMenuItem);
extensionMenu.Items.Add(nodeTrackerMenuItem);
viewLoadedParams.dynamoMenu.Items.Add(extensionMenu);
}
public void Shutdown() { }
public void Dispose() { }
}
}