Custom Marker Symbol with Labels

4365
8
01-27-2011 10:42 AM
EduardLucic
New Contributor
Hi,

We're trying to create a custom marker symbol that would have an icon, one label on top and one label on the bottom. Pretty standard. However, since the labels or icons can have variable sizes, how would you declare the symbol control template so that the icon's center sits where the MapPoint is? The behavior we see is that the whole control container is aligned at the UpperLeft corner. We could use translation, but we don't know the size of the text until the text is set. Are we missing something obvious?

Our template looks like this:

<esri:MarkerSymbol x:Key="SquareMarkerSymbol">
    <esri:MarkerSymbol.ControlTemplate>
        <ControlTemplate>
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
              <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="*"/>
                <RowDefinition Height="Auto"/>
              </Grid.RowDefinitions>
              <Border Background="Blue" Grid.RowSpan="3" />
              <TextBlock Text="text 1" Grid.Row="0" Foreground="White" HorizontalAlignment="Center" />
              <Border Background="Green" Grid.Row="1" Width="48" Height="48" />
              <TextBlock Text="text 2" Grid.Row="2" Foreground="White" HorizontalAlignment="Center" />
            </Grid>
        </ControlTemplate>
    </esri:MarkerSymbol.ControlTemplate>
</esri:MarkerSymbol>


Any help would be greatly appreciated.
Thanks,
Ed
0 Kudos
8 Replies
DominiqueBroux
Esri Frequent Contributor
Are we missing something obvious?

No, this is not that obvious!!

In this sample, http://www.arcgis.com/home/item.html?id=e361c2cc69784fcba34358d0458f66e3 there is a CenteredContentControl class which is mainly used to display the page number in the grid. This class is probably close of what you need.
0 Kudos
EduardLucic
New Contributor
Thanks Dominique. I'll take a look at it beginning next week. Up to now, playing around I was able to do something by binding to properties ActualWidth, ActualHeight with an extra converter. It's basically an icon with a bubble on top and when the description text changes, the bubble is re-centered. Converter returns -(ActualWidth/2). Same idea for the Top positioning. Not sure if it's the way to go, but it gave a decent result. Oh, but it only works with WPF. With Sivlerlight, there seems to be a bug with the notification of properties ActualWidth and ActualHeight....

Here's the template:

<esri:MarkerSymbol x:Key="MarkerSymbolWithBubble">
    <esri:MarkerSymbol.ControlTemplate>
        <ControlTemplate>
            <Canvas>
                <Grid Canvas.Left="{Binding ActualWidth, ElementName=InfoPopupGrid, Converter={StaticResource PositioningConverter}}" Canvas.Top="{Binding ActualHeight, ElementName=InfoPopupGrid, Converter={StaticResource PositioningConverter2}}">
                    <Grid.RowDefinitions>
                     <RowDefinition Height="Auto"/>
                     <RowDefinition Height="Auto"/>
                    </Grid.RowDefinitions>
                    <Grid x:Name="InfoPopupGrid" MaxWidth="300">
                     <Grid.RowDefinitions>
                      <RowDefinition Height="Auto"/>
                      <RowDefinition Height="Auto"/>
                     </Grid.RowDefinitions>
                     <Path x:Name="PopupArrow" Data="M363,252 L409,252 L385,278 z" Stretch="Fill" Stroke="Black" UseLayoutRounding="False" Height="20" 
       VerticalAlignment="Bottom" Grid.Row="1" HorizontalAlignment="Center" Width="20">
                      <Path.Fill>
                       <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="Black" Offset="0"/>
                        <GradientStop Color="#FFA5A5A5" Offset="1"/>
                       </LinearGradientBrush>
                      </Path.Fill>
                     </Path>
                     <Border x:Name="PopupContent" BorderBrush="Black" BorderThickness="1" CornerRadius="5">
                      <Border.Background>
                       <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="Black" Offset="0"/>
                        <GradientStop Color="#FF7A7A7A" Offset="0.536"/>
                        <GradientStop Color="#FF707070" Offset="1"/>
                        <GradientStop Color="#FF6A6A6A" Offset="0.515"/>
                       </LinearGradientBrush>
                      </Border.Background>
                      <Grid>
                       <TextBlock Margin="8,5,45,0" TextWrapping="Wrap" Text="Icon Title" Foreground="White" VerticalAlignment="Top" FontWeight="Bold"/>
                       <TextBlock TextWrapping="Wrap" Text="{Binding Attributes[Description]}" Foreground="White" Margin="8,20,21,0" VerticalAlignment="Top"/>
                      </Grid>
                     </Border>
                    </Grid>
                    <Image Source="pushpin.png" Stretch="None" Height="24" Width="24" HorizontalAlignment="Center" Grid.Row="1"/>
                </Grid>
            </Canvas>
        </ControlTemplate>
    </esri:MarkerSymbol.ControlTemplate>
</esri:MarkerSymbol>


Ed
0 Kudos
dotMorten_esri
Esri Notable Contributor
Below is a label symbol I often use. The trick here is two textblocks on top of each other (in addition to the red ellipse that marks the "spot"). The top one is black foreground, the back one is white with a blur effect added to it. That way you get a white "halo" around the text (note that the blur effect can be a bit of an overhead when rendering, so avoid that part if you have A LOT of points).

<esri:MarkerSymbol x:Key="labelSymbol" OffsetX="6" OffsetY="6">
 <esri:MarkerSymbol.ControlTemplate>
    <ControlTemplate>
       <Grid>
<!--Marker-->
          <Ellipse Width="12" Height="12" Fill="Red" HorizontalAlignment="Left" VerticalAlignment="Top" />
