Taoffi's blog

prisonniers du temps

Xamarin forms maps – let’s talk renderers: 2. Droid

Let us go back to Xamarin Forms Maps!

In a previous post, I exposed a WinPhone custom renderer for maps.

The object of our renderer is to:

  • Display custom pushpin (marker) icon
  • Display a custom info window for each place (pushpin / marker)

 

The custom renderer

Our Droid custom renderer will derive from MapRenderer and, for reasons we will see later, will implement the GoogleMap.IInfoWindowAdapter:

 

[assembly: ExportRenderer (typeof(iMaps.iCustomMap),
                                    typeof(iMaps.Droid.iCustomMapViewRenderer))]


namespace iMaps.Droid
{
    public class iCustomMapViewRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter

 

Customizing the marker icon

In android, markers can relatively easily be customized to show a custom icon. Here, we use the custom icon of our Custom pin

 

iCustomPin    customPin    = pin.BindingContext as iCustomPin;
string    resourceName    = System.IO.Path.GetFileNameWithoutExtension(customPin.IconResource);
int        resourceId        = Context.Resources.GetIdentifier(resourceName, "drawable",
                                        Context.PackageName);

bmp = BitmapDescriptorFactory.FromResource(resourceId);
marker.SetIcon(bmp);

Setting markers and their custom icons will be done when the map is to be rendered (i.e. in the OnElementPropertyChanged override of our renderer). That is best done once when the map will be ready.

To know when the map is ready, we must implement the IOnMapReadyCallback Interface which defines one only method: void OnMapReady(GoogleMap googleMap). With this in place, google maps will call us when the map is ready.

Our renderer will thus now look like this:

public class iCustomMapViewRenderer : MapRenderer, GoogleMap.IInfoWindowAdapter,
IOnMapReadyCallback

Our OnMapReady method will set variables required for drawing the map.

 

GoogleMap            _map;

public void OnMapReady (GoogleMap googleMap)
{
    _map        = googleMap;
    _map.InfoWindowClick    += Map_InfoWindowClick;

    // required if you wish to handle info window and its content
    _map.SetInfoWindowAdapter (this);
}

OnElementPropertyChanged of our renderer will proceed to putting the pins on the map:

 

 

bool                _isDrawingDone;

protected override void OnElementPropertyChanged (object sender,

                                        PropertyChangedEventArgs e)
{
    base.OnElementPropertyChanged (sender, e);

    if(_map == null || _isDrawingDone || e.PropertyName != "VisibleRegion")
        return;

    _map.Clear ();
    _map.MarkerClick            += HandleMarkerClick;
    _map.InfoWindowClick    += Map_InfoWindowClick;

    foreach (var pin in _mapPins)
    {
        var            marker        = new MarkerOptions ();
        iCustomPin    customPin    = pin.BindingContext as iCustomPin;

        marker.SetPosition (new LatLng (pin.Position.Latitude, pin.Position.Longitude));
        marker.SetTitle (pin.Label);

        if(! string.IsNullOrEmpty(pin.Address))
            marker.SetSnippet(pin.Address);

        BitmapDescriptor    bmp        = null;

        string    resourceName    =
                    System.IO.Path.GetFileNameWithoutExtension(customPin.IconResource);
        int        resourceId        = Context.Resources.GetIdentifier(resourceName,
                                                "drawable", Context.PackageName);

        bmp = BitmapDescriptorFactory.FromResource(resourceId);
        marker.SetIcon(bmp);

        _map.AddMarker(marker);
    }

    _isDrawingDone = true;
}

 

Customizing the popup info window

Customizing the popup info window is a different kettle of fish ('une autre paire de manche' en françaisJ)

Why?

Simply because the google maps documentation says that the info window is a View, but specify this interesting note:

Note: The info window that is drawn is not a live view. The view is rendered as an image (using View.draw(Canvas)) at the time it is returned. This means that any subsequent changes to the view will not be reflected by the info window on the map.

   

To update the info window later (for example, after an image has loaded), call showInfoWindow(). Furthermore, the info window will not respect any of the interactivity typical for a normal view such as touch or gesture events. However you can listen to a generic click event on the whole info window as described in the section below.

 

So, it IS a View… but NOT REALLYJ

Let us be more specific: it is NOT a view. It is actually a Bitmap.

 

Anyway, to be able to customize the info window, our renderer must implement the GoogleMap.IInfoWindowAdapter Interface. Which defines two methods:

