如何从属性设置器调用异步函数? [英] How to call async function from property setter?

查看:80
本文介绍了如何从属性设置器调用异步函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个wpf应用程序,其中的 TextBox 绑定到了VM中的 ActualPageNumber 属性.我还有绑定到显示给定页面的 ObservableCollection DataGrid .数据存储在DB中.当我更改 ActualPageNumber 时,设置器访问数据库的速度可能会很慢.这就是为什么我想要一个异步setter来保持gui响应的原因.

我了解没有异步设置器: https://blog.stephencleary.com/2013/01/async-oop-3-properties.html

我还发现了诸如 https://stackoverflow.com/a/9343733/5852947 https://stackoverflow.com/a/13735418/5852947 https://nmilcoff.com/2017/07/10/stop-toggling-isbusy-with-notifytask/

我仍然为如何处理此案而苦苦挣扎.AsyncEx库可以是解决方案,一个例子就很好.

我只想通知用户该页面实际上正在加载.如果可以从设置器中调用异步,则可以,但是我仍然不能在设置器中使用 await ,因为它不是 async .

解决方案

1)对于 DataGrid 的响应能力,此绑定属性可能会有所帮助: IsAsync= True

 < DataGrid ItemsSource =" {Binding MyCollection,IsAsync = True} 

还要查看以下DataGrid属性:

  VirtualizingPanel.IsVirtualizingVirtualizingPanel.VirtualizationMode(您可能需要回收)VirtualizingPanel.IsVirtualizingWhenGroupingEnableRowVirtualizationEnableColumnVirtualization 

但是要小心,虚拟化会给您带来麻烦.例如,我有一个RowHeader(带有行号),并且启用虚拟化后,这些值变得混乱了.

2)关于用于数据绑定的异步设置器:我正在使用 IAsyncCommand 的自定义版本(请参阅控件的显示+隐藏中-在我的情况下,是带有微调框的透明盖+请稍候...",以防止其他用户动作(在执行任务时可见屏幕").精简样本:

  ....公共MainWindowViewModel(){UpdateCommand = AsyncCommand.Create(Update);//我们自己的AsyncCommand自定义实现}....公共AsyncCommand UpdateCommand {get;}内部异步任务更新(对象arg){等待SafeWrapWithWaitingScreenAsync(async()=>{var value =(int)arg;//或ActualPageNumber(如果从a1中使用)var data = await GetDataFromDb(value).ConfigureAwait(false);...//使用数据填写MyCollection(这是DataGrid的ItemsSource)OnPropertyChanged(nameof(MyCollection));//如果仍然需要}).ConfigureAwait(false);}....公共异步任务SafeWrapWithWaitingScreenAsync(Func< Task>操作){DisplayWaitingScreen = true;//等待屏幕"的可见性绑定到这个尝试{等待action().ConfigureAwait(false);}抓住(前例外){HandleException(ex);//显示/登录ex}最后{DisplayWaitingScreen = false;}} 

a)从视图绑定到命令,并且

命令正文中的

a1) 使用 ActualPageNumber 属性而不是 arg

a2) 传递一个 CommandParameter ,该参数绑定到与 TextBox.Text 相同的属性.示例(可能会丢失一些东西,因为不是真正的代码):

 < TextBox Text =" {Binding ActualPageNumber,Mode = TwoWay,UpdateSourceTrigger = PropertyChanged}>< TextBox.InputBindings>< KeyBinding Key =" Return"Command ="{Binding UpdateCommand}"CommandParameter =" {Binding ActualPageNumber}"/>< KeyBinding Key =" Enter"Command ="{Binding UpdateCommand}"CommandParameter =" {Binding ActualPageNumber}"/></TextBox.InputBindings></TextBox> 

b)不确定是否正确,但是在看到https://blog.stephencleary.com/2013/01/async-oop-3-properties.html

I also found useful stuff like https://stackoverflow.com/a/9343733/5852947, https://stackoverflow.com/a/13735418/5852947, https://nmilcoff.com/2017/07/10/stop-toggling-isbusy-with-notifytask/

Still I struggle how to go on this case. AsyncEx library can be the solution, an example would be nice.

I just would like to notify the user that the page is actually loading. If I could call async from the setter I could do it, but then I still can not use await in the setter because it is not async.

解决方案

1) For the responsiveness of the DataGrid, this binding property might help: IsAsync=True

<DataGrid ItemsSource="{Binding MyCollection, IsAsync=True}"

also look into these DataGrid properties:

VirtualizingPanel.IsVirtualizing
VirtualizingPanel.VirtualizationMode (you'll probably need Recycling)
VirtualizingPanel.IsVirtualizingWhenGrouping
EnableRowVirtualization
EnableColumnVirtualization

But be careful, virtualization can play tricks on you. For example, I had a RowHeader (with the row number) and the values got scrambled when virtualization was on.

2) About the async setter for data binding: I was using a custom version of IAsyncCommand (see Stephen Cleary's example).

I used the command in 2 ways: a) binding to it from the view (avoiding the async setter altogether) or b) launching it from the setter (not nice).

Example: I created an UpdateCommand as an AsyncCommand and placed everything I needed done asynchronously (like getting the values from the DB). Everything in this command is wrapped within a display+hide of a "in progress"-like control - in my case, a transparent cover with a spinner + "please wait...", to prevent other user actions (the "screen" is visible while the task is performed). Stripped down sample:

    ....
    public MainWindowViewModel()
    {
       UpdateCommand = AsyncCommand.Create(Update); // our own custom implementation of AsyncCommand
    }
    ....

    public AsyncCommand UpdateCommand { get; }
    internal async Task Update(object arg)
    {
        await SafeWrapWithWaitingScreenAsync(async () =>
        {
            var value = (int)arg; // or the ActualPageNumber, if used from a1)

            var data = await GetDataFromDb(value).ConfigureAwait(false);

            ...// fill in MyCollection (which is the DataGrid's ItemsSource) using the data

            OnPropertyChanged(nameof(MyCollection));// if still needed
        }).ConfigureAwait(false);
    }

    ....
    public async Task SafeWrapWithWaitingScreenAsync(Func<Task> action)
    {
        DisplayWaitingScreen = true; //Visibility of the "Waiting screen" binds to this
        try
        {
            await action().ConfigureAwait(false);
        }
        catch (Exception ex)
        {
            HandleException(ex); // display/log ex
        }
        finally
        {
            DisplayWaitingScreen = false;
        }
    }

a) Binding to the command from the view and

a1) in the command's body use ActualPageNumber property instead of the arg value

or a2) passing a CommandParameter which binds to the same property as TextBox.Text does. Example (could be missing something, couse is not the real code):

<TextBox Text="{Binding ActualPageNumber, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
    <TextBox.InputBindings>
         <KeyBinding Key="Return" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
         <KeyBinding Key="Enter" Command="{Binding UpdateCommand}" CommandParameter="{Binding ActualPageNumber}" />
    </TextBox.InputBindings>
</TextBox>

b) Not sure this is right, but before seeing Stephen's approach with NotifyTaskCompletion<TResult> (which I will probably use in the future), for the setter, I launched the command something like:

private int actualPageNumber;
public int ActualPageNumber
    {
        get => actualPageNumber;
        set
        {
            actualPageNumber = value;
            OnPropertyChanged(); //the sync way

            UpdateCommand.Execute(value);
        }
    }

这篇关于如何从属性设置器调用异步函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