Select to view content in your preferred language

Custom Marker Symbol (and Custom Cursor)

2902
2
05-07-2013 12:22 PM
LanceCrumbliss
Frequent Contributor
Hi All, (using v2.4)

I've been using a custom cursor for a long time now as part of the standard features in the apps I write.  It's simply

Partial Public Class CustomMapCursor
    Inherits UserControl


    Public Sub New()
        InitializeComponent()
    End Sub


    Private ReadOnly MapProperty As DependencyProperty = DependencyProperty.Register("Map", GetType(FrameworkElement), GetType(CustomMapCursor), New PropertyMetadata(Nothing, AddressOf OnMapPropertyChanged))
    Public Property Map() As FrameworkElement
        Get
            Return DirectCast(GetValue(MapProperty), FrameworkElement)
        End Get


        Set(ByVal value As FrameworkElement)
            SetValue(MapProperty, value)
        End Set
    End Property


    Private Shared Sub OnMapPropertyChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim target As CustomMapCursor = DirectCast(d, CustomMapCursor)
        If e.OldValue IsNot Nothing Then
            Dim oldMap As FrameworkElement = CType(e.OldValue, FrameworkElement)
            RemoveHandler oldMap.MouseMove, AddressOf target.Map_MouseMove
            RemoveHandler oldMap.MouseLeave, AddressOf target.Map_MouseLeave
            RemoveHandler oldMap.MouseEnter, AddressOf target.Map_MouseEnter
        End If
        If e.NewValue IsNot Nothing Then
            Dim newMap As FrameworkElement = CType(e.NewValue, FrameworkElement)
            AddHandler newMap.MouseMove, AddressOf target.Map_MouseMove
            AddHandler newMap.MouseLeave, AddressOf target.Map_MouseLeave
            AddHandler newMap.MouseEnter, AddressOf target.Map_MouseEnter
        End If
    End Sub


    Private ReadOnly SourceProperty As DependencyProperty = DependencyProperty.Register("Source", GetType(DataTemplate), GetType(CustomMapCursor), New PropertyMetadata(Nothing, AddressOf OnSourceChanged))
    Public Property Source() As DataTemplate
        Get
            Return CType(GetValue(SourceProperty), DataTemplate)
        End Get
        Set(ByVal value As DataTemplate)
            SetValue(SourceProperty, value)
        End Set
    End Property


    Private Overloads Shared Sub OnSourceChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim target As CustomMapCursor = CType(d, CustomMapCursor)
        Dim oldSource As DataTemplate = CType(e.OldValue, DataTemplate)
        Dim newSource As DataTemplate = target.Source
        target.CustomCursor.ContentTemplate = newSource
        target.OnSourceChanged(oldSource, newSource)
    End Sub


    Protected Overridable Overloads Sub OnSourceChanged(ByVal oldSource As DataTemplate, ByVal newSource As DataTemplate)
        RaiseEvent CursorChanged(Me, New System.EventArgs)
    End Sub


    Private Sub MoveTo(ByVal pt As Point)
        If Me.Map.Cursor IsNot Cursors.None Then
            Me.Map.Cursor = Cursors.None
        End If
        CustomCursor.SetValue(Canvas.LeftProperty, pt.X)
        CustomCursor.SetValue(Canvas.TopProperty, pt.Y)
    End Sub


    Public Shared Event CursorChanged As System.EventHandler


    Private Sub Map_MouseMove(ByVal sender As Object, ByVal e As MouseEventArgs)
        MoveTo(e.GetPosition(Map))
    End Sub


    Private Sub Map_MouseLeave(sender As Object, e As MouseEventArgs)
        LayoutRoot.Visibility = Windows.Visibility.Collapsed
    End Sub


    Private Sub Map_MouseEnter(sender As Object, e As MouseEventArgs)
        LayoutRoot.Visibility = Windows.Visibility.Visible
    End Sub




End Class




XAML is:

<UserControl x:Class="[ProjectName].CustomMapCursor"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">


    <Canvas x:Name="LayoutRoot" IsHitTestVisible="False">
        <ContentControl x:Name="CustomCursor"/>
    </Canvas>
</UserControl>




It is put in the app like so:

<local:CustomMapCursor x:Name="MyCursor"  Map="{Binding ElementName=MyMap}" Source="{StaticResource PanCursor}" Grid.Column="1" HorizontalAlignment="Left" VerticalAlignment="Top"/>

(the Map is alone in Grid.Column 1, so the CustomCursor is placed on top of it)

The DataTemplate for the Cursor is

    <DataTemplate x:Key="PanCursor">
        <Image Width="24" Height="24" Source="/[ProjectName];component/CustomMapCursor/Images/PanColor.png">
            <Image.RenderTransform>
                <TranslateTransform X="-6" Y="-6"/>
            </Image.RenderTransform>
        </Image>
    </DataTemplate>


It is a simple hand. 

