WinUI3 异步绑定转换器

问题描述 投票:0回答:1

我有一个返回模型数组的 API,每个模型都有一个缩略图 ID,需要通过经过身份验证的 API 调用获取该缩略图 ID,并显示在网格视图中每个项目的数据模板中的图像元素中。

我在网上找到了许多使用 TaskCompletionNotifier 的建议,因此尝试使用此模式,但我在程序的输出中收到错误,表明 WinUI 绑定代码不会采用 TaskCompletionNotifier 并在准备好时使用该值,或者可能我只是用错了。

错误:转换器无法将“Converters.TaskCompletionNotifier`1[Microsoft.UI.Xaml.Media.Imaging.BitmapImage]”类型的值转换为“ImageSource”类型; BindingExpression: Path='ThumbnailId' DataItem='Models.CallRecording';目标元素是“Microsoft.UI.Xaml.Controls.Image”(名称=“null”);目标属性是“Source”(类型“ImageSource”)。

我使用的代码是

转换器:


using Interfaces;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Media.Imaging;

namespace Converters;

public class StorageIdToImageSourceConverter : IValueConverter
{
    private readonly IImageService imageService;

    public StorageIdToImageSourceConverter()
    {
       imageService = Ioc.Default.GetRequiredService<IImageService>();
    }

    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if (value is string storageId)
        {
            if (string.IsNullOrWhiteSpace(storageId))
            {
                return null;
            }

            var task = Task.Run(async () => {
                var getBlobAsBitmapImageResult = await imageService.GetBlobAsBitmapImageAsync(storageId);
                if (getBlobAsBitmapImageResult.IsFailed)
                {
                    return null;
                }

                return getBlobAsBitmapImageResult.Value;
            });

            return new TaskCompletionNotifier<BitmapImage?>(task);
        }

        return null;
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

TaskCompletionNotifier:(通过 github 搜索找到https://github.com/Tlaster/Cosimg/blob/679d23010bbb9b839e840b2f07e68621999f742b/TBase/TaskCompletionNotifier.cs#L11

using System.ComponentModel;

namespace Augment.Converters;

public sealed class TaskCompletionNotifier<T> : INotifyPropertyChanged
{
    public TaskCompletionNotifier(Task<T> task)
    {
        Task = task;
        if (!task.IsCompleted)
        {
            var scheduler = (SynchronizationContext.Current == null) ? TaskScheduler.Current : TaskScheduler.FromCurrentSynchronizationContext();
            task.ContinueWith(t =>
            {
                var propertyChanged = PropertyChanged;
                if (propertyChanged != null)
                {
                    propertyChanged(this, new PropertyChangedEventArgs("IsCompleted"));
                    if (t.IsCanceled)
                    {
                        propertyChanged(this, new PropertyChangedEventArgs("IsCanceled"));
                    }
                    else if (t.IsFaulted)
                    {
                        propertyChanged(this, new PropertyChangedEventArgs("IsFaulted"));
                        propertyChanged(this, new PropertyChangedEventArgs("ErrorMessage"));
                    }
                    else
                    {
                        propertyChanged(this, new PropertyChangedEventArgs("IsSuccessfullyCompleted"));
                        propertyChanged(this, new PropertyChangedEventArgs("Result"));
                    }
                }
            },
            CancellationToken.None,
            TaskContinuationOptions.ExecuteSynchronously,
            scheduler);
        }
    }

    // Gets the task being watched. This property never changes and is never <c>null</c>.
    public Task<T> Task { get; private set; }


    // Gets the result of the task. Returns the default value of TResult if the task has not completed successfully.
    public T Result { get { return (Task.Status == TaskStatus.RanToCompletion) ? Task.Result : default(T); } }

    // Gets whether the task has completed.
    public bool IsCompleted { get { return Task.IsCompleted; } }

    // Gets whether the task has completed successfully.
    public bool IsSuccessfullyCompleted { get { return Task.Status == TaskStatus.RanToCompletion; } }

    // Gets whether the task has been canceled.
    public bool IsCanceled { get { return Task.IsCanceled; } }

    // Gets whether the task has faulted.
    public bool IsFaulted { get { return Task.IsFaulted; } }

    // Gets the error message for the original faulting exception for the task. Returns <c>null</c> if the task is not faulted.
    //public string ErrorMessage { get { return (InnerException == null) ? null : InnerException.Message; } }

    public event PropertyChangedEventHandler PropertyChanged;
}

页面.xaml

            <GridView ItemsSource="{x:Bind ViewModel.CallRecordings, Mode=OneWay}">
                <GridView.ItemTemplate>
                    <DataTemplate x:DataType="models:CallRecording">
...
                                <Image
                                    Grid.Row="0" Grid.RowSpan="2"
                                    Grid.Column="0" Grid.ColumnSpan="2" 
                                    Source="{Binding ThumbnailId, Mode=OneWay, Converter={StaticResource StorageIdToImageSourceConverter}, FallbackValue='ms-appx:///Assets/NoImageSquare100x100.jpg', TargetNullValue='ms-appx:///Assets/NoImageSquare100x100.jpg'}"/>
...

在 WinUI3 项目中执行此操作的最佳方法是什么?

如果可能的话,我想避免必须为列表视图中的每个项目创建 ViewModel 的样板。

windows xaml data-binding winui-3 winui
1个回答
0
投票

我缺少的部分是使用异步转换器作为图像数据上下文的绑定,然后将图像源绑定到

Result

将 Page.xaml 更新为如下所示

<Image
  Grid.Row="0" Grid.RowSpan="2"
  Grid.Column="0" Grid.ColumnSpan="2"
  DataContext="{Binding ThumbnailId, Mode=OneWay, Converter={StaticResource StorageIdToImageSourceConverter}}"
  Source="{Binding Path=Result, Mode=OneWay}"/>

Image 元素的

DataContext
随后会变成转换后的
TaskCompletionNotifier
,然后可以绑定其 Result 属性,在准备好时将 Image 源设置为结果。

© www.soinside.com 2019 - 2024. All rights reserved.