none
WPF Async & CollectionViewSource Issues (x2) RRS feed

  • Question

  • I have a ViewModel that I want to load data (with a busy indicator running until the data's loaded, using MVVMLight) on only when it's loaded. I'm using an EventTrigger in my XAML like this:

    <i:Interaction.Triggers>
         <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding InitializeDataCommand}"/>
         </i:EventTrigger>
     </i:Interaction.Triggers>

    and handling the command in my ViewModel like this:

    public ICommand InitializeDataCommand
         => new RelayCommand(async () => await LoadData());
      
    private async Task LoadData()
     {
         _messenger.Send(new IsBusyMessage("Loading..."));
    
        var data = await Task.Run(() => GetTools());
    
        //flatten the data for the grid
         data.ForEach(t =>
         t.MyC.ForEach(c =>
         c.MyM.ForEach(m =>
         m.MyA.ForEach(a =>
         a.MyF.ForEach(f =>
         {
             var channels = Task.Run(() => GetMoreStuffByFrameId(f.Id)).Result;
             if (channels.Stuff.Any())
                 channels.Stuff.Where(x => x.Id > 0).ForEach(c =>
                 {
                     _data.Add(new MyItem()
                     {
                         Tid = t.Id,
                         TName = t.Name,
                         CId = c.Id,
                         CName = c.Name
          //etc...
                     });
                 });
         })))));
    
        GridItems = _data.ToObservableCollection(); //bind once
    
        _messenger.Send(new IsBusyMessage(string.Empty));
         _collectionViewSource.Source = _gridItems;
    
    } 

    The busy indicator fires up and spins while GetStuff() runs. Then, the busy indicator 'freezes' during the ForEach loops (Problem 1)
    Then I bind my GridItems to _data, and set my grids collectionviewsource source. However, the data isn't isnt displayed (Problem 2)

    I know the Grid bindings are fine because I can do the same routine when instantiating the viewmodel.

    How can I get my busy indicator to keep spinning, and display the data?


    NOTE: If I remove my CollectionViewSource from the grid and bind straight to GridItems, they show up. I need the CollectionViewSource though for filtering.
    Friday, June 8, 2018 10:40 AM

Answers

  • Hi Franklin,

    I have uploaded a demo project which shows the problem.

    The link is https://1drv.ms/f/s!AgrhDRMd4TRL0rgFCYHYZGTrgAXCJw

    Craig


    Hi Craig,

    Please call RaisePropertyChanged() method to notify the CollectionView has been changed:

    worker.RunWorkerCompleted += (o, ea) =>
                {
                    GridItems = tmpList.ToObservableCollection();
                    _collectionViewSource.Source = GridItems;
                    _collectionViewSource.View.Refresh();
                    RaisePropertyChanged("CollectionView");
                };

    Screenshot:


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Thursday, June 14, 2018 9:24 AM
    Moderator

All replies

  • Hi Craig,

    >>The busy indicator fires up and spins while GetStuff() runs. Then, the busy indicator 'freezes' during the ForEach loops (Problem 1)

    May I ask which way you implement the busy indicator? Have you used third-party control?

    Basically, the long running process must occur on a separate thread, for your case, it refers to the loading process. The UI thread needs to stay responsive while the process is running.  The most common way is to use the BackgroundWorker.

    >>Then I bind my GridItems to _data, and set my grids collectionviewsource source. However, the data isn't isnt displayed (Problem 2)

    I noticed that the you are trying to set the Source of CollectionViewSource object to a private variable:

     _collectionViewSource.Source = _gridItems;

    Because you have a property called GridItems, it should have implemented the INotifyPropertyChanged interface, the easy way is directly setting the Source to it:

     _collectionViewSource.Source = GridItems;



    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Monday, June 11, 2018 2:16 AM
    Moderator
  • Hi Franklin,

    Thanks for the answer. I almost have it working. What I did was wrap it in a background worker like this:

    public ICollectionView CollectionView
     => _collectionViewSource.View;

    private void LoadData()
    {
     var gridData = new List<GridOnlyFilterItem>();
     BackgroundWorker worker = new BackgroundWorker();
     
     worker.DoWork += (o, ea) =>
     {
     
      var data = await Task.Run(() => GetStuff());
      
       //flatten the data for the grid
       data.ForEach(t =>
       t.MyC.ForEach(c =>
       c.MyM.ForEach(m =>
       m.MyA.ForEach(a =>
       a.MyF.ForEach(f =>
       {
        var channels = Task.Run(() => GetMoreStuffByFrameId(f.Id)).Result;
        if (channels.Stuff.Any())
         channels.Stuff.Where(x => x.Id > 0).ForEach(c =>
         {
          _data.Add(new MyItem()
          {
           Tid = t.Id,
           TName = t.Name,
           CId = c.Id,
           CName = c.Name
       //etc...
          });
         });
       })))));
       
      };
     
     worker.RunWorkerCompleted += (o, ea) =>
     {
      GridItems = gridData.ToObservableCollection();
     
      _collectionViewSource.Source = GridItems;
      _collectionViewSource.View.Refresh();
     
      _messenger.Send(new IsBusyMessage(string.Empty));
     };
     
     _messenger.Send(new IsBusyMessage("Loading..."));
     worker.RunWorkerAsync(); 

    }

    The UI is now responsive, but the data isn't displayed on the UI, even after setting:

      _collectionViewSource.Source = GridItems;
      _collectionViewSource.View.Refresh();


    Monday, June 11, 2018 9:18 AM
  • Hi Craig,

    >>What I did was wrap it in a background worker like this. The UI is now responsive, but the data isn't displayed on the UI

    Okay, we are almost there.

    You are setting the Source of a private CollectionViewSource variable, how did you bind the UI control to this CVS?


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Tuesday, June 12, 2018 5:43 AM
    Moderator
  • Hi Franklin,

    In my XAML, my DataGrid's ItemsSource is bound to the public CollectionView like this:

    <DataGrid ItemsSource="{BindingCollectionView}" .../>


    Tuesday, June 12, 2018 7:43 AM
  • Hi Franklin,

    In my XAML, my DataGrid's ItemsSource is bound to the public CollectionView like this:

    <DataGrid ItemsSource="{BindingCollectionView}" .../>


    Hi Craig,

    Is it possible you can share a simple demo project to us? So that we can easily locate the issue instead of guessing lots of the reasons. Thanks for your understanding.


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Wednesday, June 13, 2018 10:29 AM
    Moderator
  • Hi Franklin,

    I have uploaded a demo project which shows the problem.

    The link is https://1drv.ms/f/s!AgrhDRMd4TRL0rgFCYHYZGTrgAXCJw

    Craig

    Wednesday, June 13, 2018 3:03 PM
  • Hi Franklin,

    I have uploaded a demo project which shows the problem.

    The link is https://1drv.ms/f/s!AgrhDRMd4TRL0rgFCYHYZGTrgAXCJw

    Craig


    Hi Craig,

    Please call RaisePropertyChanged() method to notify the CollectionView has been changed:

    worker.RunWorkerCompleted += (o, ea) =>
                {
                    GridItems = tmpList.ToObservableCollection();
                    _collectionViewSource.Source = GridItems;
                    _collectionViewSource.View.Refresh();
                    RaisePropertyChanged("CollectionView");
                };

    Screenshot:


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Thursday, June 14, 2018 9:24 AM
    Moderator
  • Ah, brilliant, thanks Franklin!

    Sometimes the answer is right in front of you.

    Thanks again.

    Thursday, June 14, 2018 9:38 AM