  • View GetInfoContents(Marker marker);
  • View GetInfoWindow(Marker marker);

 

GetInfoWindow: returns the entire popup window. If it returns null, then it is GetInfoContents which will define the contents of the default info window frame of the marker popup.

As the 'pseudo-view' returned in both cases are not really views but bitmaps, our task is to compose a view of the pin information before transforming this to a bitmap.

 

In our current exercise, we will return the window content. Therefor our GetInfoWindow() will return null.

public Android.Views.View GetInfoWindow(Marker marker)
{
    return null;
}

From Xamarin Forms Xaml à to Native View à to Bitmap: dangerous tour!

As I am not a 'droid boy', I decided to create a Xamarin Forms control (ContentView) and use this in the custom renderer. You may of course decide differently to use a Droid axml control. In both ways, we will have to convert that control's content into Bitmap.

Here is my ContentView Xaml (assumed to be bound to an iCustomPin):

 

<StackLayout Spacing="4" >
    <Image Source="{Binding ImageFile}"
            WidthRequest="84" HeightRequest="84" HorizontalOptions="StartAndExpand" />
    <Label FontAttributes="Bold" Text="{Binding Name}" />
    <BoxView HeightRequest="1" BackgroundColor="Gray"
                HorizontalOptions="FillAndExpand" VerticalOptions="Start" />
    <Label x:Name="labelText" Text="{Binding MapPinText}" LineBreakMode="WordWrap" />
</StackLayout>

Our GetInfoContents method will have to:

  • Identify the clicked custom pin
  • Set the binding context of the above ContentView to that pin
  • Transform that XF control to a native Droid ViewGroup
  • Transform the resulting droid native ViewGroup into a bitmap to be returned to Google maps.

The following code illustrates these steps:

 

 

iMapPinInfoCtrl        xamInfoPanel    = new iMapPinInfoCtrl();

public Android.Views.View GetInfoContents(Marker marker)
{
    iCustomMap     myMap         = this.Element as iCustomMap;
    iCustomPin     customPin     = myMap.GetPinAtPosition(
                new Position(marker.Position.Latitude, marker.Position.Longitude));
    double            infoWidth        = this.Control.Width * 0.70,
                        infoHeight        = 380.0 / 2.8;
    SamplePlace    place                = customPin.DataObject as SamplePlace;

    xamInfoPanel.BindingContext = place;

    // get the droid native control
    ViewGroup    viewGrp        = DroidXFUtilities.ConvertFormsToNative(
                xamInfoPanel.Content, new Rectangle(0, 0, infoWidth, infoHeight));

    // transform the native control into a bitmap
    Bitmap        bmp            = DroidXFUtilities.ViewGroupToBitmap(viewGrp,
                                                    this.Context,
                                                    (int)infoWidth, (int)infoHeight,
                                                    true);

    _pinImage.SetImageBitmap(bmp);

    return _pinImage;
}

To Native ViewGroup

An interesting post from Michael Ridland helped solve this task!

Get the native droid ViewGroup of XF View code snippet:

 

public static ViewGroup ConvertFormsToNative(Xamarin.Forms.View view,
                                            Rectangle size)
{
    var        vRenderer        = Platform.CreateRenderer(view);
    var        viewGroup        = vRenderer.ViewGroup;

    vRenderer.Tracker.UpdateLayout();

    var        layoutParams    = new ViewGroup.LayoutParams((int)size.Width, (int)size.Height);

    viewGroup.LayoutParameters        = layoutParams;
    viewGroup.DrawingCacheEnabled    = true;
    view.Layout(size);
    viewGroup.Layout(0, 0, (int)size.Width, (int)size.Height);
    return viewGroup;
}

Native ViewGroup to Droid.Graphics.Bitmap

I must first confess that this task was hard for meJ.

I read some articles about the subject, of which this interesting one "Converting Views to Bitmap Images in Android"… but, in the practical exercise, couldn't grasp how to get precise measures of rendered elements!

The task steps is to:

