New to Telerik UI for WPFStart a free 30-day trial

Custom RadDiagramConnection With Additional Caps

Updated on Sep 15, 2025

Environment

Product Version2021.1.325
ProductDiagram for WPF

Description

With the current implementation of the RadDiagram control applying properties to a RadDiagramConnection element will also apply them to the Connection Cap. This is because the ConnectionLine and ConnectionCap elements are visualized by a single native Path control. To further customize the separate elements, the default implementation of the RadDiagramConnection control, would need to be extended.

Solution

To extend the base implementation of the RadDiagramConnection element, extract its default control template, and add two additional Path controls.

The following example shows the extracted and modified control template of the RadDiagramConnection element, from the Office2016 theme. The used version of the assemblies for this example is NoXaml, which allows for easier customization of the controls' templates.

XAML
	<ControlTemplate TargetType="telerik:RadDiagramConnection">
	    <Grid x:Name="RootTemplate">
	        <VisualStateManager.VisualStateGroups>
	            <VisualStateGroup x:Name="SelectionStates">
	                <VisualState x:Name="Selected"/>
	                <VisualState x:Name="SelectedInGroup">
	                    <Storyboard>
	                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="SelectedInGroupPath" Storyboard.TargetProperty="Visibility" Duration="0">
	                            <DiscreteObjectKeyFrame KeyTime="0">
	                                <DiscreteObjectKeyFrame.Value>
	                                    <Visibility>Visible</Visibility>
	                                </DiscreteObjectKeyFrame.Value>
	                            </DiscreteObjectKeyFrame>
	                        </ObjectAnimationUsingKeyFrames>
	                    </Storyboard>
	                </VisualState>
	                <VisualState x:Name="Unselected"/>
	                <VisualState x:Name="SelectedAsGroup"/>
	            </VisualStateGroup>
	            <VisualStateGroup x:Name="EditMode">
	                <VisualState x:Name="NormalMode"/>
	                <VisualState x:Name="NormalEditMode">
	                    <Storyboard>
	                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="NormalContent" Storyboard.TargetProperty="Visibility">
	                            <DiscreteObjectKeyFrame KeyTime="0">
	                                <DiscreteObjectKeyFrame.Value>
	                                    <Visibility>Collapsed</Visibility>
	                                </DiscreteObjectKeyFrame.Value>
	                            </DiscreteObjectKeyFrame>
	                        </ObjectAnimationUsingKeyFrames>
	                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="EditContent" Storyboard.TargetProperty="Visibility">
	                            <DiscreteObjectKeyFrame KeyTime="0">
	                                <DiscreteObjectKeyFrame.Value>
	                                    <Visibility>Visible</Visibility>
	                                </DiscreteObjectKeyFrame.Value>
	                            </DiscreteObjectKeyFrame>
	                        </ObjectAnimationUsingKeyFrames>
	                    </Storyboard>
	                </VisualState>
	                <VisualState x:Name="TextBoxEditMode">
	                    <Storyboard>
	                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="NormalContent" Storyboard.TargetProperty="Visibility">
	                            <DiscreteObjectKeyFrame KeyTime="0">
	                                <DiscreteObjectKeyFrame.Value>
	                                    <Visibility>Collapsed</Visibility>
	                                </DiscreteObjectKeyFrame.Value>
	                            </DiscreteObjectKeyFrame>
	                        </ObjectAnimationUsingKeyFrames>
	                        <ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetName="EditTextBox" Storyboard.TargetProperty="Visibility">
	                            <DiscreteObjectKeyFrame KeyTime="0">
	                                <DiscreteObjectKeyFrame.Value>
	                                    <Visibility>Visible</Visibility>
	                                </DiscreteObjectKeyFrame.Value>
	                            </DiscreteObjectKeyFrame>
	                        </ObjectAnimationUsingKeyFrames>
	                    </Storyboard>
	                </VisualState>
	            </VisualStateGroup>
	        </VisualStateManager.VisualStateGroups>
	        <Path x:Name="DeferredPath"
	                Stroke="{telerik:Office2016Resource ResourceKey=AccentPressedBrush}"
	                Fill="{TemplateBinding Background}"
	                StrokeThickness="{TemplateBinding StrokeThickness}"
	                StrokeDashArray="2 2"/>
	        <Path x:Name="SelectedInGroupPath"
	                Visibility="Collapsed"
	                Stroke="{telerik:Office2016Resource ResourceKey=AccentPressedBrush}"
	                StrokeThickness="{TemplateBinding StrokeThickness}"/>
	        <Path
	                Stroke="{TemplateBinding Stroke}"
	                Fill="{TemplateBinding Background}"
	                StrokeThickness="{TemplateBinding StrokeThickness}"
	                x:Name="GeometryPath"
	                StrokeDashArray="{TemplateBinding StrokeDashArray}"/>
			<!--Additional Path elements-->
	        <Path x:Name="SourceConnectionCap" Fill="{TemplateBinding Background}" 
	              Stroke="{TemplateBinding Stroke}" 
	              StrokeThickness="{TemplateBinding StrokeThickness}"/>
	        <Path x:Name="TargetConnectionCap" Fill="{TemplateBinding Background}" 
	              Stroke="{TemplateBinding Stroke}" 
	              StrokeThickness="{TemplateBinding StrokeThickness}"/>
	        <Canvas>
	            <Grid x:Name="EdittingElement">
	                <Border Background="#00FFFFFF"/>
	                <ContentPresenter x:Name="NormalContent"/>
	                <ContentPresenter x:Name="EditContent" Visibility="Collapsed" Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding EditTemplate}"/>
	                <TextBox x:Name="EditTextBox" Visibility="Collapsed" Style="{StaticResource EditTextBoxStyle}">
	                    <TextBox.InputBindings>
	                        <KeyBinding Key="Enter" Command="ApplicationCommands.NotACommand"/>
	                    </TextBox.InputBindings>
	                </TextBox>
	            </Grid>
	        </Canvas>
	    </Grid>
	</ControlTemplate>

