如何在双向绑定的WPF Combobox上调用异步操作?

5

当从双向绑定的控件(例如 wpf 数据绑定中的 ComboBox)选择项目时,处理异步操作的适当方式是什么?

当我有一个双向绑定属性(例如 ComboBox 上的 SelectedValue)时,我认为我不能使用 Stephen Cleary's NotifyTaskCompletion ,因为当用户从下拉列表中选择一个值时,ComboBox 本身需要修改绑定的 Result 属性,这是 Task 的结果。

我想到的唯一可行的解决方案是,在数据绑定的 setter 中调用一个 async Task 方法,而不等待结果。只要 async 方法触发与正在进行的任何 UI 相关的属性更改事件,并且任何异常都被正确地捕获和传播到 UI,那么这应该没问题,对吗?

我假设这在异步 WPF 应用程序中是一个常见情况。你们如何处理这个问题?

到目前为止,我的解决方案:

<ComboBox 
        ItemsSource="{Binding PossibleItems}"
        DisplayMemberPath="Name"
        SelectedValue="{Binding SelectedItem}"/>

...

public Item SelectedItem
{
    get { return m_selectedItem; }
    set
    {
        m_selectedItem = value;
        OnPropertyChanged();

        InitializeAsyncAndFirePropertyChanged();   // async Task method not awaited - gives compiler warning CS4014
    }
}

public async Task InitializeAsyncAndFirePropertyChanged(ObservableCollection<RFEnvironment> possibleRfEnvironments)
{
    //should check this method for exceptions and propagate them to the UI via databinding
    OtherDataBoundProperty = await GetSomeStringFromWebAsync(); 
}

public string OtherDataBoundProperty
{
    get { return m_otherDataBoundProperty; }
    set
    {
        m_otherDataBoundProperty = value;
        OnPropertyChanged();
    }
}

注意:我发现有类似的问题,但没有一个解决像ComboBox这样的控件的双向绑定。


我遇到了类似的问题,但是仅仅运行异步而不等待是不够好的。那么异常呢?但是我还没有找到更好的解决方案。 - Karel Kral
2个回答

0
如果您正在使用函数响应式MVVM框架,例如ReactiveUI,则只需观察SelectedItem属性,并在设置该属性时启动任何所需的操作。例如:
this.WhenAnyValue(x => x.SelectedItem)
    .Subscribe(async _ => await InitializeAsyncAndFirePropertyChanged());

一个属性本身不应该启动后台操作,但当属性被设置时,视图模型可以这样做。
请参阅文档以获取更多信息:https://reactiveui.net/docs/concepts/

1
那看起来很有趣,我可能会在某个时候研究一下reactiveUI,但现在我正在寻找一种用纯MVVM和WPF解决这个问题的方法。 - TenaciousG
解决什么问题?属性不能是异步的。 - mm8
1
如何在用户从下拉框中选择一个值时最好地启动异步操作。 我越看它,我觉得没有什么神奇美丽的解决方案。我只需要确保setter立即返回并以某种方式触发异步方法。关键是确保异步方法在完成时调用适当的propertyChange-calls,并通过自身触发绑定到UI错误指示器的错误属性的propertyChange-calls来处理可能发生的任何错误。 - TenaciousG
我已经回答过了;研究一下函数式编程,并将其设置在视图模型中而不是属性本身。另一个选项是在属性设置器中简单地触发异步操作,然后要么忘记结果,要么连接到某个事件,该事件在异步方法完成后被触发。你说得对,这种方式并不美观 :) ReactiveUI和类似的库存在的原因就是为了解决这个问题。 - mm8

0

在使用WCF数据绑定时,我在属性设置器中使用async调用遇到了类似的问题。我的解决方案略微更好,因为在您的情况下,当InitializeAsyncAndFirePropertyChanged中发生异常时,没有抛出或捕获异常。修改后的代码如下所示。它使用任务继续来抛出异常并引发OnPropertyChangedOnPropertyChanged调用可以保留在原始位置,这取决于您的需求。

public class MyViewModel: INotifyPropertyChanged
{

    private readonly TaskScheduler _uiScheduler;

    public MyViewModel()
    {
        _uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();
    }

    public Item SelectedItem
    {
        get { return m_selectedItem; }
        set
        {
            m_selectedItem = value;
            InitializeAsyncAndFirePropertyChanged()
                .ContinueWith(t =>
                {
                    if (t.Exception != null)
                    {
                        throw t.Exception;
                    }

                    OnPropertyChanged();
                }, _uiScheduler);
        }
    }

    public async Task InitializeAsyncAndFirePropertyChanged(ObservableCollection<RFEnvironment> possibleRfEnvironments)
    {
        //should check this method for exceptions and propagate them to the UI via databinding
        OtherDataBoundProperty = await GetSomeStringFromWebAsync();
    }

    public string OtherDataBoundProperty
    {
        get { return m_otherDataBoundProperty; }
        set
        {
            m_otherDataBoundProperty = value;
            OnPropertyChanged();
        }
    }

    .... other code to support INotifyPropertyChanged
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接