0

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 (e.g.) 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.

miriyo
  • 103
  • 1
  • 11

1 Answers1

1

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);
        }
    }
}
Dave M
  • 2,863
  • 1
  • 22
  • 17
  • Hi, Many thanks for your great example and answer. It seems that since I'm restricted to using .NET 4.0 that Task.Run(...) isn't available to me as an option. I cannot change the version of the Framework at the moment unfortunately. I have already looked for alternatives but using the same "principle" as it were. The solution in this question https://stackoverflow.com/questions/6075998/update-ui-label-when-using-task-factory-startnew does in fact work for me, having investigated further. Would you agree that this would be a suitable alternative to your suggestion? – miriyo Mar 06 '18 at 13:01
  • I believe that there’s a nuget package that lets you use async/await with .net 4.0 – Dave M Mar 06 '18 at 13:08
  • I'm going to investigate that but also create a separate .NET 4.5 project to use your suggestion and mark your answer as the solution. Thanks again. – miriyo Mar 07 '18 at 13:11