Trying to create a control template that inherits parent class and calls?

Jesse Knott 691 Reputation points
2021-01-05T04:05:42.54+00:00

Hello everyone,
I am trying to make a template for a form on my Xamarin.Forms project.
I want to use a SyncFusion Carousel control, and since I reuse it on a number of forms, I would like to make a xaml view that I can just instantiate on the target form saving from having to recreate the control every time.

Here is the problem, I have TapGestureRecognizers and several other Data objects that get passed to this control.
Here are the samples of how it gets implemented as this time.

            <DataTemplate x:Key="CarouselitemTemplate">
                <StackLayout Orientation="Vertical">
                    <StackLayout.GestureRecognizers>
                        <TapGestureRecognizer
                            CommandParameter="{Binding Path=.}"
                            NumberOfTapsRequired="1"
                            Tapped="OnTapped" />
                    </StackLayout.GestureRecognizers>
                    <Image
                        Aspect="AspectFit"
                        HeightRequest="350"
                        Source="{Binding Path=Image, Converter={StaticResource Key=FillImageFromBytes}}"
                        WidthRequest="325" />
                    <Label HorizontalTextAlignment="Center" Text="{Binding Path=FileName}" />
                </StackLayout>
            </DataTemplate>

....
                            <Label Text="Images" />
                            <syncfusion:SfCarousel x:Name="carousel" ItemTemplate="{StaticResource Key=CarouselitemTemplate}" />

....

What I would like to do is this.
TemplateCarouselView.xaml

<?xml version="1.0" encoding="UTF-8" ?>
<syncfusion:SfCarousel
    x:Class="BoomStick.Views.TemplateCarouselView"
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:syncfusion="clr-namespace:Syncfusion.SfCarousel.XForms;assembly=Syncfusion.SfCarousel.XForms">
    <syncfusion:SfCarousel.ItemTemplate>
        <DataTemplate>
            <StackLayout Orientation="Vertical">
                <StackLayout.GestureRecognizers>
                    <TapGestureRecognizer
                        CommandParameter="{Binding Path=.}"
                        NumberOfTapsRequired="1"
                        Tapped="{Binding Path=OnTapped}" />
                </StackLayout.GestureRecognizers>
                <Image
                    Aspect="AspectFit"
                    HeightRequest="350"
                    Source="{Binding Path=Image, Converter={StaticResource Key=FillImageFromBytes}}"
                    WidthRequest="325" />
                <Label HorizontalTextAlignment="Center" Text="{Binding Path=FileName}" />
            </StackLayout>
        </DataTemplate>
    </syncfusion:SfCarousel.ItemTemplate>
</syncfusion:SfCarousel

TemplateCarouselView.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using Syncfusion.SfCarousel.XForms;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace BoomStick.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class TemplateCarouselView : SfCarousel
    {
        public TemplateCarouselView()
        {
            InitializeComponent();
        }

        public static readonly BindableProperty OnTappedProperty = BindableProperty.Create(nameof(OnTapped), typeof(Command), typeof(Command));

        public Command OnTapped
        {
            get
            {
                return (Command)GetValue(OnTappedProperty);
            }

            set
            {
                SetValue(OnTappedProperty, value);
            }
        }
    }
}

I figured I could use the BindableProperty to allow the calling instance to add it's own pointer to the Tap Callback function.
I could then implement this view like so.

<myviews:TemplateCarouselView OnTapped="SomeFunction" />

The problems I have are thus.

  1. Can this implementation inherit the ViewModel within the template? or do I need to create BindableProperty elements for each then {Binding Path=xxx}
  2. The implementation for the OnTapped is not handing off to the TapGestureRecognizer I cannot figure out how to overload the incoming property to be accepted by the xaml implementation.
  3. Each instance that implements this View needs to have it's own overloaded OnTapped function, so I cannot just make it an internal call within the TemplateView.
  4. Can I make this template use it's own internal ViewModel while still being able to inherit the parent forms ViewModel?

Thank you for any ideas or pointers!

