Forum Xamarin.Forms
We are excited to announce that the Xamarin Forums are moving to the new Microsoft Q&A experience. Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

We encourage you to head over to Microsoft Q&A for .NET for posting new questions and get involved today.

How to implement bindable property for children controls in custom control?

mshwfmshwf EGMember ✭✭✭
edited September 2019 in Xamarin.Forms

I'm implementing a custom progress bar,
where it contains multiple signs of progress with different colors:

This is how I think of it:

<controls:CustomMultiProgressBar OutlineColor="White" TailText="60%">
    <controls:CustomMultiProgressBar.Progresses>
        <controls:Progress ProgressColor="LightGreen" MaxPercentage=".4"/>
        <controls:Progress ProgressColor="DarkGreen" MaxPercentage="1" Image="star.png"/>
    </controls:CustomMultiProgressBar.Progresses>
</controls:CustomMultiProgressBar>

I found this lengthy article, but it must not be that complex.

all I need (if it was possible) a bindable property like IList<View> Children where its propertyChanged delegate handle rendering.

Best Answer

  • mshwfmshwf EGMember ✭✭✭
    Accepted Answer

    I tried this:

    public IEnumerable Progresses
    {
        get { return (IEnumerable)GetValue(ProgressesProperty); }
        set { SetValue(ProgressesProperty, value); }
    }
    
    public static readonly BindableProperty ProgressesProperty =
        BindableProperty.Create(nameof(Progresses), typeof(IEnumerable), typeof(LazyControl), null, propertyChanged: ChildrenChanged);
    

    XAML:

    <controls:LazyControl>
        <controls:LazyControl.Progresses>
            <x:Array Type="{x:Type Label}">
                <Label Text="AAA"/>
                <Label Text="BBB"/>
                <Label Text="CCC"/>
            </x:Array>
        </controls:LazyControl.Progresses>
    </controls:LazyControl>
    

    Despite it's working, I don't like using the ArrayExtension.

Answers

  • LandLuLandLu Member, Xamarin Team Xamurai

    It depends on what you want to implement.
    You could iterate the hierarchy to access the parent control through the Parent grammar. Then access its bindable property as you want.
    Another approach is to create a class deriving from your controls:Progress and define the bindable property there. Therefore, you can consume this bindable in the Xaml directly.

  • mshwfmshwf EGMember ✭✭✭
    In the first approach, what should be the `Progresses` type, and where is the iteration conducted .. I didn't fully understand what you meant, I would appreciate a code example.
  • LandLuLandLu Member, Xamarin Team Xamurai

    What does your custom control look like?
    Offer your whole sample of the custom control and I will try to explain depending on your custom control.

  • mshwfmshwf EGMember ✭✭✭
    edited September 2019
    I didn't build it yet, the above code is an imaginary code.
    I built another similar control but with single progress bar, it inherit from ContentView and has Progress, ProgressColor.. etc.. bindable properties.
    The multi progress control should be built the same way, but instead I will have another Progres control that is attached to the CustomMultiProgressBar control's children.
  • LandLuLandLu Member, Xamarin Team Xamurai

    So you can access your CustomMultiProgressBar depending on the architecture.
    i.e. if I place an control in s stack layout like:

    <StackLayout>
        <Button Text="Test" x:Name="button" />
    </StackLayout>
    

    I could get the stack layout via: button.Parent;

  • mshwfmshwf EGMember ✭✭✭
    edited September 2019

    I want to make sure my problem is clear to you.
    The problem is that Xamarin doesn't allow bindable property of type IList<View>,
    For simplicity, I tried:

    XAML:

    <?xml version="1.0" encoding="UTF-8"?>
    <ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:d="http://xamarin.com/schemas/2014/forms/design"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
                 mc:Ignorable="d" 
                 x:Class="Xamapp.CustomControls.LazyControl">
        <StackLayout x:Name="stk"/>
    </ContentView>
    

    code-behind:

    [ContentProperty("Progresses")]
    public partial class LazyControl : ContentView
        {
            public LazyControl()
            {
                  InitializeComponent();
            }
    
            public IList<Label> Progresses
            {
                get { return (IList<Label>)GetValue(ProgressesProperty); }
                set { SetValue(ProgressesProperty, value); }
            }
    
            public static readonly BindableProperty ProgressesProperty =
                BindableProperty.Create(nameof(Progresses), typeof(IList<Label>), typeof(LazyControl), null, propertyChanged: ChildrenChanged);
    
            private static void ChildrenChanged(BindableObject bindable, object oldValue, object newValue)
            {
                ((LazyControl)bindable).RenderChildren();
            }
    
            private void RenderChildren()
            {
                foreach (var child in Progresses)
                {
                    stk.Children.Add(child);
                }
            }
        }
    

    client code:

    <controls:LazyControl>
        <controls:LazyControl.Progresses>
            <Label Text="AAA"/>
            <Label Text="BBB"/>
            <Label Text="CCC"/>
        </controls:LazyControl.Progresses>
    </controls:LazyControl>
    

    The problem is that it throws null reference exception (no more info provided)

  • mshwfmshwf EGMember ✭✭✭
    Accepted Answer

    I tried this:

    public IEnumerable Progresses
    {
        get { return (IEnumerable)GetValue(ProgressesProperty); }
        set { SetValue(ProgressesProperty, value); }
    }
    
    public static readonly BindableProperty ProgressesProperty =
        BindableProperty.Create(nameof(Progresses), typeof(IEnumerable), typeof(LazyControl), null, propertyChanged: ChildrenChanged);
    

    XAML:

    <controls:LazyControl>
        <controls:LazyControl.Progresses>
            <x:Array Type="{x:Type Label}">
                <Label Text="AAA"/>
                <Label Text="BBB"/>
                <Label Text="CCC"/>
            </x:Array>
        </controls:LazyControl.Progresses>
    </controls:LazyControl>
    

    Despite it's working, I don't like using the ArrayExtension.

  • AmirImamAmirImam USMember ✭✭
    edited July 22

    @mshwf said:
    I tried this:

    public IEnumerable Progresses
    {
        get { return (IEnumerable)GetValue(ProgressesProperty); }
        set { SetValue(ProgressesProperty, value); }
    }
    
    public static readonly BindableProperty ProgressesProperty =
        BindableProperty.Create(nameof(Progresses), typeof(IEnumerable), typeof(LazyControl), null, propertyChanged: ChildrenChanged);
    

    XAML:

    <controls:LazyControl>
        <controls:LazyControl.Progresses>
            <x:Array Type="{x:Type Label}">
                <Label Text="AAA"/>
                <Label Text="BBB"/>
                <Label Text="CCC"/>
            </x:Array>
        </controls:LazyControl.Progresses>
    </controls:LazyControl>
    

    Despite it's working, I don't like using the ArrayExtension.

    Still not working
    I don't understand what is the different between this code and original code ?

Sign In or Register to comment.