Skip to content
Miroiu Emanuel edited this page Sep 26, 2022 · 12 revisions

It is important to understand how components are named and what's their role in the visual tree of the editor to understand the code and the documentation.

Hierarchy and terminology

The root component is an editor which holds nodes and connections together with a few additional UI elements such as a selection rectangle and a pending connection in order to make the editor interactive.

Nodes are containers for connectors or the node itself can be a connector (e.g. State Node).

Connectors can create pending connections that can become real connections when completed.

A picture is worth a thousand words

nodes-hierarchy

Content Layers

You may wonder how a node can be a connector itself and still behave like a normal node. The editor contains three big layers which help solve this problem:

  1. The items layer (NodifyEditor.ItemsSource) - here, each control is wrapped inside an Item Container making it selectable, draggable, etc and it is possible to have any control rendered (e.g a connector, a text block).
  2. The connections layer (NodifyEditor.Connections) - this is where all the connections coexist and are rendered behind the items layer by default
  3. The decorators layer (NodifyEditor.Decorators) - here, each control is given a location inside the graph

Having those layers separated enables the possibility of asynchronously loading each one of them.

Using an existing theme

Merge one of the following themes into your resource dictionary in App.xaml:

  • Dark theme (default theme if not specified):
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Dark.xaml" />
  • Light theme:
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Light.xaml" />
  • Nodify theme:
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Nodify.xaml" />

A minimal example

Import the nodify namespace: xmlns:nodify="https://miroiu.github.io/nodify" or xmlns:nodify="clr-namespace:Nodify;assembly=Nodify" in your file and create an instance of the editor <nodify:NodifyEditor />. If you start the application, you will see an empty space where you can create a selection rectangle.

Tip: Drag the selection rectangle near the edge of the editor area and the screen will automatically move in that direction.

Adding nodes

Now we're going to display a few nodes. Let's create the viewmodels and bind them to the view.

public class NodeViewModel
{
    public string Title { get; set; }
}

public class EditorViewModel
{
    public ObservableCollection<NodeViewModel> Nodes { get; } = new ObservableCollection<NodeViewModel>();

    public EditorViewModel()
    {
        Nodes.Add(new NodeViewModel { Title = "Welcome" });
    }
}

The view model can be of any shape, but the view for the node is generated by the ItemTemplate. (The same result would be achieved by having the DataTemplate inside NodifyEditor.Resources)

<nodify:NodifyEditor ItemsSource="{Binding Nodes}">
    <nodify:NodifyEditor.DataContext>
        <local:EditorViewModel />
    </nodify:NodifyEditor.DataContext>

    <nodify:NodifyEditor.ItemTemplate>
        <DataTemplate DataType="{x:Type local:NodeViewModel}">
            <nodify:Node Header="{Binding Title}" />
        </DataTemplate>
    </nodify:NodifyEditor.ItemTemplate>
    
</nodify:NodifyEditor>

Notice how we bind the Header of the Node to display the Title. For more node types and customization please check out the Nodes overview.

Connecting nodes

Alright, now let's add more nodes and connect them together. First, we need a representation of a connector and some collections on the node to store our connectors.

public class ConnectorViewModel
{
    public string Title { get; set; }
}

public class NodeViewModel
{
    public string Title { get; set; }

    public ObservableCollection<ConnectorViewModel> Input { get; set; } = new ObservableCollection<ConnectorViewModel>();
    public ObservableCollection<ConnectorViewModel> Output { get; set; } = new ObservableCollection<ConnectorViewModel>();
}

public class EditorViewModel
{
    public ObservableCollection<NodeViewModel> Nodes { get; } = new ObservableCollection<NodeViewModel>();

    public EditorViewModel()
    {
        Nodes.Add(new NodeViewModel
        {
            Title = "Welcome",
            Input = new ObservableCollection<ConnectorViewModel>
            {
                new ConnectorViewModel
                {
                    Title = "In"
                }
            },
            Output = new ObservableCollection<ConnectorViewModel>
            {
                new ConnectorViewModel
                {
                    Title = "Out"
                }
            }
        });
    }
}

And bind them to the view.

<nodify:Node Header="{Binding Title}"
             Input="{Binding Input}"
             Output="{Binding Output}">
  <nodify:Node.InputConnectorTemplate>
      <DataTemplate DataType="{x:Type local:ConnectorViewModel}">
          <nodify:NodeInput Header="{Binding Title}" />
      </DataTemplate>
  </nodify:Node.InputConnectorTemplate>
  
  <nodify:Node.OutputConnectorTemplate>
      <DataTemplate DataType="{x:Type local:ConnectorViewModel}">
          <nodify:NodeOutput Header="{Binding Title}" />
      </DataTemplate>
  </nodify:Node.OutputConnectorTemplate>
</nodify:Node>

The Node control supports Input and Output connectors and the way you customize these is by overwriting the default template for the InputConnectorTemplate, respectively the OutputConnectorTemplate.

Clicking and dragging a wire from the Input or Output connector will create a pending connection that we can transform into a real connection.

Let's create the ViewModel for the connection.

public class ConnectionViewModel
{
    public ConnectorViewModel Source { get; set; }
    public ConnectorViewModel Target { get; set; }
}

Drawing a grid

Drawing a simple grid is just a matter of creating a grid brush, applying the editor transform to it, and using the brush as the Background of the editor.

Because the grid we are drawing is made of lines and is not filled, the Background of the editor will have some transparency, meaning that we'll see the background color of the control below. To solve this, wrap the editor in a Grid and set its Background or set the Background of the Window.

Use the ViewportTransform dependency property to have the grid move with the view.

Note: The example uses static resources which are provided by the selected theme in App.xaml.

<Window x:Class="MyProject.MainWindow"
        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:nodify="https://miroiu.github.io/nodify"
        mc:Ignorable="d">

    <Window.Resources>
        <GeometryDrawing x:Key="SmallGridGeometry"
                        Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
                        Brush="{StaticResource NodifyEditor.SelectionRectangleBackgroundBrush}" />

        <GeometryDrawing x:Key="LargeGridGeometry"
                        Geometry="M0,0 L0,1 0.015,1 0.015,0.015 1,0.015 1,0 Z"
                        Brush="{StaticResource NodifyEditor.SelectionRectangleBackgroundBrush}" />

        <DrawingBrush x:Key="SmallGridLinesDrawingBrush"
                    TileMode="Tile"
                    ViewportUnits="Absolute"
                    Viewport="0 0 20 20"
                    Transform="{Binding ViewportTransform, ElementName=Editor}"
                    Drawing="{StaticResource SmallGridGeometry}" />

        <DrawingBrush x:Key="LargeGridLinesDrawingBrush"
                    TileMode="Tile"
                    ViewportUnits="Absolute"
                    Opacity="0.5"
                    Viewport="0 0 100 100"
                    Transform="{Binding ViewportTransform, ElementName=Editor}"
                    Drawing="{StaticResource LargeGridGeometry}" />
    </Window.Resources>

    <Grid Background="{StaticResource NodifyEditor.BackgroundBrush}">
        <nodify:NodifyEditor x:Name="Editor" Background="{StaticResource SmallGridLinesDrawingBrush}" />

        <Grid Background="{StaticResource LargeGridLinesDrawingBrush}"
              Panel.ZIndex="-2" />
    </Grid>
</Window>

Tip: Right-click and drag the screen around to move the view and use the mouse wheel to zoom in and out.

Clone this wiki locally