Cheers!
Jesse

Developer technologies .NET Xamarin
0 comments No comments
{count} votes

Accepted answer
  1. Jesse Knott 691 Reputation points
    2021-01-05T13:30:22.383+00:00

    I've finally figured out how to get this working.
    Here is the template class I had to make

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    using BoomStick.Models;
    using BoomStick.Services;
    
    using Syncfusion.SfCarousel.XForms;
    
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    namespace BoomStick.Views
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class TemplateCarouselView : SfCarousel
        {
            public DataTemplate dt;
    
            public TemplateCarouselView()
            {
                InitializeComponent();
    
                dt = new DataTemplate(() =>
                {
                    var keyLayout = new StackLayout();
    
                    var bindPhoto = new Binding()
                    {
                        Converter = new FillImageFromBytes(),
                        Path = "Image"
                    };
    
                    var bindTapped = new Binding()
                    {
                        Path = "OnTapped"
                    };
    
                    var bindLocalPath = new Binding()
                    {
                        Converter = new FillImageFromBytes(),
                        Path = "."
                    };
    
                    var tgRec = new TapGestureRecognizer()
                    {
                        CommandParameter = bindLocalPath,
                        NumberOfTapsRequired = 1
                    };
    
                    tgRec.Tapped += async (s, e) =>
                    {
                        var act = await Application.Current.MainPage.DisplayActionSheet(
                                            StringTools.GetStringResource("szPhotoOptions"),
                                            null,
                                            StringTools.GetStringResource("szOK"),
                                            ActionList.ToArray());
                        await TapHandler.ImageTapHandler((Photos)((TappedEventArgs)e).Parameter, act, KeyButton, ParentObject);
                    };
    
                    tgRec.SetBinding(TapGestureRecognizer.CommandProperty, bindTapped);
                    tgRec.SetBinding(TapGestureRecognizer.CommandParameterProperty, ".");
                    keyLayout.GestureRecognizers.Add(tgRec);
    
                    var img = new Image()
                    {
                        Aspect = Aspect.AspectFit,
                        HeightRequest = 350,
                        WidthRequest = 250,
                    };
                    img.SetBinding(Image.SourceProperty, bindPhoto);
                    var lblFileName = new Label()
                    {
                        HorizontalTextAlignment = TextAlignment.Center,
                    };
                    lblFileName.SetBinding(Label.TextProperty, "FileName");
                    keyLayout.Children.Add(img);
                    keyLayout.Children.Add(lblFileName);
    
                    return new ViewCell { View = keyLayout };
                });
    
                Carousel.ItemTemplate = dt;
            }
    
            public static readonly BindableProperty ActionListProperty = BindableProperty.Create(nameof(ActionList), typeof(List<string>), typeof(List<string>));
    
            public List<string> ActionList
            {
                get
                {
                    return (List<string>)GetValue(ParentObjectProperty);
                }
    
                set
                {
                    SetValue(ParentObjectProperty, value);
                }
            }
    
            public static readonly BindableProperty ParentObjectProperty = BindableProperty.Create(nameof(ParentObject), typeof(Object), typeof(Object));
    
            public Object ParentObject
            {
                get
                {
                    return (Object)GetValue(ParentObjectProperty);
                }
    
                set
                {
                    SetValue(ParentObjectProperty, value);
                }
            }
    
            public static readonly BindableProperty KeyButtonProperty = BindableProperty.Create(nameof(KeyButton), typeof(ImageButton), typeof(ImageButton));
    
            public ImageButton KeyButton
            {
                get
                {
                    return (ImageButton)GetValue(KeyButtonProperty);
                }
    
                set
                {
                    SetValue(KeyButtonProperty, value);
                }
            }
        }
    }
    

    I can then instantiate it like this

        <core:TemplateCarouselView
                            x:Name="carousel"
                            ActionList="{Binding Path=ActionList}"
                            KeyButton="{x:Reference KeyImageButton}"
                            ParentObject="{Binding Path=MyClass}" />
    

    It took some time, and I couldn't have done it without some help!

    Cheers!

    0 comments No comments

