Measurement Studio for .NET Languages

cancel
Showing results for 
Search instead for 
Did you mean: 

WPF Graph Editable Lables or Annotations

Solved!
Go to solution

Hello.
I am using NI Graphs in my WPF application and I would like to add label (or annotation) at the position where the user double-clicked. These labels must be editable and movable. Is that realizable? Can you help me to solve this issue please? Changing color of the label would be also the nice to have.

Thank you very much for support.

0 Kudos
Message 1 of 3
(2,572 Views)
Solution
Accepted by topic author Zelva

I have created a proof-of-concept below that adds label editing support for PointAnnotation (which already supports interactive movement).

 

MoveableLabels.png

 

First, add a plot area mouse event handler to the graph (for the double-click), and a default style for point annotations (to easily apply the same settings to every annotation that gets created):

<ni:Graph PlotAreaMouseLeftButtonDown="OnPlotAreaMouseLeftButtonDown">
    <ni:Graph.Resources>
        <Style TargetType="ni:PointAnnotation">
            <Setter Property="Fill" Value="Black" />
            <Setter Property="Stroke" Value="Black" />
            <Setter Property="TargetSize" Value="9,9" />
            <Setter Property="TargetShape" Value="Ellipse" />
            <Setter Property="ArrowHeadSize" Value="Empty" />
            <Setter Property="InteractionMode" Value="DragLabelAndTarget" />
            <Setter Property="LabelTemplate">
                <Setter.Value>
                    <DataTemplate>
                        <local:EditableLabel Tag="{Binding}" />
                    </DataTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ni:Graph.Resources>
</ni:Graph>

 

When the mouse handler detects a double-click event, it creates a new annotation at the specified point (and sets the annotation label to the annotation itself, to simplify communicating with the editable label helper):

private void OnPlotAreaMouseLeftButtonDown( object sender, MouseButtonEventArgs e ) {
    if( e.ClickCount != 2 )
        return;

    // Create an annotation at the double-click position.
    var graph = (Graph)sender;
    Point screenPosition = graph.GetPlotAreaPosition( e );
    IList dataPosition = graph.ScreenToData( graph.AllPlots[0], screenPosition );
    var annotation = new PointAnnotation {
        HorizontalPosition = dataPosition[0],
        VerticalPosition = dataPosition[1]
    };

    // (Set the annotation label to itself, for use by the EditableLabel helper.)
    annotation.Label = annotation;

    graph.Children.Add( annotation );
}

 

The editable label helper contains both a static display label, and a text box to edit the label:

<UserControl x:Class="MoveableLabels.EditableLabel"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Grid Block.TextAlignment="Center">
        <StackPanel x:Name="_editPanel">
            <TextBox x:Name="_customLabelEditor" Margin="-4,-2" Padding="0" Text="New Label:" 
                     KeyDown="OnCustomLabelEditorKeyDown" LostFocus="OnCustomLabelEditorLostFocus" />
            <TextBlock x:Name="_positionLabel" Text="(position)" />
        </StackPanel>

        <TextBlock x:Name="_displayLabel" Text="(label)" 
                   MouseDown="OnLabelMouseDown" />
    </Grid>
</UserControl>

 

When you double-click the display label, the helper shows the editor text box; and when you hit Enter or click away, the helper hides the text box and restores the display label:

public partial class EditableLabel : UserControl {
    private PointAnnotation _annotation;

    public EditableLabel( ) {
        InitializeComponent( );

        CompleteEdit( );
    }

    // When editing begins, hide the display label and show the editor.
    private void BeginEdit( ) {
        _displayLabel.Visibility = Visibility.Collapsed;
        _editPanel.Visibility = Visibility.Visible;
        _customLabelEditor.Focus( );
    }

    // When editing is finished, update and show the display label.
    private void CompleteEdit( ) {
        UpdateLabels( );
        _displayLabel.Visibility = Visibility.Visible;
        _editPanel.Visibility = Visibility.Collapsed;
    }

    private void UpdateLabels( ) {
        _positionLabel.Text = _annotation == null ? null : string.Format(
            "({0:0.00}, {1:0.00})",
            _annotation.HorizontalPosition,
            _annotation.VerticalPosition );

        string fullLabel =
              _customLabelEditor.Text
            + Environment.NewLine
            + _positionLabel.Text;
        _displayLabel.Text = fullLabel.Trim( );
    }


    protected override void OnPropertyChanged( DependencyPropertyChangedEventArgs e ) {
        base.OnPropertyChanged( e );

        // (The MainWindow sends the annotation to the EditableLabel using the Tag property in the data template.)
        if( e.Property == TagProperty ) {
            if( _annotation != null )
                _annotation.Invalidated -= OnAnnotationInvalidated;

            _annotation = (PointAnnotation)e.NewValue;
            if( _annotation != null )
                _annotation.Invalidated += OnAnnotationInvalidated;

            UpdateLabels( );
        }
    }


    // If the annotation position changes, update labels with the latest position.
    private void OnAnnotationInvalidated( object sender, RenderInvalidatedEventArgs e ) {
        if( e.Reasons.HasFlag( RenderInvalidatedReasons.PositionalChange ) )
            UpdateLabels( );
    }

    // Begin label editing on a double-click.
    private void OnLabelMouseDown( object sender, MouseButtonEventArgs e ) {
        if( e.ClickCount == 2 )
            BeginEdit( );
    }

    // End label editing on Enter key or loss of focus.
    private void OnCustomLabelEditorKeyDown( object sender, KeyEventArgs e ) {
        if( e.Key == Key.Enter )
            CompleteEdit( );
    }

    private void OnCustomLabelEditorLostFocus( object sender, RoutedEventArgs e ) {
        CompleteEdit( );
    }
}

 

You can include any other controls you might want in the edit panel (such as a color picker). And obviously, if you want to access the custom label in the rest of the application, you would want to create a custom data type or view model to hold that information (instead of the quick-and-dirty trick of passing the annotation as its own label, to access the annotation position directly).

~ Paul H
Message 2 of 3
(2,551 Views)

Hello Paul,

thank you for very quick answer and the good example. I have successfully implemented it to my application and I will continue to build on this example (add changing of color / font, deleting labels, ...).

0 Kudos
Message 3 of 3
(2,519 Views)