UI 在 WPF 中响应和使用 "SelectedItem" ListView/ListBox
UI Responsiveness and working with "SelectedItem" ListView/ListBox in WPF
在少数情况下,我试图设计我的 WPF .NET 4.0 应用程序来处理 ListBox/ListView 中 SelectedItem 的更改。
本质上我想查询其他数据以根据用户选择的内容填充 ObservableCollection/DataGrid。我的问题是,通过将所有内容都放在同一个线程上,响应性会受到影响,因为一旦 "SelectedItem" Setter 中启动的任何代码完成并且此 "wait" 没有按照我想要的方式响应。
被查询的数据通常通过来自同一数据库的 LINQ 填充一个 ObservableCollection,这就是为什么一切都使用相同的 UI 线程。
理想情况下我想要:
- 用户单击列表中的项目后立即被选中,而没有应用程序挂起的感觉。
- 一个 "IsBusy" 属性 设置为 true(用于绑定以更改某些控件的启用状态)。
- 开始查询其他数据并确认已完成,应填充(例如)DataGrid 并将 IsBusy 返回 false。
通过辅助数据加载实现此目的的最佳方法是什么 "in the background"?我过去曾在 WinForms 中使用过 BackgroundWorker,但我渴望了解更多关于 Dispatcher 的知识,我认为这是正确的方向。
我的 SelectedItem 属性 就这么简单:
public Employee SelectedEmployee
{
get
{
return mvSelectedEmployee;
}
set
{
mvSelectedEmployee = value;
RaisePropertyChanged();
IsBusy = true;
QueryMyEmployeesAddressesAndOtherData(); //which takes sometimes 2 seconds
IsBusy = false;
}
}
我希望用户知道发生了一些事情,但尽管有轻微的延迟,但还是很顺利。
谢谢。
使用 async/await 是实现此目的的最佳方法。针对您的特定情况的一个问题是 属性 setter 无法标记为 async
。但是,您可以从 setter 触发 async void
方法。这是一个简单的示例,无需更改代码库中的任何其他内容即可运行:
public Employee SelectedEmployee
{
get
{
return mvSelectedEmployee;
}
set
{
mvSelectedEmployee = value;
RaisePropertyChanged();
UpdateSelectedEmployeeAsync();
}
}
private async void UpdateSelectedEmployeeAsync()
{
IsBusy = true;
await Task.Run(() => QueryMyEmployeesAddressesAndOtherData());
IsBusy = false;
}
但您真正想要做的可能是使 QueryMyEmployeesAddressesAndOtherData
异步 "all the way down"。 Task.Run
实际上更适合后台的 运行 CPU 绑定操作,但听起来你的可能主要是 IO 绑定(等待执行某些查询)。您使用的任何持久性框架很可能具有异步支持(如果不支持,请考虑更新为支持异步的框架)。现在你可能会得到这样的结果:
private async void UpdateSelectedEmployeeAsync()
{
IsBusy = true;
await QueryMyEmployeesAddressesAndOtherDataAsync();
IsBusy = false;
}
private async Task QueryMyEmployeesAddressesAndOtherDataAsync()
{
using (var ctx = new MyContext())
{
var queryResults = await ctx.EmployeeData.Where(t=>t.EmployeeId == SelectedEmployee.Id).ToArrayAsync();
SelectedEmployeeDatas.Clear();
foreach (var data in queryResults)
{
SelectedEmployeeDatas.Add(data);
}
}
}
在少数情况下,我试图设计我的 WPF .NET 4.0 应用程序来处理 ListBox/ListView 中 SelectedItem 的更改。
本质上我想查询其他数据以根据用户选择的内容填充 ObservableCollection/DataGrid。我的问题是,通过将所有内容都放在同一个线程上,响应性会受到影响,因为一旦 "SelectedItem" Setter 中启动的任何代码完成并且此 "wait" 没有按照我想要的方式响应。
被查询的数据通常通过来自同一数据库的 LINQ 填充一个 ObservableCollection,这就是为什么一切都使用相同的 UI 线程。
理想情况下我想要:
- 用户单击列表中的项目后立即被选中,而没有应用程序挂起的感觉。
- 一个 "IsBusy" 属性 设置为 true(用于绑定以更改某些控件的启用状态)。
- 开始查询其他数据并确认已完成,应填充(例如)DataGrid 并将 IsBusy 返回 false。
通过辅助数据加载实现此目的的最佳方法是什么 "in the background"?我过去曾在 WinForms 中使用过 BackgroundWorker,但我渴望了解更多关于 Dispatcher 的知识,我认为这是正确的方向。
我的 SelectedItem 属性 就这么简单:
public Employee SelectedEmployee
{
get
{
return mvSelectedEmployee;
}
set
{
mvSelectedEmployee = value;
RaisePropertyChanged();
IsBusy = true;
QueryMyEmployeesAddressesAndOtherData(); //which takes sometimes 2 seconds
IsBusy = false;
}
}
我希望用户知道发生了一些事情,但尽管有轻微的延迟,但还是很顺利。
谢谢。
使用 async/await 是实现此目的的最佳方法。针对您的特定情况的一个问题是 属性 setter 无法标记为 async
。但是,您可以从 setter 触发 async void
方法。这是一个简单的示例,无需更改代码库中的任何其他内容即可运行:
public Employee SelectedEmployee
{
get
{
return mvSelectedEmployee;
}
set
{
mvSelectedEmployee = value;
RaisePropertyChanged();
UpdateSelectedEmployeeAsync();
}
}
private async void UpdateSelectedEmployeeAsync()
{
IsBusy = true;
await Task.Run(() => QueryMyEmployeesAddressesAndOtherData());
IsBusy = false;
}
但您真正想要做的可能是使 QueryMyEmployeesAddressesAndOtherData
异步 "all the way down"。 Task.Run
实际上更适合后台的 运行 CPU 绑定操作,但听起来你的可能主要是 IO 绑定(等待执行某些查询)。您使用的任何持久性框架很可能具有异步支持(如果不支持,请考虑更新为支持异步的框架)。现在你可能会得到这样的结果:
private async void UpdateSelectedEmployeeAsync()
{
IsBusy = true;
await QueryMyEmployeesAddressesAndOtherDataAsync();
IsBusy = false;
}
private async Task QueryMyEmployeesAddressesAndOtherDataAsync()
{
using (var ctx = new MyContext())
{
var queryResults = await ctx.EmployeeData.Where(t=>t.EmployeeId == SelectedEmployee.Id).ToArrayAsync();
SelectedEmployeeDatas.Clear();
foreach (var data in queryResults)
{
SelectedEmployeeDatas.Add(data);
}
}
}