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 线程。

理想情况下我想要:

  1. 用户单击列表中的项目后立即被选中,而没有应用程序挂起的感觉。
  2. 一个 "IsBusy" 属性 设置为 true(用于绑定以更改某些控件的启用状态)。
  3. 开始查询其他数据并确认已完成,应填充(例如)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);
        }
    }
}