controlsmauibindablepropertycontentview

.NET Maui What is wrong with this custom control's bindable properties?


The next step in my exploration of .NET Maui is to create a simple custom control. So I created a custom control consisting of a button and a MediaElement, with two bindable properties, the text to display on the button and the media source for the MediaElement. As best I can tell, the values aren't getting passed in at all... the button has no text, and when I fire the MediaElement's play command, the Source is null.

So I create the xaml for the simple player control thus:

<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
             xmlns:customcontrols="clr-namespace:LifeTrax.Mobile.Custom_Controls"
             x:Class="LifeTrax.Mobile.Custom_Controls.SimplePlayerControl"
             x:Name="root">

    <Grid Grid.Row="0" Grid.Column="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
            <RowDefinition Height="10"></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"></ColumnDefinition>
        </Grid.ColumnDefinitions>

        <Button Grid.Row="1" Grid.Column="0"  IsEnabled="True" Clicked="Button_Clicked" Text="{Binding SPC_ButtonName}" x:Name="btnPlay"></Button>
        <toolkit:MediaElement Grid.Row="0" Grid.Column="0" ShouldShowPlaybackControls="False" ShouldAutoPlay="False" 
                              ShouldLoopPlayback="False" IsVisible="False" Volume="0.15" x:Name="plyr"
                              Source="{Binding AudioSource, Mode=OneWay}" ></toolkit:MediaElement>
    </Grid>
</ContentView>

I put the functionality in the code behind, including defining the two bindable properties.

public partial class SimplePlayerControl : ContentView
{
    public static readonly BindableProperty AudioSourceProperty = BindableProperty.Create(nameof(AudioSource),
                                                                                          typeof(MediaSource), typeof(SimplePlayerControl));
    public MediaSource AudioSource
    {
        get { return (MediaSource)GetValue(AudioSourceProperty); }
        set { SetValue(AudioSourceProperty, value); }
    }

    public static readonly BindableProperty SPC_ButtonNameProperty = BindableProperty.Create(nameof(SPC_ButtonName),
                                                                                             typeof(String), typeof(SimplePlayerControl));
    public String SPC_ButtonName
    {
        get { return (String)GetValue(SPC_ButtonNameProperty); }
        set { SetValue(SPC_ButtonNameProperty, value); }
    }
    #endregion

    public SimplePlayerControl()
    {
        InitializeComponent();
    }

    private void Button_Clicked(object sender, EventArgs e)
    {
        plyr.Play();
    }
}

Now, when I use it, the xaml to call it looks like this:

<customcontrols:SimplePlayerControl AudioSource="{Binding PlayerSource, Mode=OneWay}" SPC_ButtonName="Play the mp3"></customcontrols:SimplePlayerControl>

The label for the button doesn't display, and the audio doesn't play. Because the button text is hard-coded, it looks like I've somehow messed up the binding in the simpleplayer. But even as I post this, it STILL looks correct to me. Obviously I've gotten something wrong... so, help?

EDIT: To address the comment about not having a binding context set, my understanding is that custom controls do not need a binding context because all the code is in the code-behind. But I tried adding a Binding Context to the constructor, setting it to this, but no change. I know the code behind is bound to xaml, because if I hard-code values in the constructor, it works.

public SimplePlayerControl()
{
    InitializeComponent();
    btnPlay.Text = "Play the mp3";
    plyr.Source = MediaSource.FromResource("mysound.mp3");
}

Binding the code-behind to the custom control view isn't the problem. To illustrate the problem, I added two lines in the code-behind to grab the bindable properties for the custom control. When I set a breakpoint and look at them, they are null.

public SimplePlayerControl()
{
    InitializeComponent();
    String btnName = this.SPC_ButtonName;
    MediaSource media = this.AudioSource;
    btnPlay.Text = "Play the mp3";
    plyr.Source = MediaSource.FromResource("darktheme.mp3");
}

Since "this.SPC_ButtonName" and "this.AudioSource" are null in the constructor, that must mean something is wrong with my bindable properties.


Solution

  • For custom controls, we usually use Relative bindings to achieve this.

    Please refer to the following code:

    SimplePlayerControl.xaml

    <ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
                 x:Class="MauiApp2.Views.SimplePlayerControl"
                 x:Name="root"
                 >
        <Grid Grid.Row="0" Grid.Column="0">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="*"></RowDefinition>
                <RowDefinition Height="10"></RowDefinition>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"></ColumnDefinition>
            </Grid.ColumnDefinitions>
      
    
            <Button Grid.Row="1" Grid.Column="0"  IsEnabled="True"
                    Text="{Binding SPC_ButtonName, Source={x:Reference root}}"
                    Command="{Binding Source={x:Reference root}, Path= ChildCommand}"
                    x:Name="btnPlay">
               
            </Button>
           
            <toolkit:MediaElement Grid.Row="0" Grid.Column="0" ShouldShowPlaybackControls="True" ShouldAutoPlay="False"  HorizontalOptions="CenterAndExpand" HeightRequest="630" BackgroundColor="Pink"
                                  ShouldLoopPlayback="False" IsVisible="true" Volume="0.15" x:Name="plyr"
                                  Source="{Binding AudioSource, Mode=OneWay, Source= {x:Reference root}}" ></toolkit:MediaElement>
        </Grid>
    </ContentView>
    

    SimplePlayerControl.xaml.cs

    public partial class SimplePlayerControl : ContentView
    {
        public static readonly BindableProperty AudioSourceProperty = BindableProperty.Create(nameof(AudioSource),
                                                                                              typeof(MediaSource), typeof(SimplePlayerControl));
        public MediaSource AudioSource
        {
            get { return (MediaSource)GetValue(AudioSourceProperty); }
            set { SetValue(AudioSourceProperty, value); }
        }
    
        public static readonly BindableProperty SPC_ButtonNameProperty = BindableProperty.Create(nameof(SPC_ButtonName),
                                                                                                 typeof(String), typeof(SimplePlayerControl));
        public String SPC_ButtonName
        {
            get { return (String)GetValue(SPC_ButtonNameProperty); }
            set { SetValue(SPC_ButtonNameProperty, value); }
        }
    
        public SimplePlayerControl()
        {
            InitializeComponent();
        }
    
    
        public static readonly BindableProperty ChildCommandProperty =
                BindableProperty.Create(nameof(ChildCommand), typeof(ICommand), typeof(SimplePlayerControl));
    
        public ICommand ChildCommand
        {
            get => (ICommand)GetValue(ChildCommandProperty);
            set => SetValue(ChildCommandProperty, value);
        }
    
    
    /*    public static BindableProperty CurrentChildViewProperty =
        BindableProperty.Create(nameof(CurrentChildView), typeof(object), typeof(SimplePlayerControl));
    
        public object CurrentChildView
        {
            get => (object)GetValue(CurrentChildViewProperty);
            set => SetValue(CurrentChildViewProperty, value);
        }*/
    
    
    
    /*    private void Button_Clicked(object sender, EventArgs e)
        {
            plyr.Play();
        }*/
    }
    

    Usage example:

    <customcontrols:SimplePlayerControl  x:Name="myPlayer" AudioSource="embed://test.mp4"  SPC_ButtonName="Play the mp4" ></customcontrols:SimplePlayerControl>