<!--Label-->
          <Grid Margin="8,8,0,0" HorizontalAlignment="Left" VerticalAlignment="Top" IsHitTestVisible="False">
<!--Text halo using a white blurred text-->
             <TextBlock Foreground="White" Text="{Binding Attributes[NAME]}" >
                <TextBlock.Effect>
                    <BlurEffect Radius="5" />
                </TextBlock.Effect>
            </TextBlock>
<!--Text-->
           <TextBlock Foreground="Black" Text="{Binding Attributes[NAME]}" />
        </Grid>
     </Grid>
  </ControlTemplate>
  </esri:MarkerSymbol.ControlTemplate>
</esri:MarkerSymbol>


Replace 'NAME' with whatever attribute you need to display as label.

Here the text is tied to the lower right corner using the margin property. If you need it centered, a simple trick is to set the alignment to center and provide some large enough negative margins on both left and right side to hold the text. That way you don't need to recalculate the offset of the margin.
As Dominique mentioned above, a custom control can also do this using the MeasureOverride and ArrangeOverride methods, but this is also a much more advanced (albeit better) approach.
0 Kudos
dotMorten_esri
Esri Notable Contributor
And here�??s a slightly different approach where it only shows the label on hover. Very maptip�??ish. This performs a lot better, because it doesn�??t render the label unless it needs to (the blur effect is expensive), and has a much less cluttered look. It's great when you don't want to overload the user with information, but still make it easily accessible:

 
<esri:MarkerSymbol x:Key="labelSymbol" OffsetX="6" OffsetY="6">
<esri:MarkerSymbol.ControlTemplate>
   <ControlTemplate>
      <Grid>
         <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="CommonStates">
               <VisualState x:Name="MouseOver">
                  <Storyboard>
                     <ObjectAnimationUsingKeyFrames Storyboard.TargetName="labelOnHover" Storyboard.TargetProperty="Visibility" Duration="0">
                        <DiscreteObjectKeyFrame KeyTime="0">
                           <DiscreteObjectKeyFrame.Value>
                              <Visibility>Visible</Visibility>
                           </DiscreteObjectKeyFrame.Value>
                        </DiscreteObjectKeyFrame>
                     </ObjectAnimationUsingKeyFrames>
                     <DoubleAnimation From="0" To="1" Storyboard.TargetName="labelOnHover" Storyboard.TargetProperty="Opacity"
                        Duration="0:0:.25" />
                  </Storyboard>
               </VisualState>
               <VisualState x:Name="Normal" />
            </VisualStateGroup>
         </VisualStateManager.VisualStateGroups>
<!--Marker-->
         <Ellipse Width="12" Height="12" Fill="Red" 
         HorizontalAlignment="Left" VerticalAlignment="Top" />
<!--Label-->
         <Grid Margin="-200,-8,-200,0" MaxWidth="400" x:Name="labelOnHover" Visibility="Collapsed"
           HorizontalAlignment="Center" VerticalAlignment="Top" IsHitTestVisible="False">
<!--Text halo using a white blurred text-->
            <TextBlock Foreground="White" FontWeight="Bold" Text="{Binding Attributes[NAME]}" >
               <TextBlock.Effect>
                  <BlurEffect Radius="5" />
               </TextBlock.Effect>
            </TextBlock>
<!--Text-->
            <TextBlock Foreground="Black" FontWeight="Bold" Text="{Binding Attributes[NAME]}" />
         </Grid>
      </Grid>
   </ControlTemplate>
</esri:MarkerSymbol.ControlTemplate>
</esri:MarkerSymbol>


Note: Compared to the previous example, the label has been moved to the upper center. This is because when hovering on the graphic, the mousecursor gets in the way of the label, so this moves it out of the way (but of course also to demonstrate how to center a label, as mentioned in my last reply).
0 Kudos
EduardLucic
New Contributor
Morten,

Interesting approach with the negative margins. Tried it out and worked out nicely (just had to set the Horizontal alignment of the inner Grid to Center). I guess if you want to center also vertically depending on the height of the labels (assuming multiline), that's where you would need a custom control?

Also, is there a way to access the WPF control from the Graphic object or know when it gets created or everything should be done through binding?

Ed
0 Kudos
dotMorten_esri
Esri Notable Contributor
Vertical center alignment can be done the same way using equal negative margins for top and bottom.

If you wrap the above template in a control, you can get access to the control when its created through its constructor, use the various override methods etc. Note though that the graphic it renders might change from time to time because of the virtualization going on under the covers (ie. if a graphic moves out of the map view, and another later moves in, the control instance might be reused to render this new graphic). Also note that there is no link from within this control back up to the graphic it is representing.
0 Kudos
EduardLucic
New Contributor
Thanks a lot for your help 🙂

Ed
0 Kudos
EduardLucic
New Contributor

If you wrap the above template in a control, you can get access to the control when its created through its constructor, use the various override methods etc. Note though that the graphic it renders might change from time to time because of the virtualization going on under the covers (ie. if a graphic moves out of the map view, and another later moves in, the control instance might be reused to render this new graphic). Also note that there is no link from within this control back up to the graphic it is representing.


Morten, can you give me an example of what you mean by "Wrapping in a control"? I didn't have much success until now and I think i need to do this if I want to control the exact position of the a bubble above the icon. Same idea as the maps pin on the iPhone maps application. If the text height in the bubble is variable, I can't just offset by a fixed height, I have to take into account the actual size of the bubble once the text is rendered.

Thanks,
Ed
0 Kudos