I have a model TrackModel, that models my music tracks (now it's just a class of a mock):
public partial class TrackModel : ObservableObject
{
/// <summary>
/// Name of the track that will be displayed on the track list
/// </summary>
[ObservableProperty]
private string title;
/// <summary>
/// The source file, represented as FileMediaSource
/// </summary>
[ObservableProperty]
private MediaSource source; //TODO: later rename this ti FileMediaResource
public TrackModel()
{
}
public TrackModel(string title, MediaSource source)
{
this.title = title;
this.source = source;
}
}
On the page my ViewModel is defined like this:
public partial class MusicPageViewModel : ObservableObject
{
[ObservableProperty]
private MediaSource source;
[ObservableProperty]
private ObservableCollection<TrackModel> tracks;
public int Index { get; private set; } = 0;
public MusicPageViewModel()
{
}
public void FetchTracks()
{
Tracks = new ObservableCollection<TrackModel>
{
new TrackModel { Title = "Mostantol", Source = MediaSource.FromResource("Mostantol.mp3") },
new TrackModel { Title = "In The Dark", Source = MediaSource.FromResource("In The Dark.mp3"), },
new TrackModel { Title = "Midnight Sky", Source = MediaSource.FromResource("Midnight Sky.mp3") },
new TrackModel { Title = "Fever", Source = MediaSource.FromResource("Fever.mp3") },
new TrackModel { Title = "Prisoner", Source = MediaSource.FromResource("Prisoner.mp3") },
new TrackModel { Title = "Taki Taki", Source = MediaSource.FromResource("Taki Taki.mp3") },
new TrackModel { Title = "Solo", Source = MediaSource.FromResource("Solo.mp3") },
new TrackModel { Title = "Dark Night Rider", Source = MediaSource.FromResource("Dark Night Rider.mp3") },
new TrackModel { Title = "Omen III", Source = MediaSource.FromResource("Omen III.mp3") },
new TrackModel { Title = "Get-A-Way", Source = MediaSource.FromResource("Get-A-Way.mp3") },
new TrackModel { Title = "3AM Eternal", Source = MediaSource.FromResource("3AM Eternal.mp3") },
new TrackModel { Title = "Baby Got Back", Source = MediaSource.FromResource("Baby Got Back.mp3") },
new TrackModel { Title = "Ice Ice Baby", Source = MediaSource.FromResource("Ice Ice Baby.mp3") },
new TrackModel { Title = "Holiday rap", Source = MediaSource.FromResource("Holiday rap.mp3") },
new TrackModel { Title = "Jo reggelt!", Source = MediaSource.FromResource("Jo reggelt!.mp3") },
new TrackModel { Title = "Aj lav ju", Source = MediaSource.FromResource("Aj lav ju.mp3") },
new TrackModel { Title = "Zug a Volga", Source = MediaSource.FromResource("Zug a Volga.mp3") },
new TrackModel { Title = "Mi kene, ha volna", Source = MediaSource.FromResource("Mi kene, ha volna.mp3") },
new TrackModel { Title = "Megamix (Slager FM Bonus Track)", Source = MediaSource.FromResource("Megamix (Slager FM Bonus Track).mp3") },
};
}
public MediaSource FirstTrack => Source = Tracks[Index].Source;
public MediaSource NextTrack()
{
Index++;
Index = Index > Tracks.Count - 1 ? 0 : Index;
return Tracks[Index].Source;
}
public MediaSource PreviousTrack()
{
Index--;
Index = Index < 0 ? Tracks.Count - 1 : Index;
return Tracks[Index].Source;
}
}
The page code behind:
public partial class MusicPage : ContentPage
{
private readonly ILogger logger;
public MusicPageViewModel ViewModel => (MusicPageViewModel)base.BindingContext;
public MusicPage(MusicPageViewModel viewModel, ILogger<MusicPage> logger)
{
BindingContext = viewModel;
this.logger = logger;
InitializeComponent();
}
protected override void OnAppearing()
{
base.OnAppearing();
ViewModel.FetchTracks();
audioPlayer.Source = ViewModel.FirstTrack;
}
public void OnMediaEnded(object sender, EventArgs e) => audioPlayer.Source = ViewModel.NextTrack();
public void OnNext(object sender, EventArgs e) => audioPlayer.Source = ViewModel.NextTrack();
public void OnPrevious(object sender, EventArgs e) => audioPlayer.Source = ViewModel.PreviousTrack();
}
XAML for the main page:
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Solution.MobileApp.Pages.Tabs"
xmlns:component="clr-namespace:Solution.MobileApp.Components"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:models="clr-namespace:Solution.MobileApp.Models"
xmlns:viewModel="clr-namespace:Solution.MobileApp.ViewModels"
x:DataType="viewModel:MusicPageViewModel"
x:Class="Solution.MobileApp.Pages.Tabs.MusicPage"
BackgroundColor="#333333"
Shell.NavBarIsVisible="False">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<CollectionView Grid.Row="0" x:Name="collectionView"
ItemsSource="{Binding Tracks}"
BackgroundColor="#333333"
Margin="0,5,0,0">
<CollectionView.ItemsLayout>
<LinearItemsLayout Orientation="Vertical" ItemSpacing="5"/>
</CollectionView.ItemsLayout>
<CollectionView.ItemTemplate>
<DataTemplate x:DataType="models:TrackModel">
<component:TrackListItemComponent Track="{Binding .}"/>
</DataTemplate>
</CollectionView.ItemTemplate>
</CollectionView>
<Frame Grid.Row="1">
<component:AudioPlayerComponent x:Name="audioPlayer"
MediaEnded="OnMediaEnded"
Next="OnNext"
Previous="OnPrevious"/>
</Frame>
</Grid>
</ContentPage>
Here I have definied a template for a collection view with a TrackListItemComponent. The TrackListItemComponent looks like this:
public partial class TrackListItemComponent : ContentView
{
public static readonly BindableProperty TrackProperty = BindableProperty.Create(nameof(Track), typeof(TrackModel), typeof(TrackListItemComponent), null);
public TrackModel Track
{
get => (TrackModel)GetValue(TrackProperty);
set => SetValue(TrackProperty, value);
}
public TrackListItemComponent()
{
BindingContext = this;
InitializeComponent();
}
private void OnDelete(object sender, EventArgs e)
{ }
private void OnTapp(object sender, EventArgs e)
{ }
}
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Solution.MobileApp.Components.TrackListItemComponent"
xmlns:vm="clr-namespace:Solution.MobileApp.Models"
x:DataType="vm:TrackModel">
<SwipeView x:Name="swipe">
<SwipeView.RightItems>
<SwipeItems>
<SwipeItem IconImageSource="delete.png"
BackgroundColor="Red"
Invoked="OnDelete" />
</SwipeItems>
</SwipeView.RightItems>
<Frame BindingContext="{x:Reference this}"
Margin="2"
BackgroundColor="#4D4D4D"
BorderColor="Black"
HasShadow="False">
<Frame.GestureRecognizers>
<TapGestureRecognizer Tapped="OnTapp"/>
</Frame.GestureRecognizers>
<StackLayout Orientation="Horizontal" HorizontalOptions="EndAndExpand" VerticalOptions="Center"
FlowDirection="LeftToRight">
<Label Text="{Binding Title}" TextColor="White" />
<Label Text="{Binding Source.Id}" TextColor="Wheat" />
</StackLayout>
</Frame>
</SwipeView>
</ContentView>
My problem is that the CollectionView's item of type TrackModel is not binding to the component's Track property (the get/set never hits). I can see the 19 element is the CollectionView, but all are empty.
I can't figure out what I did wrong.
It looks like you are not setting the BindingContext for the
TrackModelin the ContentView. You can either set the BindingContext in Xaml like below or in code-behind.