The problem I'm having now that I have never had before is that the rate of movement slows down greatly as more graphics are added.  The are points, with a MarkerSymbol defined in a ResourceDictionary as (Cont. in next post)
0 Kudos
2 Replies
LanceCrumbliss
Frequent Contributor
    <esri:MarkerSymbol x:Key="VehicleRecoverySymbol" OffsetX="16" OffsetY="17">
        <esri:MarkerSymbol.ControlTemplate>
            <ControlTemplate>
                <Grid RenderTransformOrigin="0.5,0.5">
                    <Grid.RenderTransform>
                        <TransformGroup>
                            <ScaleTransform ScaleX="1" ScaleY="1" />
                        </TransformGroup>
                    </Grid.RenderTransform>
                    <VisualStateManager.VisualStateGroups>
                        <VisualStateGroup x:Name="CommonStates">
                            <VisualState x:Name="MouseOver" />
                            <VisualState x:Name="Normal"/>
                        </VisualStateGroup>
                        <VisualStateGroup x:Name="SelectionStates">
                            <VisualState x:Name="Selected">
                                <Storyboard>
                                    <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="Highlight" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.25"/>
                                    <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="Picture" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.25"/>
                                    <DoubleAnimationUsingKeyFrames RepeatBehavior="Forever" BeginTime="00:00:01" Storyboard.TargetName="Lights" Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationY)">
                                        <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0" />
                                        <EasingDoubleKeyFrame KeyTime="00:00:00.5" Value="180" />
                                        <EasingDoubleKeyFrame KeyTime="00:00:00.5" Value="-180" />
                                        <EasingDoubleKeyFrame KeyTime="00:00:01" Value="0" />
                                    </DoubleAnimationUsingKeyFrames>
                                    <DoubleAnimation BeginTime="00:00:00" Storyboard.TargetName="Lights"
                                                         Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.Y)" To="-3" Duration="0:0:0.5"/>
                                </Storyboard>
                            </VisualState>
                            <VisualState x:Name="Unselected" />
                        </VisualStateGroup>
                    </VisualStateManager.VisualStateGroups>
                    <StackPanel x:Name="Lights" Orientation="Horizontal" HorizontalAlignment="Left" Margin="12,0,0,0" VerticalAlignment="Top"  RenderTransformOrigin="0.5,0.5">
                        <StackPanel.Projection>
                            <PlaneProjection RotationY="0" />
                        </StackPanel.Projection>
                        <StackPanel.RenderTransform>
                            <TranslateTransform Y="6"/>
                        </StackPanel.RenderTransform>
                        <Border Background="Blue" Width="4" Height="6" VerticalAlignment="Top" CornerRadius="2,0,0,0"/>
                        <Border Background="Red" Width="4" Height="6" VerticalAlignment="Top" CornerRadius="0,2,0,0"/>


                    </StackPanel>


                    <Image x:Name="Shadow" HorizontalAlignment="Left"  Source="/[ProjectName];component/Images/shadow.png" RenderTransformOrigin="0.5,0.5" IsHitTestVisible="False"/>
                    <Image x:Name="Picture" HorizontalAlignment="Left"  Source="/[ProjectName];component/Images/symbol_blank.png" RenderTransformOrigin="0.5,0.5"/>
                    <Image x:Name="Highlight" HorizontalAlignment="Left"  Source="/[ProjectName];component/Images/symbol_blank_highlight.png" RenderTransformOrigin="0.5,0.5" Opacity="0"/>
                    <Canvas HorizontalAlignment="Left" VerticalAlignment="Top" Margin="9,11,0,0">
                        <Path Data="M13,6.5 C13,10.0899 10.0899,13 6.5,13 C2.91015,13 0,10.0899 0,6.5 C0,2.91015 2.91015,0 6.5,0 C10.0899,0 13,2.91015 13,6.5 z" Fill="#FF9A4AB2" Height="13" Stretch="Fill" Width="13"/>
                        <Path Fill="White" Data="F1 M 12.6933,10.1179L 12.6933,12.0575C 12.6933,12.3452 12.5976,12.5854 12.406,12.7779C 12.2145,12.9704 11.9768,13.0666 11.6929,13.0666L 10.7071,13.0666C 10.4232,13.0666 10.1855,12.9704 9.99396,12.7779C 9.80243,12.5854 9.70667,12.3452 9.70667,12.0575L 9.70667,10.1179L 3.92,10.1179L 3.92,12.0575C 3.92,12.3452 3.82424,12.5854 3.63271,12.7779C 3.44118,12.9704 3.20347,13.0666 2.91958,13.0666L 1.93375,13.0666C 1.64986,13.0666 1.41215,12.9704 1.22062,12.7779C 1.0291,12.5854 0.933333,12.3452 0.933333,12.0575L 0.933333,10.1179L 0,10.1179L 0,4.83578L 1.61292,-3.05176e-005L 12.1275,-3.05176e-005L 13.6267,4.83578L 13.6267,10.1179L 12.6933,10.1179 Z M 11.3517,0.933258L 2.36833,0.933258L 1.04417,4.66663L 12.6,4.64914L 11.3517,0.933258 Z M 12.6933,7.74661C 12.6933,7.35965 12.5562,7.02957 12.2821,6.75638C 12.0079,6.48318 11.6783,6.34659 11.2933,6.34659C 10.9064,6.34659 10.5792,6.48318 10.3119,6.75638C 10.0445,7.02957 9.91083,7.35965 9.91083,7.74661C 9.91083,8.11996 10.0479,8.44321 10.3221,8.7164C 10.5962,8.98959 10.92,9.12619 11.2933,9.12619C 11.6783,9.12619 12.0079,8.99252 12.2821,8.72513C 12.5562,8.45779 12.6933,8.13159 12.6933,7.74661 Z M 3.69542,7.74661C 3.69542,7.35965 3.56174,7.02957 3.29437,6.75638C 3.02701,6.48318 2.70083,6.34659 2.31583,6.34659C 1.92889,6.34659 1.60174,6.48318 1.33437,6.75638C 1.06701,7.02957 0.933333,7.35965 0.933333,7.74661C 0.933333,8.13159 1.06701,8.45779 1.33437,8.72513C 1.60174,8.99252 1.92889,9.12619 2.31583,9.12619C 2.70083,9.12619 3.02701,8.99252 3.29437,8.72513C 3.56174,8.45779 3.69542,8.13159 3.69542,7.74661 Z " Height="9" Stretch="Fill" UseLayoutRounding="False" Width="9" Canvas.Top="1.909" Canvas.Left="1.992" />
                        <Path Fill="#00FFFFFF" Data="F1 M 11.3517,0.933258L 2.36833,0.933258L 1.04417,4.66663L 12.6,4.64914L 11.3517,0.933258 Z" Height="9" Stretch="Fill" UseLayoutRounding="False" Width="9" Canvas.Top="1.909" Canvas.Left="1.992" />
                        <Path Fill="#00FFFFFF" Data="F1 M 12.6933,7.74661C 12.6933,7.35965 12.5562,7.02957 12.2821,6.75638C 12.0079,6.48318 11.6783,6.34659 11.2933,6.34659C 10.9064,6.34659 10.5792,6.48318 10.3119,6.75638C 10.0445,7.02957 9.91083,7.35965 9.91083,7.74661C 9.91083,8.11996 10.0479,8.44321 10.3221,8.7164C 10.5962,8.98959 10.92,9.12619 11.2933,9.12619C 11.6783,9.12619 12.0079,8.99252 12.2821,8.72513C 12.5562,8.45779 12.6933,8.13159 12.6933,7.74661 Z M 3.69542,7.74661C 3.69542,7.35965 3.56174,7.02957 3.29437,6.75638C 3.02701,6.48318 2.70083,6.34659 2.31583,6.34659C 1.92889,6.34659 1.60174,6.48318 1.33437,6.75638C 1.06701,7.02957 0.933333,7.35965 0.933333,7.74661C 0.933333,8.13159 1.06701,8.45779 1.33437,8.72513C 1.60174,8.99252 1.92889,9.12619 2.31583,9.12619C 2.70083,9.12619 3.02701,8.99252 3.29437,8.72513C 3.56174,8.45779 3.69542,8.13159 3.69542,7.74661 Z " Height="9" Stretch="Fill" UseLayoutRounding="False" Width="9" Canvas.Top="1.909" Canvas.Left="1.992" />
                    </Canvas>
                </Grid>
            </ControlTemplate>
        </esri:MarkerSymbol.ControlTemplate>
    </esri:MarkerSymbol>