Create a class that derives from the RadDiagramConnection class and implement the following logic for handling the two additional path elements.

C#
	public class CustomConnection : RadDiagramConnection
	{
	    private Path sourceConnectionCap;
	    private Path targetConnectionCap;

	    public override void OnApplyTemplate()
	    {
	        base.OnApplyTemplate();
	        this.sourceConnectionCap = (Path)this.GetTemplateChild("SourceConnectionCap");
	        this.targetConnectionCap = (Path)this.GetTemplateChild("TargetConnectionCap");
	        this.UpdateGeometryOverride();
	    }

	    protected override Geometry CreateGeometry(BridgeType bridgeType, bool roundedCorners)
	    {
	        this.AddConnectionCaps();
	        return base.CreateGeometry(bridgeType, roundedCorners);
	    }

	    private void AddConnectionCaps()
	    {
	        if (this.sourceConnectionCap != null && this.targetConnectionCap != null)
	        {
	            var sourcePoint = this.StartPoint.Substract(this.Position);
	            var targetPoint = this.EndPoint.Substract(this.Position);
	            var transformedPoints = this.TranslateConnectionPoints(false).ToList();

	            Point sourceCapSecondPoint;
	            Point targetCapSecondPoint;
	            if (this.ConnectionType == ConnectionType.Spline)
	            {
	                var points = new List<Point> { sourcePoint };
	                points.AddRange(transformedPoints);
	                points.Add(targetPoint);
	                GeometryExtensions.GetSplineFigureTangents(points, out 	sourceCapSecondPoint, out targetCapSecondPoint);
	            }
	            else
	            {
	                sourceCapSecondPoint = transformedPoints.Count == 0 ? targetPoint : 	transformedPoints[0];
	                targetCapSecondPoint = transformedPoints.Count == 0 ? sourcePoint : 	transformedPoints[transformedPoints.Count - 1];
	            }

	            if (this.SourceCapType != CapType.None)
	            {
	                this.sourceConnectionCap.Data = CreateSourceCapGeometryData(sourcePoint, 	sourceCapSecondPoint, ref sourcePoint);
	            }
	            if (this.TargetCapType != CapType.None)
	            {
	                this.targetConnectionCap.Data = CreateTargetCapGeometryData(targetPoint, 	targetCapSecondPoint, ref targetPoint);
	            }
	        }
	    }

	    private Geometry CreateSourceCapGeometryData(Point startPoint, Point endPoint, ref 	Point baseLineStart)
	    {
	        var geometry = new PathGeometry();
	        geometry.Figures.Add(this.CreateSourceCapGeometry(startPoint, endPoint, ref 	baseLineStart));
	        return geometry;
	    }

	    private Geometry CreateTargetCapGeometryData(Point startPoint, Point endPoint, ref 	Point baseLineStart)
	    {
	        var geometry = new PathGeometry();
	        geometry.Figures.Add(this.CreateTargetCapGeometry(startPoint, endPoint, ref 	baseLineStart));
	        return geometry;
	    }
	}

The result is a custom connection element, which can be used both in Xaml and in code.

XAML
	<telerik:RadDiagram>
		<!--values set for the new custom Path properties-->
	    <local:CustomConnection StartPoint="400, 100" 
	                            EndPoint="200, 100"
	                            SourceCapType="Arrow2" 
	                            TargetCapType="Arrow4Filled"                                     
								SourceCapSize="6, 6" 
	                            TargetCapSize="6, 6"/>
	</telerik:RadDiagram>
C#
	var connection = new CustomConnection() 
    {
        SourceCapType = Telerik.Windows.Diagrams.Core.CapType.Arrow1,
        TargetCapType = Telerik.Windows.Diagrams.Core.CapType.Arrow4Filled,
        StartPoint = new Point(400, 100),
        EndPoint = new Point(200, 100)
    };

    this.diagram.Items.Add(connection);

Figure 1: Result

Custom ConnectionLine

In this article
EnvironmentDescriptionSolution
Not finding the help you need?
Contact Support