wpf - 如何从属性 setter 调用异步方法

标签 wpf binding async-await

这是我的问题:
我在属性过滤器上绑定(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/

相关文章:

javascript - 为什么 no-return-await 与 const x = wait ?

c# - 出错时重试异步文件上传

c# - 在 WPF\C# 中模拟按下按钮时的退格键

c# - 是否可以将 TextBlock 的文本绑定(bind)到应用程序 AssemblyVersion?

c# - 使用 WriteableBitmap.AddDirtyRect() 时内存泄漏的解决方法

java - JSON 模式和继承

c# - 将 Grid.RowDefinition 高度绑定(bind)到 Xamarin 中的网格宽度

wpf - 使用 WPF 在 F# 中调用窗口加载事件的方法

java - 使用绑定(bind)创建/改进伪终端

javascript - 为什么带有 async/await 的 Array.map() 返回奇怪的结果?