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.
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>