简体   繁体   中英

UI Responsiveness and working with “SelectedItem” ListView/ListBox in WPF

I have a handful of situations where I am trying to design my WPF .NET 4.0 application to handle a change of a SelectedItem in a ListBox/ListView.

Essentially I want to query other data to fill an ObservableCollection/DataGrid based on what a user has selected. My problem is that by having everything on the same thread, the responsiveness suffers inasmuch as the item that is clicked only shows as being Selected once any code started in the "SelectedItem" Setter has finished and this "wait" doesn't respond how I want it to.

The data being queried normally fills an ObservableCollection via LINQ from the same database which is why everything is using the same UI thread.

Ideally I want:

  1. A user to click on an item in a list which is selected immediately without the feeling of the application hanging.
  2. An "IsBusy" property to be set to true (used in bindings to change the enabled status of certain controls).
  3. Start querying other data and cnce the that is finished, a (eg) DataGrid should be populated and the IsBusy is returned to false.

What is the best way to achieve this with secondary data loading "in the background"? I've used BackgroundWorker in the past in WinForms but am eager to learn more about the Dispatcher which I think is the right direction.

My SelectedItem property is as straightforward as this:

    public Employee SelectedEmployee
    {
        get
        {
            return mvSelectedEmployee;
        }

        set
        {
            mvSelectedEmployee = value;
            RaisePropertyChanged();

            IsBusy = true;
            QueryMyEmployeesAddressesAndOtherData(); //which takes sometimes 2 seconds
            IsBusy = false;
        }
    }

I want the user to know something is happening but smoothly despite there being a slight delay.

Thank you.

Using async/await is the best way to achieve this. One issue for your particular scenario is that property setters cannot be marked async . You can however fire off an async void method from the setter. Here's a simple example that will work without you having to change anything else in your code base:

public Employee SelectedEmployee
{
    get
    {
        return mvSelectedEmployee;
    }

    set
    {
        mvSelectedEmployee = value;
        RaisePropertyChanged();
        UpdateSelectedEmployeeAsync();
    }
}

private async void UpdateSelectedEmployeeAsync()
{
    IsBusy = true;
    await Task.Run(() => QueryMyEmployeesAddressesAndOtherData());
    IsBusy = false;
}

But probably what you'll really want to do is make QueryMyEmployeesAddressesAndOtherData asynchronous "all the way down". Task.Run is really more for running CPU bound operations in the background, but it sounds like yours is probably mostly IO bound (waiting on some queries to execute). Whatever persistence framework you are using very likely has async support (if it doesn't, consider updating to one that does). Now you might end up with something like this:

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);
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM