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

alen0pnh  于 2023-01-10  发布在  其他
关注(0)|答案(3)|浏览(314)

我的问题是:
我有一个WPF文本框绑定在属性Filter上。它作为一个过滤器工作:每次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}");
    }
}

由于不允许使用异步属性,如果绑定需要调用异步方法,我该怎么做才能使它工作呢?

ilmyapht

ilmyapht1#

我将假设DisplayValues方法实现正在更改绑定到UI的属性,并且为了进行演示,我将假设它是一个List<string>

private List<string> _values;

public List<string> Values
{
    get
    {  
        return _values;
    }
    private set 
    {
        _values = value;
        OnPropertyChange();
    }
}

它是绑定:

<ListBox ItemsSource="{Binding Values}"/>

现在,正如您所说的,不允许使属性setter异步,因此我们必须使其同步,我们可以做的是将Values属性更改为某种类型,该类型将隐藏其数据来自异步方法的事实,作为实现细节,并以同步方式构造此类型。
Stephen Cleary的Mvvm.Async库中的NotifyTask将帮助我们实现这一点,我们要做的是将Values属性更改为:

private NotifyTask<List<string>> _notifyValuesTask;

public NotifyTask<List<string>> NotifyValuesTask
{
    get
    {  
        return _notifyValuesTask;
    }
    private set 
    {
        _notifyValuesTask = value;
        OnPropertyChange();
    }
}

并更改其绑定:

<!-- 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相似类型,包括忙碌指示符和错误传播,以及有关this MSDN articaleNotifyTask用法的更多信息(注意,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冻结,您不应该在调用GetAsync后使用Result属性,而是使用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();
    }
}
dvtswwa3

dvtswwa32#

你可以这样做。注意在“async void”中你需要处理所有的异常。如果你不这样做,应用程序可能会崩溃。

public class MyClass: INotifyPropertyChanged
{
    private string _filter;
    public string Filter
    {
        get { return _filter; }
        set
        {
             RaisePropertyChanged("Filter");
            _filter = value;
        }
    }
    public MyClass()
    {
        this.PropertyChanged += MyClass_PropertyChanged;
    }

    private async void MyClass_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == nameof(Filter))
        {
            try
            {
                // call to an async WEB API to get values from the filter
                var values = await GetValuesFromWebApi(Filter);
                DisplayValues(values);
            }
            catch(Exception ex)
            {

            }
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
j91ykkif

j91ykkif3#

这在一个属性设置器中工作&我使用它:

set
{
                 
  System.Windows.Application.Current.Dispatcher.Invoke(async () =>{ await this.SomeMethodAsync(); });
}

为清楚起见,使用async方法:

Task SomeMethodAsync()
{
  //fill in whatever fits your scenario
   await Task.Run(()=> blah blah blah);
}

相关问题