如何从属性设置器调用异步方法

时间:2018-01-11 17:42:50

标签: wpf binding async-await

这是我的问题:
我在属性Filter上绑定了一个WPF TextBox。它用作过滤器:每次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}");
    }
}

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

2 个答案:

答案 0 :(得分:2)

我将假设DisplayValues方法实现正在更改绑定到UI的属性,对于演示,我将假设它是import shutil import datetime import fnmatch import pandas as pd DF = pd.DataFrame(columns=['Text']) DF.at[1,'Text'] = "##################" DF.at[2,'Text'] = "#" DF.at[3,'Text'] = "##################" DF.at[4,'Text'] = " " DF.at[5,'Text'] = "Headers = Y" DF.at[6,'Text'] = "Compress = Zip" DF.at[7,'Text'] = "Column_Delimter =," DF.at[8,'Text'] = "Progress = 10000" DF.at[9,'Text'] = "Filename = test.txt" DF.at[10,'Text'] = " " DF.at[11,'Text'] = "SQL: MultiStatement" DF.at[12,'Text'] = " " DF.at[13,'Text'] = "SQL QUERY GOES HERE" print(DF.at[7,'Text']) DF.to_csv('VCI.txt', index = False, header=False)

List<string>

它的绑定:

private List<string> _values;

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

现在正如你所说,不允许将属性设置器设置为异步,因此我们必须使其同步,而我们可以做的是将Values属性更改为某种类型,这将隐藏它的数据传递的事实从异步方法作为实现细节,并以同步方式构造此类型。

来自Stephen Cleary的NotifyTask库的

Mvvm.Async将帮助我们,我们将做的是将Values属性更改为:

<ListBox ItemsSource="{Binding 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}}"/> 类似,包括忙碌指标和错误传播,this MSDN articale中有关Task用法的更多信息(通知NotifyTask将其视为NotifyTask)。

现在最后一部分是更改Filter属性setter,以便每次更改过滤器时将notifyValuesTask设置为新的NotifyTaskCompletion,并使用相关的异步操作(不需要NotifyTask任何内容,所有监视已嵌入await):

NotifyTask

您还应该注意到GetValuesFromWebApi方法会阻止它,并且会冻结您的用户界面,在调用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(); } } 两次使用Result后,您不应该使用GetAsync属性:

await

答案 1 :(得分:0)

你可以这样做。请注意,在“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));
    }