【问题标题】:How to make my constructor async in UWP MVVM model? (MVVM Lighttoolkit)如何使我的构造函数在 UWP MVVM 模型中异步? (MVVM Lighttoolkit)
【发布时间】:2017-09-12 05:40:10
【问题描述】:

我有一个 UWP 项目想要读取 StorageFolder VideosLibrary 并在带有缩略图的视图中显示 mp4 文件列表。

使用 MVVM ligth 工具包,我已经用 xaml 设置了这 4 只苍蝇。 Xaml 正在使用 UWP 社区工具包包装面板。

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) 视频.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>

我想在我的 VideoListModel 中为构造函数做类似下面的事情。

public async MainViewModel()
{
   VideoItems = new ObservableCollection<MainMenuItem>(await GetVideoItem());

}

如何以异步方式完成此初始化? 为了获得缩略图,我创建了 GetVideoItem() 方法, 但是我找不到在构造函数中异步调用 GetVideoItem 的方法。 有谁知道如何解决这个任务?

【问题讨论】:

  • 这是您需要阅读的文章:msdn.microsoft.com/en-gb/magazine/…
  • 你为什么不使用委托代替。不建议在构造函数中使用异步方法或函数。
  • 抱歉我对委托缺乏了解,我使用了异步方法,因为我的GetVideoItem 方法中有CreateFolderAsync 方法和GetFilesAsync 方法。你能给我一些建议,我该如何修改我的代码?
  • 如何使用“加载窗口”中的命令并使命令异步。所以它会异步加载数据
  • 构造函数不能是异步的。例如,您可以使用 Activated 事件。但是考虑一下后果,用户会在一段时间内看一个没有内容的窗口。而且你必须确保他不能做任何需要 VideoItems 成员有效的事情。您真的想在窗口变得可见之前 执行此操作。考虑一个异步工厂方法。

标签: c# asynchronous uwp mvvm-light windows-community-toolkit


【解决方案1】:

简短的回答是:你不能创建一个构造函数async

但是有一些选项可以解决这个问题。这里有两个建议:

解决方案 1:ViewModel 生命周期

很多 MVVM 框架使用生命周期方法来解决这个问题。您可以添加一个ActivateAsync 方法,该方法由您的框架在实例化ViewModel 后调用。

在您的示例中,这可以在您的 ViewModelLocator 中完成。

interface IActivate
{
    Task ActivateAsync();
}

// Call it like this:
(model as IActivate)?.ActivateAsync(); // this will work even if the model does not implement IActivate

解决方案 2:使用工厂

另一种选择是使用factory method 来创建ViewModelfactory method 可以获取所有async 数据并在所有数据聚合后创建对象。

public static async Task<CustomViewModel> Create()
{
    var data = await FetchAsyncData();
    return new CustomViewModel(data);
}

示例:

这里是关于如何使用 activate 模式的简短 sn-p。

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;
    }
}

现在工厂方法只返回一个完全激活的模型。这种模式的优点是创建者不需要知道它正在创建的模型。它检查模型是否需要激活并调用它。然后可以将所有激活逻辑放在ViewModel中。

【讨论】:

  • 我已将代码重构为public VideoListModel(List&lt;VideoListItem&gt; data) { VideoItems = new ObservableCollection&lt;VideoListItem&gt;(data); } public async static Task&lt;VideoListModel&gt; Create() { var data = await GetVideoItem(); return new VideoListModel(data); } 在这种情况下,我应该如何在 ViewModelLocator 中解析我的 ViewModel?
  • 我从未使用过ServiceLocator,但应该有一种方法可以将构造函数参数传递给它。在任何情况下,您都不能使用属性,因为 getter 始终是同步的。我将更新我的答案,并以Activate 模式为例,因为我认为这在你的情况下更简单。
  • 感谢您的回复。我仍然没有解决我的问题。我试过public async Task&lt;VideoListModel&gt; Create() { var model = ServiceLocator.Current.GetInstance&lt;VideoListModel&gt;(); var activate = model as IActivate; if(activate != null) { await activate.ActivateAsync(); } return model; }
  • 在我的模型中.... public async static Task&lt;List&lt;VideoListItem&gt;&gt; Create() { var data = await GetVideoItem(); return data; } public VideoListModel() { var task =Task.Run(Create); task.Wait(); task.Wait(); VideoItems = new ObservableCollection&lt;VideoListItem&gt;(task.Result); SelectedVideoItem = VideoItems.FirstOrDefault(); } GetVideoItem() 和上面一样但是还是不行
  • 或者我的界面不正确? public interface IActivate { Task ActivateAsync(); }
【解决方案2】:

我建议使用异步任务通知器,如我在 async MVVM data binding 上的文章中所述。

例如,使用来自this helper libraryNotifyTask

public NotifyTask<List<VideoListItem>> VideoItems { get; }

public VideoListModel(IKnownFolderReader knownFolder)
{
  _knownFolder = knownFolder;
  VideoItems = NotifyTask.Create(() => _knownFolder.GetData());
}

