如何从属性设置器调用异步函数? [英] How to call async function from property setter?
问题描述
我有一个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屋!