John
John

Reputation: 1888

Binding in Control Template

Relatively new to using ControlTemplates so this may be a super easy question. But I have footer that I use on several pages, so I think a ControlTemplate would be a good idea. First problem I'm running into though is how to make the image path binded.

Here is my App.xaml file

<Application
xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:abstractions="clr-namespace:SVG.Forms.Plugin.Abstractions;assembly=SVG.Forms.Plugin.Abstractions"
x:Class="MyApp.App">
<Application.Resources>
    <ResourceDictionary>
        <Style x:Key="HomeButtonStyle" TargetType="Button">
            <Setter Property="FontSize" Value="Medium" />
            <Setter Property="TextColor" Value="White" />
            <Setter Property="FontAttributes" Value="Bold"/>
            <Setter Property="BorderWidth" Value="1"/>
            <Setter Property="BorderColor" Value="White"/>
            <Setter Property="BorderRadius" Value="5"/>
            <Setter Property="Margin" Value="16,16,16,0"/>
        </Style>
        <ControlTemplate x:Key="Menu">
            <StackLayout
              Orientation="Horizontal"
              VerticalOptions="End"
              BackgroundColor="Black"
              HeightRequest="70"
              HorizontalOptions="FillAndExpand"
              Spacing="20">
                <abstractions:SvgImage
                    x:Name="Settings"
                    SvgAssembly="{Binding SvgAssembly}"
                    SvgPath="{Binding SettingsIcon}"
                    HeightRequest="50"
                    WidthRequest="50"
                    BackgroundColor="Transparent"
                    VerticalOptions="Center"
                    HorizontalOptions="CenterAndExpand"/>
                <abstractions:SvgImage
                    x:Name="Notification"
                    SvgAssembly="{Binding SvgAssembly}"
                    SvgPath="{Binding Notification}"
                    HeightRequest="50"
                    WidthRequest="50"
                    BackgroundColor="Transparent"
                    VerticalOptions="Center"
                    HorizontalOptions="CenterAndExpand"/>
                <abstractions:SvgImage
                    x:Name="Help"
                    SvgAssembly="{Binding SvgAssembly}"
                    SvgPath="{Binding HelpIcon}"
                    HeightRequest="50"
                    WidthRequest="50"
                    BackgroundColor="Transparent"
                    VerticalOptions="Center"
                    HorizontalOptions="CenterAndExpand"/>
            </StackLayout>
        </ControlTemplate>
    </ResourceDictionary>
</Application.Resources>

And then how I am trying to use it (HomePage.xaml)...

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:abstractions="clr-namespace:SVG.Forms.Plugin.Abstractions;assembly=SVG.Forms.Plugin.Abstractions"
             x:Class="Page.HomePage">
  <StackLayout>
  ...
<ContentView ControlTemplate="{StaticResource Menu}"></ContentView>

</StackLayout>


</ContentPage>

Here is my HomePage.xaml.cs file...

public partial class HomePage : ContentPage
{
    public HomePage()
    {
        InitializeComponent();
        BindingContext = new SvgImagesViewModels();

    }
}

And the ViewModel

public class SvgImagesViewModels
{

    private readonly string _os = DeviceInfo.Hardware.OS == OperatingSystemType.iOS ? "iOS.Images" : "Droid.Images";
    /* The Assembly */ 
    public Assembly SvgAssembly => typeof(App).GetTypeInfo().Assembly;

    /* The Image Paths */ 
    public string UserAvatar => $"{_os}.avatar.svg";
    public string Notification => $"{_os}.bell.svg";
    public string RedNotification => $"{_os}.red_bell.svg"; 
    public string Delete => $"{_os}.delete.svg";
    public string HelpIcon => $"{_os}.help.svg";
    public string Home => $"{_os}.home.svg"; 
    public string SettingsIcon => $"{_os}.settings.svg";
    public string NoImage => $"{_os}.no_image.svg";
    public string Star => $"{_os}.star.svg";
    public string DownCarrot => $"{_os}.down_carrot.svg";
    public string UpCattor => $"{_os}.up_carrot.svg";
    public string RightCarrot => $"{_os}.right_carrot.svg";
    public string LeftCarrot => $"{_os}.left_carrot.svg";
}

And here is the error I am getting...

09-14 20:07:22.428 E/mono-rt (11715): [ERROR] FATAL UNHANDLED EXCEPTION: System.AggregateException: One or more errors occurred. ---> System.NullReferenceException: Object reference not set to an instance of an object.
09-14 20:07:22.428 E/mono-rt (11715):   at SVG.Forms.Plugin.Droid.SvgImageRenderer+<<OnElementChanged>b__3_0>d.MoveNext () [0x00020] in <filename unknown>:0 
09-14 20:07:22.428 E/mono-rt (11715):   --- End of inner exception stack trace ---
09-14 20:07:22.428 E/mono-rt (11715):   at (wrapper dynamic-method) System.Object:b7e27031-315a-4ca4-9b10-5b1ac793598c (intptr,intptr)
09-14 20:07:22.428 E/mono-rt (11715):   at (wrapper native-to-managed) System.Object:b7e27031-315a-4ca4-9b10-5b1ac793598c (intptr,intptr)
09-14 20:07:22.428 E/mono-rt (11715): ---> (Inner Exception #0) System.NullReferenceException: Object reference not set to an instance of an object.
09-14 20:07:22.428 E/mono-rt (11715):   at SVG.Forms.Plugin.Droid.SvgImageRenderer+<<OnElementChanged>b__3_0>d.MoveNext () [0x00020] in <filename unknown>:0 <---

So obviously I am trying to apply the BindingContext incorrectly.

Upvotes: 1

Views: 821

Answers (1)

Stephane Delcroix
Stephane Delcroix

Reputation: 16222

in your ControlTemplate, you should use {TemplateBinding}, like this:

<ResourceDictionary>
  ...
  <ControlTemplate x:Key="Menu">
    <StackLayout>
      <abstractions:SvgImage
          SvgAssembly="{TemplateBinding SvgAssembly}"
          SvgPath="{TemplateBinding SettingsIcon}" />
          ...
     </StackLayout>
   </ControlTemplate>
</ResourceDictionary>

But now that you have TemplateBindings, you need to define your own templatable Control

public class Menu : ContentView
{
    public static BindableProperty SvgAssemblyProperty =
        BindableProperty.Create("SvgAssembly", typeof(Assembly), typeof(Menu), null);

    public static BindableProperty SettingsIconProperty =
        BindableProperty.Create("SettingsIcon", typeof(string), typeof(Menu), null);

    ...
}

and you can then use it, and bind to it:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:abstractions="clr-namespace:SVG.Forms.Plugin.Abstractions;assembly=SVG.Forms.Plugin.Abstractions"
             x:Class="Page.HomePage">
  <StackLayout>
    ...
    <Menu ControlTemplate="{StaticResource Menu} SvgAssembly="{Binding SvgAssmbly}" SettingsIcon="{Binding SettingsIcon}" .../>
  </StackLayout>
</ContentPage>

This should answer your question. Now, if all you're trying to do is to have different icons for each platform, using ControlTemplate is overkill. You could use OnPlatform, or use `{x:Static}, or probably 4-5 different ways. Have fun experimenting...

Upvotes: 1

Related Questions