然后,您的数据绑定将从 ItemsSource="{Binding VideoItems}" 更改为 ItemsSource="{Binding VideoItems.Result}"。此外,VideoItems 还具有其他几个属性,例如 IsNotCompletedIsFaulted,因此您的数据绑定可以根据任务的状态显示/隐藏元素。

这种方法避免了微妙的problems with Resultproblems with ContinueWith

【讨论】:

  • 感谢您的回复!我已经使用 Noti.Mvvm.Async 包尝试了您的代码!它就像我之前发送的代码一样工作得很好,让我的代码更干净!谢谢!我了解到数据绑定并不是真的必须是 ObservableCollection 类!非常感谢! !感谢您的帮助!
  • 嗨@Stephen Cleary!虽然我可以使用 Noti.Mvvm.Async ,但是在我的 xaml 页面中,它显示了这个错误Severity Code Description Project File Line Suppression State Error Could not load file or assembly 'Nito.Mvvm.Async, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' or one of its dependencies. The system cannot find the file specified. 你对此有什么想法吗?
  • @Leow:这听起来像是 Visual Studio XAML 设计器中的一个错误。我会尝试在 MSDN 论坛上发帖。 VS 团队最近做了很多 XAML 工作,所以这可能是暂时的回归。
【解决方案3】:

我终于找到了显示视频列表的方法!

我没有设置ListView ItemSource! 虽然它仍然有Type not found in cache:UWP.Services.IKnownFolderReader的错误,但我认为通过加载文件启动应用程序时它会消失。

这是我的最终代码。

1) 页面.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}"
  Loaded="Page_Loaded">
<!--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"
                   Height="200"
                   Width="200"
                   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"
              ItemsSource="{Binding VideoItems}"
              ItemTemplate="{StaticResource VideoTemplate}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Controls:WrapPanel Background="LightBlue"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ListView>

    <!--<StackPanel Orientation="Vertical"
                VerticalAlignment="Center"
                HorizontalAlignment="Center">
        <Viewbox MaxHeight="100"
                 MaxWidth="100">
            <SymbolIcon Symbol="Video" />
        </Viewbox>
        <TextBlock TextAlignment="Center"
                   Text="Home"
                   Margin="0,15,0,0" />

    </StackPanel>-->

</Grid>
</Page>

2) 型号

  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) 视图模型

class VideoListModel : ViewModelBase
{
    private IKnownFolderReader _knownFolder;

    private ObservableCollection<VideoListItem> _videoItems;
    public ObservableCollection<VideoListItem> VideoItems
    {
        get { return _videoItems; }
        set { Set(ref _videoItems, value); RaisePropertyChanged(); }
    }



    private VideoListItem _selectedVideoItem;

    public VideoListItem SelectedVideoItem
    {
        get { return _selectedVideoItem; }
        set { Set(ref _selectedVideoItem, value);}
    }


    public VideoListModel(IKnownFolderReader knownFolder)
    {
        _knownFolder = knownFolder;
        var task = _knownFolder.GetData();
        task.ConfigureAwait(true).GetAwaiter().OnCompleted(() => {
            List<VideoListItem> items = task.Result;
            VideoItems = new ObservableCollection<VideoListItem>(items);
        });
    }



}

4) IKnownFolderReader.cs

public interface IKnownFolderReader
{
    Task<List<VideoListItem>> GetData();
}

5) VideoFilesReader.cs

public class VideoFilesReader : IKnownFolderReader
{
    private VideoListItem videoItems;
    public async Task<List<VideoListItem>> GetData()
    {
        List<VideoListItem> videoItems = new List<VideoListItem>();
        StorageFolder videos_folder = await KnownFolders.VideosLibrary.GetFolderAsync("Videos");
        var queryOptions = new QueryOptions(CommonFileQuery.DefaultQuery, new[] { ".mp4" });
        var videos = await videos_folder.CreateFileQueryWithOptions(queryOptions).GetFilesAsync();


        foreach (var video in videos)
        {
            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));

        }

        return videoItems;
    }
}

小心查看部分!

为了从文件夹中进行异步读取,我了解到应该将服务与 interface 分开

感谢大家的帮助!我应该更多地研究MVVM模式,MVVM轻工具包和UWP开发,以防止再次出现此类错误!

【讨论】:

    【解决方案4】:

    我遇到了类似的问题,我做了这样的事情:

    public MainViewModel()
    {
    GetVideoItem().ContinueWith(result=>{VideoItems = new ObservableCollection<MainMenuItem>(result)});
    
    
    }
    

    您也可以为此放置一个 loadingData 变量,让用户知道数据正在加载。

        public IsLoading{get;set}=true;
        public MainViewModel()
        {
        GetVideoItem().ContinueWith(result=>{VideoItems = new ObservableCollection<MainMenuItem>(result);
                                    IsLoading=false;});
        }
    

    【讨论】:

    • 我已经用 public VideoListModel() { GetVideoItem().ContinueWith(result=>{VideoItems = new ObservableCollection(result.Result)}) 尝试了你的方法;但是,它只是在异步加载完成之前通过 VideoListModel() 构造函数。
    • 您不能使构造函数异步,这将通过,当任务完成后它将更新您的 VideoItems 集合
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-26
    • 2015-06-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多