  • Create a Linear Layout
  • Create a Bitmap and put it into a Canvas
  • Loop through the ViewGroup's elements (Views) and add them to the Layout
  • Draw the Layout into the Canvas
  • Return the Bitmap (which will contain the rendered layout's views)

Getting the precise measures of views is not simple. Notably for images and labels with text wrap attribute.

Here is a simplified snippet of my code (assuming that no images are part of the ViewGroup's elements). The solution's source code has more details about handling images. But you should be able to do much better if you are an android expertJ

 

 

public static Android.Graphics.Bitmap ViewGroupToBitmap(ViewGroup viewGroup,
                                             Context context,
                                             int width, int height)
{
    int        viewCount = viewGroup == null ? 0 : viewGroup.ChildCount;
    Android.Widget.LinearLayout layout        = new Android.Widget.LinearLayout(context);
    Bitmap    bmpLayout    = Bitmap.CreateBitmap(width, height, Bitmap.Config.Argb8888);
    Android.Graphics.Color    white        = Android.Graphics.Color.Argb(0xff, 0xff, 0xff, 0xff);

    layout.DrawingCacheEnabled            = true;
    layout.SetBackgroundColor(white);

    Canvas    canvas    = new Canvas(bmpLayout);

    // add the sub views contained in this view group
    for (int ndx = 0; ndx < viewCount; ndx++)
    {
        Android.Views.View view = viewGroup.GetChildAt(0);

        int            wid            = Math.Max(0, view.MeasuredWidth),
                        hi                = Math.Max(0, view.MeasuredHeight);

        viewGroup.RemoveView(view);
        layout.AddView(view, wid, hi);
    }

    layout.Draw(canvas);

    return bmpLayout;
}

 

The result looks like this:

 

You may Download the code Here

XML – the ASCII of our era

At the dawn of XML, Tim Bray once said "XML is the ASCII of the future"… which is now clearly the case!

 

To ease reading and handling XML files, I started writing an XML browser last year. The 'Open file' menu of the app first proposed '*.xml' file extension.

As you may imagine, quite quickly that expanded to so many other file extensions: *.xml; *.xaml; *.xsd; *.wsdl; *.csproj; *.vbproj; *.dwproj; *.rptproj; *.config; *.dim; *.ds?; *.cube; *.partitions; *.manifest; *.database; *.rsd; *.rds; *.rdl the list is actually endless!

Why write a XML browser / editor?

Most XML tools I found look more like good (some excellent) editors / syntax visualizers. What I needed more was to have a logical image of the tree structure of an XML file: which node is parent of which…

Xml Note Pad did part of this but was more text-editor-oriented than logical structure visualizer. Its most recent version also dates 2007… time is running!

Of course there is not one sole way to handle such question. Visualizing the tree structure is one aspect. Editors with syntax highlighting, nodes expand/collapse… are still of course very useful.

Let us take a simple user control xaml file with the code similar to this:

 

Viewing the above code's node tree helps better understand its structure:

 

How can we handle XML tree presentation?

XML code is composed of some foundation elements:

  • The element name (the xml tag… which is part of a namespace).
  • The element property (or properties). Which may be either:
    • A value (string / primitive type)
    • A composite element:
      • A set of attributes
      • And / or a set of child elements

 

A simple example:

<person gender="female">

Element = person, gender = an attribute whose value is female

  <firstname>Anna</firstname>

firstname (property of person) whose value is Anna

  <lastname>Smith</lastname>

lastname (property of person) whose value is Smith

</person>

End of the composite element person

 

The above code can also be written like this (1st attribute shifted to a property level):

<person>
  <gender>female</gender>
  <firstname>Anna</firstname>
  <lastname>Smith</lastname>
</person>

 

You may look here for more information about the XML model.

 

At our .net level, we have objects defined in System.Xml.Linq that allow us to (more or less easily) explore, traverse and query a XML tree. Some of these:

  • XDocument (derives from à XContainer à XNode à XObject)
  • XElement (derives from à XContainer à XNode à XObject)

 

These xml-specialized objects are indispensable to safely communicate with the xml model, but are not good candidates for a view to represent the xml tree.

In some way, in a MMVM pattern, they represent the model (and business logic). We need an intermediate component that can be integrated into a view model and a view.

Property Bags to the rescue

I briefly mentioned property bags in a previous post about MSBuild browser. Let me elaborate a little more about this.

A property bag is a set of abstracted properties. Where a property is presented as:

  • A name
  • A data type
  • A value

And where the property 'value' can be either:

  • An object or primitive value (string for instance)
  • Or a set of properties (i.e. a property bag)

 

This model proved quite useful in many contexts. You may for instance transform any object into a property bag representing its properties (in a strongly-typed form). This can be quite useful in situations where loose coupling is a concern (applications ßà services for instance). I will talk about this in details later in a following post.

 

For our current question about presenting xml trees, property bag model seems to be in sync with the xml model:

Xml element:

name / value / attributes / sub-elements (children)

Property bag element:

name / value / sub-elements (children)

 

To clarify by the practice, here is an example for parsing a XElement attributes into a property bag:

 

static PropertyBag ParseXmlNodeAttributes(XElement xnode, ObjProperty parent)
{
    // create a property bag as a child of the parent property
    PropertyBag targetBag    = new PropertyBag(parent);

    // add the xml element attributes to the bag
    foreach (XAttribute attrib in xnode.Attributes())
    {
        targetBag.Add(attrib.Name.LocalName, attrib.Value);
    }

    return targetBag;
}

 

Parsing a XElement can be done in a few steps:

  • Create an ObjProperty with name = xml element's name
  • If the element's value is composite (attributes and/or child items): add element's attributes and children to the ObjProperty Children
  • Otherwise set the ObjProperty value to the element's value

 

The following code illustrates this (simplified version of the code for clarity)

 

public static PropertyBag ParseXml(XElement xnode, ObjProperty parent)
{
    string            propertyName    = xnode.Name.LocalName;
    PropertyBag    bagObject        = new PropertyBag(propertyName);
    ObjProperty    property            = new ObjProperty(propertyName);
    PropertyBag    bagAttributes    = new PropertyBag();
    PropertyBag    bagSubItems    = new PropertyBag();

    // get the XEelement attributes' bag
    bagAttributes    = ParseXmlNodeAttributes(xnode, property);

    // add attributes (if any) to Children
    if (bagAttributes.Count > 0)
    {
        bagObject.AddRange(bagAttributes);
    }

    // add sub elements if any
    var        subNodes        = xnode.Descendants();

    foreach(var element in subNodes)
    {
        string            nodeName        = element.Name.LocalName;
        PropertyBag    elementBag    = ParseXml(element, property);

        ExtractPropertyValue(bagSubItems, elementBag, nodeName, property);
    }

    bagObject.AddRange(bagSubItems);

    return bagObject;
}

 

The Xml Property Bags' view model

Presenting a property bag in a view requires some additional properties that are specific for a view. For instance, an 'Expanded' property for a tree node, a 'Selected' property for an item, as well as useful commands and search/filter features… etc.

Our property bag view model will embed the bag item and adds such properties.

 

The Xml Property Bags' Views

We need to present our xml property bags in either a tree view or a grid view.

As I said earlier, a property bag item contains either a primitive value (which may be presented as a string) or a set of child items.

In a WPF tree view that can be presented like this (code simplified for clarity):

    <!-- import view model namespace -->
    xmlns:vm="clr-namespace:iSoapExplorer2015.viewmodel"


<TreeView x:Name="treeView1" ItemsSource="{Binding Containers}">

    <TreeView.ItemTemplate>
        <!-- hierarchial data template for containers items -->
        <HierarchicalDataTemplate
                    DataType="{x:Type vm:PropertyBagItemVM}"
                    ItemsSource="{Binding Containers}" > <!-- Containers = property bags -->
            <StackPanel Orientation="Horizontal">
                <!—note: 'Node' is the property bag item -->
                <TextBlock Text="{Binding Node.Name}"/>    
            </StackPanel>
        </HierarchicalDataTemplate>

    </TreeView.ItemTemplate>

 

That results is a view that looks like the one at the beginning of this post (with an image added to the tree view item template)

A data grid view may look like this (again: code simplified for clarity):

 

<DataGrid ItemsSource="{Binding }">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Property" Binding="{Binding Node.Name}" />
        <DataGridTemplateColumn Header="Value" Binding="{Node.ValueAsString}" />
    </DataGrid.Columns>
</DataGrid>

For a xaml Stack Panel snippet element, our view may look like this (an image column added to the above simplified code):

 

With some more work, you may implement more useful features and visual effects.

The current version offers: Xml nodes Tree and Grid views, Search nodes (by name and / or value), get the selected node XML, Export the entire tree to an xml file… etc.

 

 

I stopped short from editing the nodes (names / values)… but you may add such feature (before I do… probably in a near futureJ)

 

Here is the current version's binaries.

Have fun adding new features to the source code if you would like to.

Xamarin forms – Platform startup: Forms.Init() visit!

As XF documentations say, your platform-application (Droid, iOS or WP) should call Forms.Init() at startup.

That should be done:

  • In iOS: in the AppDelegate.FinishedLaunching method
    • global::Xamarin.Forms.Forms.Init();
  • In Droid: in the MainActivity. OnCreate (Bundle bundle)
    • global::Xamarin.Forms.Forms.Init(this, bundle);
  • In Win Phone: in your application's MainPage constructor
    • global::Xamarin.Forms.Forms.Init();

 

 

 

 

What does Forms.Init do?

On droid

Here is an example of what Forms.Init method does on Android:

 

public static void Init(Activity activity, Bundle bundle)
{
    // get the calling assembly 
    Assembly callingAssembly = Assembly.GetCallingAssembly(); 

    // Call SetupInit 
    SetupInit(activity, callingAssembly); 
}

 

Let us continue following the call to SetupInit():

 

private static void SetupInit(Activity activity, Assembly resourceAssembly) 
{
    // set the Context to current activity 
    Context = activity; 

    // initialize resources for this assembly 
    ResourceManager.Init(resourceAssembly); 

    // set the AccentColor according to OS version 
    if (Build.VERSION.SdkInt <=    BuildVersionCodes.GingerbreadMr1) 
    { 
       Color.Accent = Color.FromHex("#fffeaa0c"); 
    } 
    else 
    { 
       Color.Accent = Color.FromHex("#ff33b5e5"); 
    } 

    // log (if not initialized) 
    if (!IsInitialized) 
    { 
       Log.get_Listeners().Add(new DelegateLogListener( 
 (c, m) => Trace.WriteLine(m, c))); 
    } 

    // set the Device.OS version and platform services (here android) 
       Device.OS = TargetPlatform.Android; 
       Device.PlatformServices = new AndroidPlatformServices(); 

    // recreate the device info from this activity 
    if (Device.info != null) 
    { 
       ((AndroidDeviceInfo) Device.info).Dispose(); 
       Device.info = null; 
    } 

    IDeviceInfoProvider formsActivity = activity as IDeviceInfoProvider; 

    if (formsActivity != null) 
    { 
       Device.Info = new AndroidDeviceInfo(formsActivity); 
    } 

    // recreate the ticker 
    AndroidTicker ticker = Ticker.Default as AndroidTicker; 

    if (ticker != null) 
    { 
       ticker.Dispose(); 
   } 

   Ticker.Default = new AndroidTicker(); 

    // initialize renderers (if not initialized (again)) 
    if (!IsInitialized) 
    { 
       // register renderers for attributes export / cell / image source 
       Type[] typeArray1 = new Type[] 
                   { typeof(ExportRendererAttribute), 
                   typeof(ExportCellAttribute), 
                   typeof(ExportImageSourceHandlerAttribute) 
                   }; 

       // registrer the handlers of these types 
       Registrar.RegisterAll(typeArray1); 
    } 

    // set the device idiom according to screen width dpi 
    Device.Idiom = (Context.Resources.Configuration.SmallestScreenWidthDp >= 600) 
             ? TargetIdiom.Tablet : TargetIdiom.Phone; 
 
    // set search expression default (if not already set) 
    if (ExpressionSearch.Default == null) 
    { 
       ExpressionSearch.Default = new AndroidExpressionSearch(); 
    } 

    // set initialzed flag 
    IsInitialized = true; 
} 

 

 

 

On iOS

 

public static void Init() 
{ 
    if (!IsInitialized) 
    { 
       // set initialized flag 
       IsInitialized = true; 

       // set the AccentColor 
       Color.Accent = Color.FromRgba(50, 0x4f, 0x85, 0xff); 

       Log.get_Listeners().Add(new DelegateLogListener( 
             // obscure decompilation of an anonymous methodJ 
          <>c.<>9__9_0 
             ?? (<>c.<>9__9_0 = new Action<string, string>      (<>c.<>9.<Init>b__9_0)))); 

       // device os and platform services 
       Device.OS = TargetPlatform.iOS; 
       Device.PlatformServices = new IOSPlatformServices(); 

       Device.Info = new IOSDeviceInfo(); 

       // set the ticker 
       Ticker.Default = new CADisplayLinkTicker(); 

       // register renderers for attributes export / cell / image source 
       Type[] typeArray1 = new Type[] 
                      { typeof(ExportRendererAttribute), 
                      typeof(ExportCellAttribute), 
                      typeof(ExportImageSourceHandlerAttribute) 
                      }; 

       Registrar.RegisterAll(typeArray1); 

       // set device idiom 
       Device.Idiom = (UIDevice.get_CurrentDevice  ().get_UserInterfaceIdiom() == 1L) 
             ? TargetIdiom.Tablet : TargetIdiom.Phone; 

       // set search expression default 
       ExpressionSearch.Default = new iOSExpressionSearch(); 
    } 
}

 

 

On Win Phone

 

public static void Init()
{
    if (!isInitialized)
    {
        // create an event trigger object (why?) 
        // note: constructor initializes an EventNameProperty DependencyProperty 
        new EventTrigger();

        string name = Assembly.GetExecutingAssembly().GetName().Name;
        ResourceDictionary dictionary1 = new ResourceDictionary();

        // load xaml resources 
        dictionary1.set_Source(
                new Uri(string.Format("/{0};component/WPResources.xaml", name),
                UriKind.Relative));

        // add resources to merged dictionaries 
        Application.get_Current().get_Resources().get_MergedDictionaries().Add(dictionary1);

        // set accent( color from resources 
        Color color = (Application.get_Current().get_Resources().get_Item("PhoneAccentBrush") as SolidColorBrush).get_Color();

        byte introduced3 = color.get_R();
        byte introduced4 = color.get_G();
        byte introduced5 = color.get_B();

        Color.Accent = Color.FromRgba((int)introduced3,
                                (int)introduced4,
                                (int)introduced5,
                                (int)color.get_A());

        // log 
        Log.get_Listeners().Add(new DelegateLogListener(<> c.<> 9__3_0
                    ?? (<> c.<> 9__3_0 = new Action<string,
                    string>(<> c.<> 9.< Init > b__3_0))));

        // set device os and platform services 
        Device.OS = TargetPlatform.WinPhone;
        Device.PlatformServices = new WP8PlatformServices();
        Device.Info = new WP8DeviceInfo();

        // register renderers for attributes export / cell / image source 
        Type[] typeArray1 = new Type[]
                    { typeof(ExportRendererAttribute),
                            typeof(ExportCellAttribute),
                            typeof(ExportImageSourceHandlerAttribute)
                    };

        Registrar.RegisterAll(typeArray1);

        // set ticker default 
        Ticker.Default = new WinPhoneTicker();

        // set device idiom 
        Device.Idiom = TargetIdiom.Phone;

        // set search expression default 
        ExpressionSearch.Default = new WinPhoneExpressionSearch();

        // set initialzed flag 
        isInitialized = true;
    }
}

 

 

 

Xamarin forms: an inside look – II. The (simplified) journey of a Label from your code to screen

Understanding how your UI code ends up by being shapes and colors on a device screen may help in better usage, better interpretation for encountered issues and for evaluating if you really need a custom renderer for a given control.

Exploring this also explains some of the interesting internal Xamarin forms mechanisms to play its awesome cross-platform game!

To do this, I will here attempt to explore the journey of a simple control: The Label.

For simplicity, I will track the journey to an Android device.

Label hierarchy

Label is a class defined Xamarin.Forms.Core.dll. Its hierarchy is roughly the following:

 

The Label renderer

At the end of this simple control processing chain, we have the android's Label renderer responsible of drawing it on the screen of each device… The renderer hierarchy (on Android):

The mono component

The ViewRenderer (XF Platform.Android.dll) is defined as:

public abstract class ViewRenderer<TView, TNativeView>

: VisualElementRenderer<TView>,

global::Android.Views.View.IOnFocusChangeListener,

IJavaObject,

IDisposable

 

It inherits the VisualElementRenderer (abstract) class which is defined as:

public abstract class VisualElementRenderer<TElement>

: FormsViewGroup,

IVisualElementRenderer,

IRegisterable,

IDisposable,

global::Android.Views.View.IOnTouchListener,

IJavaObject,

global::Android.Views.View.IOnClickListener

where TElement : VisualElement

 

Both of them refer to objects from the Android.Views and Android.Runtime namespaces defined in Mono.Android.dll.

 

The Label renderer referenced types, inheritance and interface implementations (summary)

 

Label renderer calls (summary)

The IRegisterable and Registrar link nodes

As you can notice, both ViewRenderer and VisualElementRenderer implement the IRegisterable Interface defined in XF.Core.dll.

 

IRegistrable is implemented by all Renderers. And is referenced by the Registrar class (we will see later).

Let us follow IRegistrable implemented child tree till the Label Renderer node:

(Incidentally, in he above figure, we again notice our ImageSourceHandler at the very root of the tree… you may look at my notes here)

The key node: XF Registrar

Registrar (Xamarin.Forms.Core.dll) is an internal static class. It exposes (internally) some methods for registering generic handlers of which we find the Renderers.

The method RegisterAll(Type[] attrTypes) of the Registrar class proceeds registration of the input array of types (note: as the class is internal, I could not find – for now – who calls this method: see a sample call at the end of this post).

If we follow the method's decompiled code, we understand that items of the Type array argument are expected to be each decorated with an attribute of type HandlerAttribute (deriving from System.Attribute). Here is a simplified version of the method's code:

 

internal static void RegisterAll(Type[] attrTypes)
{
    Assembly[] assemblies = Device.GetAssemblies();

    foreach (Assembly assembly2 in assemblies)
    {
        foreach (Type type in attrTypes)
        {
            Attribute[] attributeArray = Enumerable.ToArray<Attribute>(CustomAttributeExtensions.GetCustomAttributes(assembly2, type));
            foreach (HandlerAttribute attribute in attributeArray)
            {
                if (attribute.ShouldRegister())
                {
                    Registered.Register(attribute.HandlerType, attribute.TargetType);
                }
            }
        }
    }
}

 

 

HandlerAttribute exposes two members: HandlerType and TargetType, both are of type System.Type

 

Few attributes derive from this HandlerType attribute. Of which you have… oh… the famous ExportRendererAttribute!

 

And that is roughly how XF finds the renderer to call for a given control on each platform.

Sample call to Registrar at droid application launch in AppCompat:

Xamarin forms backyard - exploring MSBuild projects

In a previous post, I talked about how to manually change the project (.csproj) file to create an App.xaml and App.xaml.cs files in a Xamarin forms project. The reason for this is to be able to declare and use global application resources (styles, assets… etc.) linked to the main application file (App.cs changed to App.xaml.cs). That is, in a way, to repair a guilty XF project template that doesn't create these items by default.

Like in all development processes, when you use xamarin forms, you spend your time coding and building your projects.

In Windows, this essential build process goes through MSBuild which reads and interprets .csproj file's instructions into specific actions to compile and package your applications.

Those .csproj files are XML files containing configurations, variables and instructions… many can be manipulated in Visual Studio or Xamarin Studio. Still, some of these configurations require a deeper dive into MSBuild details. Your project, for instance, may sometimes fail to build for reasons that are difficult to trace without such dive.

Diving into MSBuild is something different from simply reading the .csproj xml instructions.

Understanding the way these instructions would be interpreted and handled by MSBuild is crucial for your investigations success.

MSBuild API to the rescue

Fortunately, MSBuild offers an API that allows you to explore or modify the build engine behavior according to your requirements.

I used this API to build a simple MSBuild browser (Windows WPF application) that allows you to examine the detailed interpretations of MSBuild to a .csproj or .vbproj file.

A project configuration browser

The browser uses the objects defined in the MSBuild Microsoft.Build.Evaluation namespace.

Three main view models of the application are used to represent MSBuild objects:

  • iProject: msbuild project view model. Encapsulates the collection of:
  • iProjectItemType: project item types (and actions). Each project item type encapsulating a collection of:
  • iProjectItem: an item of the related type. This object encapsulates a property bag of the related MSBuild item's properties (For instance: metadata).

 

PropertyBag object is used to allow future application extensibility. It parses MSBuild objects' properties independently of the version referenced (the current application references MSBuild 14.0.0.0).

 

The result looks like this:

 

You can download the binaries Here

If you are interested in extending features, you can download the source code here.

 

Xamarin forms – the OnPlatform<T> heavens!

Xamarin forms offer this class (OnPlatform<T>) of great help to adapt your code (either c# or Xaml) to the current runtime device's platform.

So you may write:

string helloDevice = Device.OnPlatform<string>(
            "Hello iOS", "Hello Droid", "Hello Win Phone");

 At runtime, on iOS, your string would have the related platform value:

In Xaml, you specify the type to which the OnPlatform should act and the values for each platform:

<ContentPage.Padding>
   <OnPlatform x:TypeArguments="Thickness">
      <OnPlatform.iOS>0, 20, 0, 0</OnPlatform.iOS>
      <OnPlatform.Android>0, 0, 0, 0</OnPlatform.Android>
      <OnPlatform.WinPhone>0, 0, 0, 0</OnPlatform.WinPhone>
   </OnPlatform>
</ContentPage.Padding>

The main difficulty in Xaml is that you should know exactly the Type to pass as TypeArgument.

For system known types, you should use the x: prefix like in:

x:TypeArguments="x:String"
x:TypeArguments="x:Double"
x:TypeArguments="x:Int32"

For Xamarin Forms objects properties and types, it is easy to use the Visual Studio Object Browser to obtain the correct information:

Here, for instance, you know that the Padding is of type Xamarin Forms Thickness:

    <OnPlatform x:TypeArguments="Thickness">

 

Xamarin forms maps – let’s talk renderers: 1. Win Phone

I continue about XF maps, where a custom renderer is needed if you want to customize appearance and interaction.

As most Xamarin forms samples about maps expose renderers for Droid and iOS (I did not see any about WP), I will start by exposing a sample for that 'missing' renderer. Another post will follow with some ideas about the renderers for the other platforms.

 

In this sample, we handle SamplePlace objects (see below) through our custom map and custom pin objects (see previous post)

What we want:

  • Display custom pushpins (icons specified for each custom pin)
  • When a pushpin is clicked, display a custom information callout about the place.

 

The way to do this:

The renderer (which derives from MapRenderer) receives our custom map, containing the map pins. The binding context of each Pin is set to the related custom pin. The place data is accessed through the custom pin's DataObject.

The renderer has two levels of duties:

  • A 'formal' role inherited from the default renderer: create and display the specific platform map control, position the pins on it and define the interaction with the control's events (map taps / pins taps / callout taps…).
  • A customization role: use our specific embedded information to customize pins' appearance and callouts.

 

The renderer code

Our renderer class:

public class iCustomMapRenderer : ViewRenderer<iCustomMap, Map>


For Xamarin.Forms.Maps to retrieve our renderer at runtime, it should be 'exported'. The code should now be like the following:

 

// Note: ExportRendererAttribute is defined in Xamarin.Forms namespace
[assembly: ExportRenderer(typeof(iCustomMap), typeof(iCustomMapRenderer))]

namespace iMaps.WinPhone
{
	public class iCustomMapRenderer : ViewRenderer<iCustomMap, Map>
	{
		private Map				_winMap;			// the win phone map control
		private iCustomMap		_xfMap;				// the xamarin forms map we are handling
		private iCustomPin		_selectedPin;	// our current slected pin

		// the callout user control that will display selected place info
		UserControl	_placeInfoCtrl	= new UserControl()
		{
			HorizontalAlignment	= HorizontalAlignment.Stretch,
			VerticalAlignment		= VerticalAlignment.Stretch,
			MinHeight			= 300,
			Background			= whiteBrush,
		};
		…



 

Here is a global view of the renderer's methods:

 

In the ElementPropertyChanged (override), we will:

  • Create the win phone map control.
  • Parse the received pins, and use their specified icons as pushpins.
  • Subscribe to each pushpin Tap event to display the callout.
  • Subscribe to the map Tap event to hide any displayed callout.

 

// Center the map on the 1st pin
var			pin	= _xfMap.Pins[0];

// create the win phone map control. set as the native control
_winMap		= new Map
		{
			ZoomLevel = 13,
			Center = new GeoCoordinate(pin.Position.Latitude, pin.Position.Longitude)
		};

this.SetNativeControl(_winMap);

// subscribe to map Tap event (to unselect last pin if any)
_winMap.Tap		+= WinMap_Tap;

// AddCurrentLocationToMap();
// loop through the received pins. display each with its custom pin icon
foreach (var formsPin in _xfMap.Pins)		//.CustomPins)
{
	var			pushPin		= new Pushpin();
	iCustomPin	customPin	= formsPin.BindingContext as iCustomPin;
	SamplePlace	place		= customPin == null ? null 
										: customPin.DataObject as SamplePlace;

	// subscribe to the pushpin tap event (to display the callout)
	pushPin.Tap += PushPin_Tap;

	// set the pushpin position on the map
	var geoCoordinate			= new GeoCoordinate(formsPin.Position.Latitude, formsPin.Position.Longitude);
	pushPin.GeoCoordinate		= geoCoordinate;

	// set the pushpin tag to the custom pin
	pushPin.Tag					= customPin;
	…
	…



The custom pushpin icon

To put a UI element on the map, you create a MapOverlay containing the element on the desired location (geo coordinate) and you add that MapOverlay to a Layer that you put on the map.

A Pushpin (defined in Microsoft.Phone.Maps.Toolkit namespace) is a UI element that exposes a Content property (of type ContentControl) which we can set to our image of choice (the one specified by the custom pin object for instance). In that case, the pushpin would look like this:

We may also directly put our image on the MapOverlay. In that case that would be like this:

 

The callout control

Let us create a (win phone) xaml UserControl for displaying place object's information. We will use this when a pushpin will be tapped.

 

<Border x:Name="LayoutBorder" Background="Transparent" Height="auto" Padding="8" >
	<Grid VerticalAlignment="Top" Background="Transparent">
		<Grid.RowDefinitions>
			<RowDefinition Height="24" />
			<RowDefinition />
		</Grid.RowDefinitions>
		<Grid.ColumnDefinitions>
			<ColumnDefinition Width="24" />
			<ColumnDefinition />
		</Grid.ColumnDefinitions>

		<Image Source="{Binding Source, Source={StaticResource imgMapPointer}}" Grid.Column="0" Grid.Row="0" Stretch="Fill" Height="24" Width="24" HorizontalAlignment="Left" VerticalAlignment="Top" />

		<Border Padding="8" Background="White" Grid.Row="0" Grid.Column="1" Grid.RowSpan="2">
			<StackPanel>
				<Image x:Name="imgPlace" Height="128" Width="128" HorizontalAlignment="Left" Source="{Binding Converter={StaticResource placeImageConverter}}" Margin="8" />
				<TextBlock FontWeight="Bold" Text="{Binding Name}" Foreground="Black" />
				<Rectangle Height="1" Fill="Black" HorizontalAlignment="Stretch" />
				<TextBlock Margin="8,0" FontSize="14" Text="{Binding MapPinText}" Foreground="Black" />
			</StackPanel>
		</Border>
	</Grid>
</Border>



 

At design time, the code above would look like:

At runtime, the Pushpin Tap event handler will set the control's DataContext to the selected SamplePlace object. It will then display its image and information.

The pushpin Tap event handler determines the currently selected place (through the pushpin.Tag) and calls this method to update the control:

 

void UpdateInfoControlContents(SamplePlace place)
{
	_placeInfoCtrl.ClearValue(UserControl.ContentProperty);

	if(place == null)
		return;

	PlacePushPinInfoCtrl ctrl = new PlacePushPinInfoCtrl() { DataContext = place };

	_placeInfoCtrl.Content = ctrl;
}



 

 

There is certainly still much to do for these mechanics to produce something elegant and informative.

That may just be a good start.... Download the code and have more fun!

In a following post I will talk about some ideas for Droid and iOS map renderers (also included in the sample).

Xamarin forms: appCompat & rounded buttons, cleaner solution!

This is a follow up for my last post about AppCompat and rounded buttons where your custom renderer would not be called!

In that post, I exposed a 'hack' solution which is better be avoided.

On this page (the same where Kelly Adams presented a decompiled code of the AppCompat launcher), Thomas Omer-Decugis exposed a simple and efficient solution: you create a new class that derives from Button and create (in the Droid project) your custom renderer for this class.

In that case, your renderer is called and can proceed to handling properties like BorderRadius that are ignored by AppCompat.

 

public class iRoundedButton : Button 

In this screen capture, the first 2 buttons are standard buttons (where border radius is not handled by AppCompat)

The following 2 are defined as: (xmlns:local="clr-namespace:iRoundedButton")

<local:iRoundedButton … 

 

And where the custom renderer handles the border radius.

 

The custom renderer itself is the same as in the previous sample, with this minor change:

[assembly: ExportRenderer(typeof(iRoundedButton /*Xamarin.Forms.Button*/), typeof(CustomButtonCompatRenderer))]