This is a migrated thread and some comments may be shown as answers.

Custom RadDiagramConnection with rotated text

8 Answers 106 Views
Diagram
This is a migrated thread and some comments may be shown as answers.
Valentin
Top achievements
Rank 1
Iron
Iron
Valentin asked on 06 Feb 2018, 10:10 AM

Hello Telerik,

 

I'm using a RadDiagram with a custom template for RadDiagramConnection. The function of this template is to rotate some text linked to the connector.

The following code is the style of my custom RadDiagramConnection :

<Style TargetType="diagramscontrols:RadDiagramConnection" x:Key="RadDiagramConnectionStyle_RotateText">
  <!--<Setter Property="Background" Value="{StaticResource DiagramShape_Connection_Background}"/>
  <Setter Property="Stroke" Value="{StaticResource DiagramShape_Connection_BorderBrush}"/>-->
  <Setter Property="StrokeThickness" Value="1"/>
  <!--<Setter Property="ZIndex" Value="{StaticResource connectionZIndex}"/>-->
  <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
  <Setter Property="VerticalContentAlignment" Value="Stretch"/>
  <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="diagramscontrols:RadDiagramConnection">
        <Grid x:Name="RootTemplate" MinHeight="0" HorizontalAlignment="Stretch">
          <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="{TemplateBinding Stroke}"
                Opacity="0.7"
                Fill="{TemplateBinding Background}"
                StrokeThickness="{TemplateBinding StrokeThickness}"
                StrokeDashArray="2 2"/>
          <Path x:Name="SelectedInGroupPath" Visibility="Collapsed" Stroke="{StaticResource DiagramShape_Selected_BorderBrush}" />
          <Path Stroke="{TemplateBinding Stroke}"
                Fill="{TemplateBinding Background}"
                StrokeThickness="{TemplateBinding StrokeThickness}"
                x:Name="GeometryPath"
                StrokeDashArray="{TemplateBinding StrokeDashArray}"/>
          <Grid x:Name="EdittingElement" RenderTransformOrigin="0.5 0.5" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
            <!--TextBlock.TextAlignment="{Binding Tag.TextAlignement, RelativeSource={RelativeSource AncestorType=diagramscontrols:RadDiagramConnection}}"-->
            <Grid.RenderTransform>
              <RotateTransform>
                <RotateTransform.Angle>
                  <MultiBinding Converter="{StaticResource BoundsToAngleConverter}">
                    <Binding Path="StartPoint" RelativeSource="{RelativeSource AncestorType=diagramscontrols:RadDiagramConnection}" />
                    <Binding Path="EndPoint" RelativeSource="{RelativeSource AncestorType=diagramscontrols:RadDiagramConnection}" />
                  </MultiBinding>
                </RotateTransform.Angle>
              </RotateTransform>
            </Grid.RenderTransform>
            <Border Background="Transparent"/>
            <TextBlock x:Name="NormalContent" Foreground="{TemplateBinding Foreground}"
                       FontFamily="{TemplateBinding FontFamily}"
                       FontSize="{TemplateBinding FontSize}"
                       Text="{TemplateBinding Content}"
                       TextAlignment="{Binding Tag.TextAlignement, RelativeSource={RelativeSource AncestorType=diagramscontrols:RadDiagramConnection}}"
                       HorizontalAlignment="{Binding Tag.HorizontalTextAlignement, RelativeSource={RelativeSource AncestorType=diagramscontrols:RadDiagramConnection}}"
                       VerticalAlignment="Center" />
 
 
            <!--<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_Connection}" >
              <TextBox.InputBindings>
                <KeyBinding Key="Enter" Command="ApplicationCommands.NotACommand"/>
              </TextBox.InputBindings>
            </TextBox>
          </Grid>
        </Grid>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

 

However, I want that the text will be above of the connector. Moreover, I have some bad behaviors depending on the position of the Connector. I attached a file (arrowTexts) to show you these bad behaviors. Tell me if you want more examples.

 

