Disallow circular connections

Depending on your application you might face the situation, where you need to deny circular connections on your diagram. What do I understand under circular connections? I understand connection-pathes, which, when you follow the connections, you never find an end. I think the screenshot below explains it pretty well.

Screenshot of Circular Connection in WPF Diagram Designer

In my case I’m working on an ETL/Workflow-tool and in certain use-cases the user is not allowed to build such circular connections, as the flow would never end (and run forever).

Implementation of the functionality

Now the first thing we need to find out, is where in the WPF Diagram Designer code we can implement this functionality. If you think about, the moment is then, when we move with our mouse cursor over the connector-adorner. So let’s open the file ConnectorAdorner.cs! In there you can see, that we have already the OnMouseMove-event, which is exactly, what we’re looking for. Please find the line

HitTesting(e.GetPosition(this));

The HitTesting-method checks, if the connector has been hit, or the DesignerItem itself. If the connector has been hit, the HitConnector-variable is set, otherwise it is set to null. This means, if the connector is not null, we must check, if the connection is circular or not! Please see and add right below the HitTestmethod the following:

if (HitConnector != null)
{
	if (IsCircularConnection(hitConnector))
	{
		this.Cursor = Cursors.No;
	}
}

Your’re right, the IsCircularConnection-method is still missing, so let’s implement it. The first part of this method gets all the leaving connections from the DesignerItem we try to connect to. Then it iterates over them and calls the recursive method IsCircularPath, which follows all connections to see, if they return to our originating DesignerItem. If it does, it returns true, so that we know the whole path is circular! The following code implements these to methods and must be added to the ConnctionAdorner-class as well:

private bool IsCircularConnection(Connector hitConnector)
{
	IEnumerable<Connection> connectionList = this.designerCanvas.Children.OfType<Connection>();

	//Get all connections, which are leaving from hit connector
	foreach (Connection connection in connectionList.
				Where(t=>
 					t.Source.ParentDesignerItem.ID ==
					hitConnector.ParentDesignerItem.ID))
	{
		bool isCircular = IsCircularPath(connectionList, this.sourceConnector.ParentDesignerItem.ID, connection);
		if (isCircular == true)
		{
			return true;
		}
	}

	return false;
}

private bool IsCircularPath(IEnumerable<Connection> connectionList, Guid sourceDesignerItemId, Connection connection)
{
	if (connection.Sink.ParentDesignerItem.ID == sourceDesignerItemId)
	{
		return true;
	}
	else
	{
		foreach (Connection con in connectionList.
				Where(t =>
					t.Source.ParentDesignerItem.ID ==
					connection.Sink.ParentDesignerItem.ID))
		{
			return IsCircularPath(connectionList, sourceDesignerItemId, con);
		}
	}

	return false;
}

Now you can start the Diagram Designer and draw a circular connection. If you try to connect the last connection, your cursor should change to a forbidden/no-symbol. Right at the moment you can still connect it, despite the no-symbol showing up. The reason is, we need to prevent, that the connection is beeing added. This happens in the OnMouseUp-method. Please change this method from

protected override void OnMouseUp(MouseButtonEventArgs e)
{
	if (HitConnector != null)

to

protected override void OnMouseUp(MouseButtonEventArgs e)
{
	if (HitConnector != null && this.Cursor != Cursors.No)

Congrats, your Diagram Designer does now no longer allow circular connections!

What can you do further with what we’ve learned here? Well think about, that you probably do not only want to disallow circular connections, but probably also that one certain type of DesignerItem is not allowed to connect to a spefic other type of DesignerItem. In my case of an ETL-tool it is i.e. not allowed to connect from targets to sources, but only the other way round. Make sense huh? To do this you would replace (or add to) the IsCircularConnection-method with a method like IsTypeAallowToConnectTypeB. Hope this gave you a good insight to the further power of this little tutorial!