I have a UWP project want to reads StorageFolder VideosLibrary and show a list of mp4 files at Views with thumbnail.
With MVVM ligth toolkit I have setup this 4 flies with xaml. The Xaml is using UWP community toolkit wrap panel.
1)ViewModelLocator.cs
namespace UWP.ViewModels
{
/// <summary>
/// This class contains static reference to all the view models in the
/// application and provides an entry point for the bindings.
/// </summary>
class ViewModelLocator
{
/// <summary>
/// Initializes a new instance of the ViewModelLocator class.
/// </summary>
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
// Create design time view services and models
}
else
{
// Create run Time view services and models
}
//Register services used here
SimpleIoc.Default.Register<VideoListModel>();
}
public VideoListModel VideoListModel
{
get { return ServiceLocator.Current.GetInstance<VideoListModel>();
}
}
}
2) VideoListItem.cs
namespace UWP.Models
{
class VideoListItem : ViewModelBase
{
public string VideoName { get; set; }
public string Author { get; set; }
public Uri Vid_url { get; set; }
public BitmapImage Image { get; set; }
public VideoListItem(string videoname,string author,Uri url, BitmapImage img)
{
this.VideoName = videoname;
this.Author = author;
this.Vid_url = url;
this.Image = img;
}
}
}
3) VideoListModel.cs
namespace UWP.ViewModels
{
class VideoListModel : ViewModelBase
{
public ObservableCollection<VideoListItem> VideoItems { get; set; }
private VideoListItem videoItems;
public VideoListModel()
{
}
public async static Task<List<VideoListItem>> GetVideoItem()
{
List<VideoListItem> videoItems = new List<VideoListItem>();
StorageFolder videos_folder = await KnownFolders.VideosLibrary.CreateFolderAsync("Videos");
var queryOptions = new QueryOptions(CommonFileQuery.DefaultQuery, new[] { ".mp4" });
var videos = await videos_folder.CreateFileQueryWithOptions(queryOptions).GetFilesAsync();
foreach (var video in videos)
{
//Debug.WriteLine(video.Name);
//videoItems.Add(new VideoListItem());
var bitmap = new BitmapImage();
var thumbnail = await video.GetThumbnailAsync(ThumbnailMode.SingleItem);
await bitmap.SetSourceAsync(thumbnail);
videoItems.Add(new VideoListItem(video.DisplayName, "", new Uri(video.Path),bitmap));
}
//foreach(var video in videoItems)
//{
// Debug.WriteLine("Name:{0} , Author:{1}, Uri:{2}, Bitmap:{3}", video.VideoName, video.Author, video.Vid_url, video.Image.UriSource);
//}
return videoItems;
}
}
}
4) Video.xaml
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:UWP.Views"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:Controls="using:Microsoft.Toolkit.Uwp.UI.Controls"
x:Class="UWP.Views.Video"
mc:Ignorable="d"
NavigationCacheMode="Enabled"
DataContext="{Binding Source={StaticResource ViewModelLocator},Path=VideoListModel}">
<!--NavigationCacheMode Enable for the page state save-->
<Page.Resources>
<DataTemplate x:Key="VideoTemplate">
<Grid Width="{Binding Width}"
Height="{Binding Height}"
Margin="2">
<Image HorizontalAlignment="Center"
Stretch="UniformToFill"
Source="{Binding Image}" />
<TextBlock Text="{Binding VideoName}"/>
<StackPanel Orientation="Horizontal">
<TextBlock Text="Author" />
<TextBlock Text="{Binding Author}" />
</StackPanel>
</Grid>
</DataTemplate>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<ListView Name="VideosListWrapPanal"
ItemTemplate="{StaticResource VideoTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Controls:WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ListView>
</Grid>
</Page>
public async MainViewModel()
{
VideoItems = new ObservableCollection<MainMenuItem>(await GetVideoItem());
}
how can I accomplish this initialization in an asynchronous way? To get the thumbnail I have created the method of GetVideoItem(), But I can't find a way to call the GetVideoItem asynchronously in a constructor. Does anyone know how to solve this task?
The short answer is: you cant make a constructor async
.
But there are options to solve this. Here are two proposals:
A lot of MVVM Frameworks use lifecylce methods to solve this problem. You could add an ActivateAsync
method, which is called by your framework after instantiating the ViewModel
.
In your example this could be done in your ViewModelLocator.
interface IActivate
{
Task ActivateAsync();
}
// Call it like this:
(model as IActivate)?.ActivateAsync(); // this will work even if the model does not implement IActivate
Another option is to use a factory method
for creating the ViewModel
. The factory method
could fetch all async
data and create the object after all data was aggregated.
public static async Task<CustomViewModel> Create()
{
var data = await FetchAsyncData();
return new CustomViewModel(data);
}
Here a short snippet on how you could use the activate
pattern.
public class ViewModelLocator
{
// existing implementation goes here
public async Task<TViewModel> Create<TViewodel>
{
var model = ServiceLocator.Current.GetInstance<TViewodel>();
var activate = model as IActivate;
if(activate != null)
await activate.ActivateAsync();
return model;
}
}
Now the factory method returns only a fully activated model. This pattern has the advantage, that the creator does not need to know the model it is creating. It checks if the model needs activation and calls it. All activation logic can then be placed in the ViewModel
.
I recommend using an asynchronous task notifier, as described in my article on async MVVM data binding.
E.g., using NotifyTask
from this helper library:
public NotifyTask<List<VideoListItem>> VideoItems { get; }
public VideoListModel(IKnownFolderReader knownFolder)
{
_knownFolder = knownFolder;
VideoItems = NotifyTask.Create(() => _knownFolder.GetData());
}
Your data binding would then change from ItemsSource="{Binding VideoItems}"
to ItemsSource="{Binding VideoItems.Result}"
. In addition, VideoItems
has several other properties such as IsNotCompleted
and IsFaulted
so that your data binding can show/hide elements based on the state of the task.
This approach avoids the subtle problems with Result
and problems with ContinueWith
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With