I want to know if you can help me to find a solution to have the good text position.

 

Thank you very much !

8 Answers, 1 is accepted

Sort by
0
Martin Ivanov
Telerik team
answered on 09 Feb 2018, 09:36 AM
Hello Valentin,

To place the content (the text) of the connection above the connection line you can offset the content using the Margin of the TextBlock element that holds it ("NormalContent"). You can calculate the Margin based on the rotation angle applied. For the angle of 0 degrees you can apply a top margin that equals the size of the TextBlock. Otherwise, half of the size.

To fix the issue where the text goes too far way from the connection line you can replace the RendertTrasnform with LayoutTransform.

I attached a project based on your code that shows this approach. I hope it helps.

Regards,
Martin Ivanov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
0
Valentin
Top achievements
Rank 1
Iron
Iron
answered on 12 Feb 2018, 08:13 AM

Hi Martin,

 

I have the same behavior like you, so, it's working find !

 

I have a little other question, but it's not a problem : Actually, when a connector is linked to 2 squares (for exemple), the text is automatically replaced when I'm moving one square. But, when I'm using a connector linked to nothing, when I'm moving the start or end point, the text is only replaced when the connector is at it new position => This is not dynamically, not depending on the movement.

 

Do you know how I can have the same behavior for a linked connector and a non-linked connector ?

 

Thank you very much.

0
Martin Ivanov
Telerik team
answered on 14 Feb 2018, 03:46 PM
Hello Valentin,

The StartPoint and EndPoint of the connection will be updated while dragging the target or the source shape. If you don't have a target or source, the Start/EndPoint will be updated when you drop the connection. This is why the converter doesn't kick-in. To make this work you can provide a third binding in the multi binding that points to the Data property of the Path with x:Name set to "DeferredPath".
<Grid.LayoutTransform>
    <RotateTransform>
        <RotateTransform.Angle>
            <MultiBinding Converter="{StaticResource BoundsToAngleConverter}">
                <Binding Path="StartPoint" RelativeSource="{RelativeSource AncestorType=telerik:RadDiagramConnection}" />
                <Binding Path="EndPoint" RelativeSource="{RelativeSource AncestorType=telerik:RadDiagramConnection}" />
                <Binding ElementName="DeferredPath" Path="Data" />
            </MultiBinding>
        </RotateTransform.Angle>
    </RotateTransform>
</Grid.LayoutTransform>
Then in the converter you can check if the Data is not. In that case replace the start and end points with the one from the geometry that comes from the Data.
public class BoundsToAngleConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {            var p1 = (Point)values[0];
        var p2 = (Point)values[1];
 
        var defferedGeometry = values[2] as PathGeometry;
        if (defferedGeometry != null)
        {
            var figure = (PathFigure)defferedGeometry.Figures[0];
            var lineSegment = (LineSegment)figure.Segments[0];
            p1 = figure.StartPoint;
            p2 = lineSegment.Point;
        }
 
        var angleDeg = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X) * 180 / Math.PI;
        if (angleDeg == 180)
        {
            angleDeg = 0;                    
        }
        return angleDeg;
    }
 
    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}
Note that in this case the content won't be positioned properly because the bounds of the connection are not updated according the deferred path. Here you will need to implement some logic that offsets the content properly.

Regards,
Martin Ivanov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
0
Valentin
Top achievements
Rank 1
Iron
Iron
answered on 01 Mar 2018, 09:02 AM

Hello Martin,

 

Thank you for this behaviour !

0
Valentin
Top achievements
Rank 1
Iron
Iron
answered on 06 Mar 2018, 10:24 AM

Hello,

 

During my tests, I've seen that there are 3 types of Connector (see connector.png), and an Exception occured because the cast is impossible, for this row :

Dim lineSegment As LineSegment = CType(pathFigure.Segments(0), LineSegment)

 

