Load Xaml Markup for PushPin (as a UserControl)

Oct 20, 2009 at 7:23 AM
Edited Oct 28, 2009 at 5:58 PM

Have made a slight modification to load PointBase from Xaml Markup without using Style. Doing so allows all the Functionality of UserControl and also ability to store the XAML in separate location, i.e *.xaml file, database etc.

The following steps for the modification:

1. Change GeometryBase to inherit from UserControl instead of Control.

public abstract class GeometryBase : UserControl

2. Comment/Remove from the GeometryBase constructor the following line.

DefaultStyleKey = typeof(GeometryBase);

3. Go to PointBase and Comment/Remove from the constructor the following line.

DefaultStyleKey = typeof (PointBase);

 

4. Still in the PointBase, Replace the following lines in OnApplyTemplate() method, there are only 2 line changes (1. and 2)

        public override void OnApplyTemplate()
{
_IsLoaded = true;
ForceMeasure();

//1. Comment This line
//_ScaleTransform = (ScaleTransform) GetTemplateChild("_ScaleTransform");

//2. Add this line
_ScaleTransform = (this.Content as FrameworkElement).FindName("_ScaleTransform") as ScaleTransform;

_ScaleTransform.CenterX = Anchor.X;
_ScaleTransform.CenterY = Anchor.Y;

if (Layer != null)
{
Layer.UpdateChildLocation(this);
}
}

That is all the changes required for the DeepEarth Framework. Now in order to use the new PointBase class, the old PushPin that came with DeepEarthPrototype Project have to be changed. The Following steps shows how this is done:

1. Create a XAML file without code behind. Yes, only the *.xaml file, there is no need for the .cs file, alternatively you can use a text file or database to store the strings but xaml allows you to use Expression Blend to make changes. Here I created a file named BasicPin.xaml as an Embedded Resource from the "Properties->Build Action" (notice that this is copied from Styles in App.xaml within the DeepEarthPrototype project, we will not be using styles in this manner anymore). Note that _ScaleTransform is required because PointBase.OnApplyTemplate() will try to find this. There is not need for the <UserControl>Tags that is auto generated by Visual Studio if you use VS to create the XAML, the full xaml is as follows.

<Grid>
<Grid.RenderTransform>
<ScaleTransform x:Name="_ScaleTransform" ScaleX="1" ScaleY="1" />
</Grid.RenderTransform>
<Image Source="arrow.png" Height="48" Width="48" />
</Grid>

2. Create a class that inherits PointBase, here I replace the PushPin class that came as an example, inherited from PointBase. Once the control is loaded, PushPin_Loaded event will be called to invoke the OnApplyTemplate method. The LoadFromString method is used to load the xaml markup into the control.

    public class PushPin:PointBase
{
public PushPin()
{
Loaded += new RoutedEventHandler(PushPin_Loaded);
}

void PushPin_Loaded(object sender, RoutedEventArgs e)
{
OnApplyTemplate();
}

public void LoadFromString(string xaml)
{
this.Content = XamlReader.Load(xaml) as UIElement;
}
}

