我有一个返回模型数组的 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 的样板。
我缺少的部分是使用异步转换器作为图像数据上下文的绑定,然后将图像源绑定到
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 源设置为结果。