2 additional answers

Sort by: Most helpful
  1. Jesse Knott 691 Reputation points
    2021-01-05T05:48:58.5+00:00

    Okay, I've figured out part of the problem. The DataTemplate was not liking being defined in the XAML inline with the function. I separated it into the codebehind and assigned it there.

    The biggest problem I have now is I cannot assign a Binding to the event handler for the Tapped event in the TapGestureRecognizer.

    Is there something I am overlooking (most likely) or some method I can use to pass the {Binding Path="MyEventName"} to the Tapped event?

    Here is the code to make the template work the way I wanted.

    TemplateCarouselView.xaml

    <?xml version="1.0" encoding="UTF-8" ?>
    <syncfusion:SfCarousel
        x:Class="BoomStick.Views.TemplateCarouselView"
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:syncfusion="clr-namespace:Syncfusion.SfCarousel.XForms;assembly=Syncfusion.SfCarousel.XForms"
        x:Name="Carousel" />
    

    TemplateCarouselView.xaml.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    using BoomStick.Services;
    
    using Syncfusion.SfCarousel.XForms;
    
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    namespace BoomStick.Views
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class TemplateCarouselView : SfCarousel
        {
            public DataTemplate dt;
    
            public TemplateCarouselView()
            {
                InitializeComponent();
    
                dt = new DataTemplate(() =>
                {
                    var keyLayout = new StackLayout();
    
                    var bindPhoto = new Binding()
                    {
                        Converter = new FillImageFromBytes(),
                        Path = "Image"
                    };
    
                    var bindTapped = new Binding()
                    {
                        //Converter = new FillImageFromBytes(),
                        Path = "OnTapped"
                    };
    
                    var bindLocalPath = new Binding()
                    {
                        Converter = new FillImageFromBytes(),
                        Path = "."
                    };
                    var tgRec = new TapGestureRecognizer()
                    {
                        CommandParameter = bindLocalPath,
                        NumberOfTapsRequired = 1
                    };
    
                    /*****************************************************************************************
                                    Here is my problem line of code... I cannot pass anything to the Tapped event
                        Just to the command property, but this still doesn't seem to fire when I execute the app.
                    *****************************************************************************************/
                    tgRec.SetBinding(TapGestureRecognizer.CommandProperty, "OnTapped");
                    tgRec.SetBinding(TapGestureRecognizer.CommandParameterProperty, ".");
                    keyLayout.GestureRecognizers.Add(tgRec);
    
                    var img = new Image()
                    {
                        Aspect = Aspect.AspectFit,
                        HeightRequest = 350,
                        WidthRequest = 250,
                    };
                    img.SetBinding(Image.SourceProperty, bindPhoto);
                    var lblFileName = new Label()
                    {
                        HorizontalTextAlignment = TextAlignment.Center,
                    };
                    lblFileName.SetBinding(Label.TextProperty, "FileName");
                    keyLayout.Children.Add(img);
                    keyLayout.Children.Add(lblFileName);
    
                    return new ViewCell { View = keyLayout };
                });
    
                Carousel.ItemTemplate = dt;
            }
    
            public static readonly BindableProperty OnTappedProperty = BindableProperty.Create(nameof(OnTapped), typeof(Command), typeof(Command));
    
            public Command OnTapped
            {
                get
                {
                    return (Command)GetValue(OnTappedProperty);
                }
    
                set
                {
                    SetValue(OnTappedProperty, value);
                }
            }
        }
    }
    

    Any help would be GREATLY Appreciated!

    Cheers!!

    Jesse

    0 comments No comments

  2. Cole Xia (Shanghai Wicresoft Co,.Ltd.) 6,756 Reputation points
    2021-01-05T06:58:09.78+00:00

    Hello,

    Welcome to Microsoft Q&A!

    We can't bind Tapped event but we can bind TapGestureRecognizer.Command instead .

    Thank you.


    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.