3. The following is a simplified code to show how a PushPin class is made to work. First, an IO stream is created from the Resource Stream (My BasicPin.xaml is residing in the directory "Project Root->Controls->BasicPin.xaml). This IO stream simply read the contents of the file, but you can read from a database or a text file as long as it is Xaml Markup.

 

            var point = new Point(-77.0365, 38.897);

anchorTestLayer = new GeometryLayer(map) { ID = "RESULTSLAYER", UpdateMode = GeometryLayer.UpdateModes.ElementUpdate };
map.Layers.Add(anchorTestLayer);

 

 

            System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream("DeepEarthPrototype.Controls.BasicPin.xaml");
StreamReader reader = new System.IO.StreamReader(s);

PushPin pin = new PushPin() { Point = point };

pin.LoadFromString(reader.ReadToEnd());
anchorTestLayer.Add(pin);

 

That is all there is to it, it should work pretty much the same as before without breaking (I hope). Enjoy!

 

Note:Amendment to code in the following posts which allows for access of child controls

Developer
Oct 28, 2009 at 3:20 PM

I've tried this and got it working. Now my question is, how do I access specific controls (say a TextBlock) from the XAML? Like the GetTemplateChild() functions in the "old" way.

Oct 28, 2009 at 5:54 PM

Hi,

I apologise for the semi-functional code I have posted, I overlooked this obvious feature. In order to access specific controls, there are a couple of simple modifications that needs to be done. Modify the Pushpin class as follows:

public class PushPin : PointBase
{
Map _map;
FrameworkElement LayoutRoot;//added

public PushPin(Map m)
{
_map = m;
Loaded += new RoutedEventHandler(PushPin_Loaded);
}

void PushPin_Loaded(object sender, RoutedEventArgs e)
{
OnApplyTemplate();
}

public object FindControlChild(string name) //Added
{
return LayoutRoot.FindName(name);
}

public void LoadFromString(string xaml)
{
LayoutRoot = XamlReader.Load(xaml) as FrameworkElement; //amended
this.Content = LayoutRoot;
}
}

Here, I changed the UIElement to FrameworkElement for the XamlReader. By doing this, I will be able to do a FindName which will return you any child element in the xaml and cast them into its corresponding controls. Like the following:

=======BasicPin.xaml===========

 

<Grid>
     <Grid.RenderTransform>
           <ScaleTransform x:Name="_ScaleTransform" ScaleX="1" ScaleY="1" />
     </Grid.RenderTransform>
     <TextBlock name="txt" Text="Something" />
     <Image Source="arrow.png" Height="48" Width="48" />
</Grid>
========Page.xaml.cs===========

PushPin pin = new PushPin() { Point = point };

pin.LoadFromString(reader.ReadToEnd());
anchorTestLayer.Add(pin);

TextBlock txt = pin.FindControlChild("txt") as TextBlock;
txt.Text = "YAY!!";

I hope this is what you are looking for.

public class PushPin : PointBase
    {
        Map _map;
        FrameworkElement LayoutRoot;//added
        
        public PushPin(Map m)
        {
            _map = m;
            Loaded += new RoutedEventHandler(PushPin_Loaded);
        }

        void PushPin_Loaded(object sender, RoutedEventArgs e)
        {
            OnApplyTemplate();
        }

        public object FindControlChild(string name) //Added
        {
            return LayoutRoot.FindName(name);
        }

        public void LoadFromString(string xaml)
        {
            LayoutRoot = XamlReader.Load(xaml) as FrameworkElement; //amended
            this.Content = LayoutRoot;
      
public class PushPin : PointBase
    {
        Map _map;
        FrameworkElement LayoutRoot;//added
        
        public PushPin(Map m)
        {
            _map = m;
            Loaded += new RoutedEventHandler(PushPin_Loaded);
        }

        void PushPin_Loaded(object sender, RoutedEventArgs e)
        {
            OnApplyTemplate();
        }

        public object FindControlChild(string name) //Added
        {
            return LayoutRoot.FindName(name);
        }

        public void LoadFromString(string xaml)
        {
            LayoutRoot = XamlReader.Load(xaml) as FrameworkElement; //amended
            this.Content = LayoutRoot;
        }
    }
  }
    }
Developer
Oct 29, 2009 at 7:51 AM

Exactly, thanks.

 

Developer
Nov 11, 2009 at 2:28 PM

Now since the release of the Bing Maps Silverlight Control v1 is released Im would like to achieve the same thing in the Bing branch of DeepEarth, but digging into the BaseGeometry class etc is quite hairy so any help is appreciated. Or does the current architecture support this already using styles/templates?

 

Nov 11, 2009 at 5:53 PM

By "achieve this" I assume that you would like to load Xaml Markup and load it on runtime. I don't think the current architecture supports this, because styles cannot be changed on runtime (I stand to be corrected). I suppose modifying the BaseGeometry from inheriting from Control to inheriting from UserControl could potentially break many of the existing controls build upon styles. In order to do this, I would think there is no other way but the hairy way. I cannot get my head around why GeometryBase inherits from Control, since UserControl can do most of what Control could and more. Sorry that I cannot provide an easy solution to this, Let me know how else I could help.

Nov 11, 2009 at 6:12 PM

Another example/way on how you could create a Silverlight UserControl that inherits from PointBase.

In Visual Studio,

  1. Right Click on Project->Add -> New Item-> Silverlight User Control
  2. enter name (e.g CustomPin.xaml)
  3. Open up CustomPin.xaml.cs
  4. Change the base class from UserControl to PointBase  e,g public partial class CustomPin: PointBase
  5. Open up PushPin.xaml
  6. Change the xaml from UserControl to Pointbase like this:
<DeepEarth_Client_MapControl_Geometry:PointBase x:Class="MapLingo.Controls.UserContentPins"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
xmlns:DeepEarth_Client_MapControl_Geometry="clr-namespace:DeepEarth.Client.MapControl.Geometry;assembly=DeepEarth.Client.MapControl"
 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006">
  <Grid>
<Grid.RenderTransform> <ScaleTransform x:Name="_ScaleTransform" ScaleX="1" ScaleY="1" /> </Grid.RenderTransform> </Grid>
 </DeepEarth_Client_MapControl_Geometry:PointBase>

Thats all.
Coordinator
Nov 11, 2009 at 8:49 PM

So in the Bing Maps specific classes I went for a simple image for the pin and then for the popup you can supply a custom XAML template.

If you wanted to change this then you can override the style and make it anything you want. But this is not dynamic like jeffpang's solution. It's possible you could have these xaml pins defined and created elsewhere and loaded in from a webservice.

I'd like to explore this idea further but there is a big bug in Silverlight currently, any redraw on the page is causing the core MSI control to redraw spiking the CPU. This means currently I don't recommend complex xaml pins, espeacially those with animations if you're going to have hundreds of them shown.

Nov 12, 2009 at 7:56 AM
Edited Nov 12, 2009 at 7:58 AM

I don't seem to have this issue...  I am using Core 2 Duo - 2.4Ghz, 2GB RAM, 32Bit System, Silverlight 3 runtime on my Firefox browser. The base processing usage is about 15% without running DeepEarth. After I ran the DeepEarth, I added about 20 instances of pushpins (Using my version with UserControls), simple storyboard of 5 keyframe (looking like a bunch of icons jumping around randomly), it spiked to 70% and settle back down to about 40% when I stopped zoomimg or scolling the map. Can you kindly do some stress testing on your end as well? I think I might have some ideas on how to workaround the performance issue should there be any. (If there really is, then I'll experiment with my solution, its in the idea stage now)

Developer
Nov 12, 2009 at 1:04 PM

I've managed to get this working now in the Bing branch by

- creating a "DynamicPushPin" class inheriting from UserControl (more or less like DE PointBase)

- instead of using the Vector.CreateOverlay which uses the EnhancedLayer.Add function I've created a very similar function that takes a IEnumarable<DynamicPushPin> which it adds to the EnhancedLayer with AddChild instead.

This means that these pins does not inherit from BaseGeometry which in turn means I have had to implement scaling and zooming visibility within the pushpins instead. But since that was exactly what I wanted to do anyway (custom scaling for a circle type pin) it all works out fine :)

 

 

 

Apr 26, 2011 at 2:21 PM

Ok... I have implemented this solution but now I have another problem... I cannot draw a Line or a Polygon with the classes LineString and Polygon... someone have a solution to this issue?

Thanks

Claudio