1
votes

I have a simple custom control that I am having trouble getting an exposed bindable Command property to work.

Here's the setup: MainPage.xaml hosts CustomControl.xaml (beige area below). CustomControl contains a Label and a Button. MainPage contains the CustomControl, Entry, Label, and a Button. All controls are bound to the CustomControlText property in the MainPageViewModel. So as that property changes, all controls should update.

It mostly works...

Look at the demo video below. I click the button on the MainPage, and all controls update including the custom control. And when I change the Entry value, all fields update. However, clicking 'Increment From CustomControl' does nothing. It should invoke the SubmitCommand2 in the MainViewModel.

enter image description here

All my code is below (simple File/New Project example). How would I change this code so clicking 'Increment From CustomControl' ultimately invokes the SubmitCommand2 Command in MainPageViewModel?

MainPage.xaml

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:App7"
             x:Class="App7.MainPage">

    <StackLayout>
        <BoxView HeightRequest="100" />

        <local:CustomControl 
            Margin="50"
            WidthRequest="300"
            TextData="{Binding CustomControlText}" 
            Command="{Binding SubmitCommand2}"
        />

        <Entry Text="{Binding CustomControlText}" />

        <Label Text="{Binding CustomControlText}" />

        <Button Text="Increment from Main Page" Command="{Binding SubmitCommand}" />

    </StackLayout>

</ContentPage>

MainPage.xaml.cs

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }
}

MainPageModel.cs

public class MainPageModel : FreshBasePageModel
{
    public MainPageModel() { }

    public string CustomControlText { get; set; }

    private int _index = 0;

    public Command SubmitCommand
    {
        get
        {
            return new Command(() =>
            {
                _index++;
                CustomControlText = $"Hello World {_index}";
            });
        }
    }

    public Command SubmitCommand2
    {
        get
        {
            return new Command(() =>
            {
                _index++;
                _index++;
                CustomControlText = $"Hello World {_index}";
            });
        }
    }

    public override void Init(object initData)
    {
        CustomControlText = "Hello World";

        base.Init(initData);
    }
}

CustomControl.xaml

<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="App7.CustomControl"
             BackgroundColor="Beige"
             x:Name="this">
    <ContentView.Content>
        <StackLayout>
            <Entry x:Name="entryControl"
                Placeholder="Enter Text"
                Text="{Binding Source={x:Reference this}, Path=TextData}"
            />
            <Button Text="Increment From CustomControl"
                Command="{Binding Source={x:Reference this}, Path=Command}"
            />
        </StackLayout>
    </ContentView.Content>
</ContentView>

CustomControl.xaml.cs

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CustomControl : ContentView
{
    public CustomControl()
    {
        TextData = "";

        InitializeComponent();
    }

    public static readonly BindableProperty TextDataProperty = BindableProperty.Create(
                                               propertyName: "TextData",
                                               returnType: typeof(string),
                                               declaringType: typeof(CustomControl),
                                               defaultBindingMode: BindingMode.TwoWay,
                                               defaultValue: "");

    public string TextData
    {
        get { return base.GetValue(TextDataProperty).ToString(); }
        set { base.SetValue(TextDataProperty, value); }
    }

    public static readonly BindableProperty CommandProperty = BindableProperty.Create(
                                               propertyName: "Command",
                                               returnType: typeof(Command),
                                               declaringType: typeof(CustomControl),
                                               defaultBindingMode: BindingMode.OneWay);

    public Command Command { get; set; }
}
1

1 Answers

2
votes

The Command property of CustomControl should also be implemented with base.GetValue and base.SetValue, same way as TextData is.

public partial class CustomControl : ContentView
{
    ...

    public static readonly BindableProperty CommandProperty =   
        BindableProperty.Create(
            propertyName: nameof(Command),
            returnType: typeof(ICommand),
            declaringType: typeof(CustomControl),
            defaultBindingMode: BindingMode.OneWay,
            defaultValue: default(ICommand));

    public ICommand Command 
    {
        get => (ICommand) GetValue(CommandProperty);
        set => SetValue(CommandProperty, value);
    }
}