char m
char m

Reputation: 8336

How to fetch data in background and update UI with the data asynchronously when ready?

I have Selected objects DataGrid whose row selection changed is handled in method (using interactrion event trigger InvokeCommandAction). A row represents a real life object and has lots of properties.

I first set an property that row corresponds and highlight object on map, do some other stuff and finally call async a method that fetches object properties and refreshes property DataGrid with those properties.

SelectedObjectsViewModel.cs (Viewmodel for UserControl containing SelectedObjects DataGrid)

public void SelectedObjectsGridSelectionChangedCommand(object parameter)
{
    IList selectedRows = parameter as IList;
    if (selectedRows.Count == 1)
        ObjectProperties.Instance.SetAttributeObjectNoRefresh(((SelectedNetObjectBindingSource)selectedRows[0]).Data);
    else                         
        ObjectProperties.Instance.SetAttributeObjectNoRefresh(null);

     SetAttributeObjectExtraHightlight<SelectedNetObjectBindingSource>(selectedRows);

     DoSomeOtherStuff(selectedRows)

     App.Current.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Action)(() =>
     {
         ((ObjectViewModel)MyApp.ViewModels["Shared.Panels.Object"]).SelectedObjectsChanged();
     }));
}

ObjectViewModel.cs (Viewmodel for UserControl containing Properties DataGrid)

    // this is ItemsSource binding for properties datagrid 
    public ICollectionView ObjectInfoItems { get; private set; }

    public ObjectViewModel()
    {
         ObjectInfoItems = CollectionViewSource.GetDefaultView(ObjectProperties.Instance.GetAttributeObjectAttributes());
    }

    public void SelectedObjectsChanged()
    {
         // refreshed DataGrid
         ObjectInfoItems.Refresh();
    }

ObjectProperties.cs

    public void SetAttributeObjectNoRefresh(NetObject netObject)
    {
        _attributeObject = netObject;
    }

   public IEnumerable<PropertyRow> GetAttributeObjectAttributes()
    {
        if (_attributeObject != null)
        {
            List<string> fieldNames = new List<string>();
            List<string> visibleNames = new List<string>();
            List<string> values = new List<string>();
            /* get properties for _attributeObject from db and 
             * process in native code */
            var properties = GetPropertiesFilterAndSort(fieldNames, visibleNames, values, _attributeObject);

            foreach (var property in properties)
            {
                yield return property;
            }
        }
    }

However there's up to 1,2 second delay for DataGrid containing object properties to be refreshed after everything else like highlighting is done. I would like to start fetching immediately i know the object, proceed with highlighting etc and finally have asynchronously started SelectedObjectsChanged to use the fetched property data just to refresh DataGrid.

Fetching involves both database fetch and some processing. Depending on circumstances and settings the database fetch time can be up to 80%, but it can be only 50%.

My question is: How this should be done so that:

  1. The row selection in Selected objects DataGrid and highlighting on map proceed immediately not waiting property fetching or property datagrid refresh at all
  2. The property DataGrid refresh after the Selected objects datagrid selection does not have long delay because it's fetching the properties

Thanks!

Upvotes: 2

Views: 1131

Answers (1)

mm8
mm8

Reputation: 169420

You should execute any long-running method on a background thread. Note that you cannot access any UI elements on a background thread though, so you basically need to start a new task that fetches the data, and then handle any UI stuff once the task completes.

Here is a basic sample that executes the GetAttributeObjectAttributes() method on a background thread:

private ICollectionView _ojectInfoItems;
public ICollectionView ObjectInfoItems
{
    get { return myVar; }
    set { myVar = value; OnPropertyChanged(); }
}

public ObjectViewModel()
{
    Task.Factory.StartNew(() =>
    {
        return ObjectProperties.Instance.GetAttributeObjectAttributes();
    }).ContinueWith(task =>
    {
        ObjectInfoItems = CollectionViewSource.GetDefaultView(task.Result);
    }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}

Note that the ObjectViewModel class should implement the INotifyPropertyChanged interface and raise the PropertyChanged event whenever the data-bound ObjectInfoItems property is set.

Edit:

Another example based on your comments:

public void SelectedObjectsGridSelectionChangedCommand(object parameter)
{
    object data = null;
    IList selectedRows = parameter as IList;
    if (selectedRows.Count == 1)
        data = (SelectedNetObjectBindingSource)selectedRows[0]).Data;

    Task.Factory.StartNew(() =>
    {
        ObjectProperties.Instance.SetAttributeObjectNoRefresh(null);

        Parallel.Invoke(
            () => SetAttributeObjectExtraHightlight<SelectedNetObjectBindingSource>(selectedRows),
            () => DoSomeOtherStuff(selectedRows));

    }).ContinueWith(task =>
    {
        ((ObjectViewModel)MyApp.ViewModels["Shared.Panels.Object"]).SelectedObjectsChanged();
    }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
}

Upvotes: 4

Related Questions