Actually, I added a condition, but I want to know if the behaviour is the same, but just with the good cast Type ?

The added condition :

If (TypeOf pathFigure.Segments(0) Is LineSegment) Then
    Dim lineSegment As LineSegment = CType(pathFigure.Segments(0), LineSegment)
 
    p1 = pathFigure.StartPoint
    p2 = lineSegment.Point
End If

 

Thank you.

0
Martin Ivanov
Telerik team
answered on 07 Mar 2018, 10:13 AM
Hello Valentin,

The different connection types could use different number of type of segments. I would recommend you to debug the code in the converter that gets the segments and adjust it so that it fits for all connection types if your implementation requires this.

Regards,
Martin Ivanov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
0
Valentin
Top achievements
Rank 1
Iron
Iron
answered on 30 Apr 2018, 01:50 PM

Hi,

 

I reopened this post to improve my template. You can see in the .Xaml (top of this post) :

<TextBlock x:Name="NormalContent" Foreground="{TemplateBinding Foreground}"
                       FontFamily="{TemplateBinding FontFamily}"
                       FontSize="{TemplateBinding FontSize}"
                       Text="{TemplateBinding Content}"
                       TextAlignment="{Binding Tag.TextAlignement, RelativeSource={RelativeSource AncestorType=diagramscontrols:RadDiagramConnection}}"
                       HorizontalAlignment="{Binding Tag.HorizontalTextAlignement, RelativeSource={RelativeSource AncestorType=diagramscontrols:RadDiagramConnection}}"
                       VerticalAlignment="Center" />

 

I want to have a perfect horizontal alignment on each Connector Type, and for each alignement (left, center, right). I'm using a Margin Converter to manage the margin of the TextBlock :

<TextBlock.Margin>
    <MultiBinding Converter="{StaticResource MarginConverter}">
        <Binding Path="StartPoint" RelativeSource="{RelativeSource AncestorType=telerik:RadDiagramConnection}" />
        <Binding Path="EndPoint" RelativeSource="{RelativeSource AncestorType=telerik:RadDiagramConnection}" />
        <Binding Path="ActualHeight" RelativeSource="{RelativeSource Self}" />
    </MultiBinding>
</TextBlock.Margin>

 

Public Function Convert(ByVal values As Object(), ByVal targetType As Type, ByVal parameter As Object, ByVal culture As CultureInfo) As Object Implements IMultiValueConverter.Convert
    Dim p1 As Point = CType(values(0), Point)
    Dim p2 As Point = CType(values(1), Point)
    Dim height As Double = CDbl(values(2))
    Dim angleDeg As Double = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X) * 180 / Math.PI
    Dim topMargin As Double = 0
 
    If (angleDeg <> 90) AndAlso (angleDeg <> -90) Then
        topMargin = height
    End If
 
    Return New Thickness(-topMargin * 2, -topMargin, 0, 0)
End Function

 

The alignement is correct only when the Connector is not rotated.

And I don't find the calculation to align it correctly.

 

Can you help me ?

 

Thank you.

0
Martin Ivanov
Telerik team
answered on 02 May 2018, 10:36 AM
Hello Valentin,

To achieve your requirement you can set the Width of the TextBlock that shows the content of the RadDiagramConnection. And then set the HorizontalAlignment property. The width of the shape would be the distance between the connection's start and end points. I updated my last project to show this approach. I hope that helps.

Regards,
Martin Ivanov
Progress Telerik
Want to extend the target reach of your WPF applications, leveraging iOS, Android, and UWP? Try UI for Xamarin, a suite of polished and feature-rich components for the Xamarin framework, which allow you to write beautiful native mobile apps using a single shared C# codebase.
Tags
Diagram
Asked by
Valentin
Top achievements
Rank 1
Iron
Iron
Answers by
Martin Ivanov
Telerik team
Valentin
Top achievements
Rank 1
Iron
Iron
Share this question
or