我有一个wpf应用程序,其中的 TextBox 绑定到了VM中的 ActualPageNumber 属性.我还有绑定到显示给定页面的 ObservableCollection 的 DataGrid .数据存储在DB中.当我更改 ActualPageNumber 时,设置器访问数据库的速度可能会很慢.这就是为什么我想要一个异步setter来保持gui响应的原因.
我了解没有异步设置器: blog.stephencleary/2013/01/async-oop-3-properties.html
我还发现了诸如 stackoverflow/a/9343733/5852947 , stackoverflow/a/13735418/5852947 ,"> nmilcoff/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 的自定义版本(请参阅斯蒂芬·克雷里(Stephen Cleary)的例子).
我以两种方式使用了该命令: a)从视图绑定到它(完全避免使用异步设置程序)或 b)从设置程序启动它(不是很好).
示例:我创建了一个 UpdateCommand 作为 AsyncCommand ,并异步放置了我需要做的所有事情(例如从数据库中获取值).此命令中的所有内容都包裹在进行中"类控件的显示+隐藏中-在我的情况下,是带有微调框的透明盖+请稍候...",以防止其他用户动作(在执行任务时可见屏幕").精简样本:
....公共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)不确定是否正确,但是在看到斯蒂芬的方法与 NotifyTaskCompletion< TResult> (我可能会在未来),对于二传手,我启动了类似以下命令:
private int actualPageNumber;public int ActualPageNumber{得到=>ActualPageNumber;放{ActualPageNumber =值;OnPropertyChanged();//同步方式UpdateCommand.Execute(值);}}I have a wpf application with a TextBox bound to ActualPageNumber property in the VM. I also have a DataGrid bound to an ObservableCollection which displays the given page. The data are stored in DB. When I change the ActualPageNumber, the setter accesses the db which can be slow. That is why I wanted an async setter, to keep the gui responsive.
I understand there is no async setter: blog.stephencleary/2013/01/async-oop-3-properties.html
I also found useful stuff like stackoverflow/a/9343733/5852947, stackoverflow/a/13735418/5852947, nmilcoff/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 EnableColumnVirtualizationBut 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); } }
更多推荐
如何从属性设置器调用异步函数?
发布评论