The result is attached.

MapTips are enabled on the layer, but in testing even I remove them there is still a slowdown.  Also for testing, setting the Layer's IsHitTestVisible Property to False will eliminate the slowdown, but of course, that is not acceptable in production as the MapTips will not work in that case.

There are no effects anywhere in the application. I have enabled drawregions for debugging. No regions are redrawn. The only redraw is the 24x24 canvas hosting the CustomCursor as it moves across the map. I would say the threshold on my machine is about 800 points using the symbol above before the slowdown is noticeable. 

If I remove the CustomCursor and use the system cursors, there is no slowdown, no matter how many symbols are placed on the map.  This isn't ideal, per the client.

Also per the client, clustering is not an option, so I would like to find a solution to this.  Any help would be greatly appreciated.

Lance
0 Kudos
LanceCrumbliss
Frequent Contributor
Following up on this, if i use a simple symbol defined as a resource like:

<esri:SimpleMarkerSymbol x:Key="TestSymbol" Color="Red" Size="8"/>


then there is no slowdown. 

Also, in the case of the custom symbol in the post(s) preceding this one, it is defined as the symbol in a SimpleRenderer and that is then applied to the graphicslayer.  In the case of the TestSymbol above, I am not setting a Renderer on the graphicslayer, but instead applying the symbol directly to the graphic.  Of course, that leaves a blank legend image, so that's not ideal.

Also, in further testing, if I set the outer Grid of the custom MarkerSymbol's ControlTemplate to IsHitTestVisible = False, then there is no slowdown.  But of course, this is also results in no maptips.

This may be a Symbol issue and not a CustomCursor issue.  What can I do to make the Custom MarkerSymbol work well with the CustomCursor?

Lance
0 Kudos