这是我的问题:
我在属性过滤器上绑定(bind)了一个 WPF 文本框。它用作过滤器:每次 TextBox.Text 更改时,都会设置 Filter 属性。
<TextBox Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged, Mode=OneWayToSource}" />
现在在 ViewModel 上有我的 Filter 属性:每次过滤器更改时,我都会更新我的值。
private string _filter;
public string Filter
{
get { return _filter; }
set
{
_filter = value;
// call to an async WEB API to get values from the filter
var values = await GetValuesFromWebApi(_filter);
DisplayValues(values);
}
}
public async Task<string> GetValuesFromWebApi(string query)
{
var url = $"http://localhost:57157/api/v1/test/result/{query}";
// this code doesn't work because it is not async
// return await _httpClient.GetAsync(url).Result.Content.ReadAsStringAsync();
// better use it this way
var responseMessage = await _httpClient.GetAsync(url);
if (responseMessage.IsSuccessStatusCode)
{
return await responseMessage.Content.ReadAsStringAsync();
}
else
{
return await Task.FromResult($"{responseMessage.StatusCode}: {responseMessage.ReasonPhrase}");
}
}
由于不允许使用异步属性,如果绑定(bind)需要调用异步方法,我该怎么做才能使绑定(bind)正常工作?
最佳答案
我将假设 DisplayValues 方法实现正在更改绑定(bind)到 UI 的属性,为了演示,我将假设它是 List<string>
:
private List<string> _values;
public List<string> Values
{
get
{
return _values;
}
private set
{
_values = value;
OnPropertyChange();
}
}
它的绑定(bind):
<ListBox ItemsSource="{Binding Values}"/>
现在正如您所说,不允许使属性 setter 异步,因此我们必须使其同步,我们可以做的是将 Values 属性更改为某种类型,该类型将隐藏它的数据来自异步方法的事实作为实现细节并以同步方式构造此类型。
NotifyTask
来自 Stephen Cleary 的 Mvvm.Async库将帮助我们解决这个问题,我们要做的就是将 Values 属性更改为:private NotifyTask<List<string>> _notifyValuesTask;
public NotifyTask<List<string>> NotifyValuesTask
{
get
{
return _notifyValuesTask;
}
private set
{
_notifyValuesTask = value;
OnPropertyChange();
}
}
并更改它的绑定(bind):
<!-- Busy indicator -->
<Label Content="Loading values" Visibility="{Binding notifyValuesTask.IsNotCompleted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Values -->
<ListBox ItemsSource="{Binding NotifyValuesTask.Result}" Visibility="{Binding
NotifyValuesTask.IsSuccessfullyCompleted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
<!-- Exception details -->
<Label Content="{Binding NotifyValuesTask.ErrorMessage}"
Visibility="{Binding NotifyValuesTask.IsFaulted,
Converter={StaticResource BooleanToVisibilityConverter}}"/>
这样,我们创建了一个表示
Task
的属性。为数据绑定(bind)定制的类似类型,包括繁忙指示器和错误传播,有关 NotifyTask
的更多信息this MSDN articale 中的用法(注意 NotifyTask
在那里被视为 NotifyTaskCompletion
)。现在最后一部分是更改 Filter 属性 setter 以将 notifyValuesTask 设置为新的
NotifyTask
每次更改过滤器时,使用相关的异步操作(无需 await
任何东西,所有监控都已嵌入 NotifyTask
):private string _filter;
public string Filter
{
get
{
return _filter;
}
set
{
_filter = value;
// Construct new NotifyTask object that will monitor the async task completion
NotifyValuesTask = NotifyTask.Create(GetValuesFromWebApi(_filter));
OnPropertyChange();
}
}
您还应该注意到 GetValuesFromWebApi 方法会阻塞,它会使您的 UI 卡住,您不应该使用
Result
调用后的属性GetAsync
使用 await
两次:public async Task<string> GetValuesFromWebApi(string query)
{
var url = $"http://localhost:57157/api/v1/test/result/{query}";
using(var response = await _httpClient.GetAsync(url))
{
return await response.Content.ReadAsStringAsync();
}
}
关于wpf - 如何从属性 setter 调用异步方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48212998/