Lasse Madsen
Lasse Madsen

Reputation: 602

ListView is not scrolling with grouping

I simply changed my ListView to use grouping, but now I can't use ScrollTo anymore.

I have create a simple app, so you can see the problem.

The XAML-page looks like (I am not using XAML in my app at the moment, but I will in an upcoming version).

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
   xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
   xmlns:local="clr-namespace:ScrollListExample"
   x:Class="ScrollListExample.ProjectPage">

   <Grid>
      <Grid.RowDefinitions>
         <RowDefinition Height="Auto" />
         <RowDefinition Height="*" />
      </Grid.RowDefinitions>

      <ListView x:Name="ProjectsListView" HasUnevenRows="True" IsGroupingEnabled="True" ItemsSource="{Binding Projects}">
         <ListView.GroupHeaderTemplate>
            <DataTemplate>
               <ViewCell>
                  <Label Text="{Binding Path=Key}" />
               </ViewCell>
            </DataTemplate>
         </ListView.GroupHeaderTemplate>
         <ListView.ItemTemplate>
            <DataTemplate>
               <ViewCell>
                  <Grid>
                     <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="Auto" />
                     </Grid.ColumnDefinitions>
                     <Label Grid.Column="0" Grid.Row="0" LineBreakMode="TailTruncation" Text="{Binding Path=ProjectName}" />
                     <Label Grid.Column="0" Grid.Row="1" Text="{Binding Path=ProjectReference, StringFormat='Sag: {0}'}" />
                     <Label Grid.Column="0" Grid.Row="2" Text="{Binding Path=CustomerName}" />
                     <Label Grid.Column="0" Grid.Row="3" Text="{Binding Path=FullAddress}" />
                     <Label Grid.Column="1" Grid.Row="0" Text="{Binding Path=StartTime}" />
                  </Grid>
               </ViewCell>
            </DataTemplate>
         </ListView.ItemTemplate>
      </ListView>
   </Grid>

</ContentPage>

And the code-behind file for the example looks like this

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class ProjectPage : ContentPage
{
   public ProjectPage()
   {
      InitializeComponent();
      BindingContext = new ProjectsViewModel();
   }

   protected override void OnAppearing()
   {
      base.OnAppearing();

      Acr.UserDialogs.UserDialogs.Instance.ShowLoading();

      var projects = Newtonsoft.Json.JsonConvert.DeserializeObject<IList<ProjectDto>>("[{\"ProjectName\":\"Test sag\",\"ProjectReference\":\"10072\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 3\",\"StartDate\":\"2017-02-02T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"aaa\",\"ProjectReference\":\"10077\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 12\",\"StartDate\":\"2017-02-08T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10082\",\"CustomerName\":\"Test firma\",\"FullAddress\":\"Testvej 50\",\"StartDate\":\"2017-02-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10085\",\"CustomerName\":\"Testvej boligselskab\",\"FullAddress\":\"Testvej 14\",\"StartDate\":\"2017-02-24T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10086\",\"CustomerName\":\"Testing\",\"FullAddress\":\"Testevej 14\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test1\",\"ProjectReference\":\"10087\",\"CustomerName\":\"Plejecenter testlyst\",\"FullAddress\":\"Testlystvej 11\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test2\",\"ProjectReference\":\"10088\",\"CustomerName\":\"Charlie\",\"FullAddress\":\"Testvej 50\",\"StartDate\":\"2017-02-27T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10089\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10090\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10091\",\"CustomerName\":\"Standard Debitor\",\"FullAddress\":\"[Mangler]\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10092\",\"CustomerName\":\"Tester\",\"FullAddress\":\"Testvej 11\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10093\",\"CustomerName\":\"Plejehjemmet test\",\"FullAddress\":\"Testvej 90\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"},{\"ProjectName\":\"Test\",\"ProjectReference\":\"10094\",\"CustomerName\":\"Plejehjemmet test\",\"FullAddress\":\"Testvej 90\",\"StartDate\":\"2017-03-16T00:00:00\",\"StartTime\":\"\"}]");

      var viewModel = BindingContext as ProjectsViewModel;
      if (viewModel != null)
         viewModel.OriginalProjects = projects;

      Acr.UserDialogs.UserDialogs.Instance.ShowLoading("Loading");

      Task.Delay(5000).ContinueWith((x) =>
      {
          Device.BeginInvokeOnMainThread(Acr.UserDialogs.UserDialogs.Instance.HideLoading);

         Search();
      });
   }

   private void Search(string inputVal = null)
   {
      var viewModel = BindingContext as ProjectsViewModel;

      if (viewModel != null)
      {
         var projects = viewModel.OriginalProjects.Where(p => !string.IsNullOrEmpty(inputVal) ? p.ProjectName.Contains(inputVal) : true);

         var orderedProjects = projects.OrderBy(p => p.StartDate);

         Device.BeginInvokeOnMainThread(() =>
         {
            foreach (ProjectDto project in orderedProjects)
            {
               var coll = viewModel.Projects.FirstOrDefault(c => c.Key == project.StartDate);

               if (coll == null)
                  viewModel.Projects.Add(coll = new ObservableCollectionWithDateKey { Key = project.StartDate });

               coll.Add(project);
            }

            var group = viewModel.Projects.LastOrDefault();
            if (group != null)
               ProjectsListView.ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false);
         });
      }
   }
}

class ProjectsViewModel : INotifyPropertyChanged
{
   private ObservableCollection<ObservableCollectionWithDateKey> _projects;

   public event PropertyChangedEventHandler PropertyChanged;

   public IEnumerable<ProjectDto> OriginalProjects { get; set; }

   public ObservableCollection<ObservableCollectionWithDateKey> Projects
   {
      get { return _projects; }
      set
      {
         _projects = value;
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Projects)));
      }
   }

   public ProjectsViewModel()
   {
      Projects = new ObservableCollection<ObservableCollectionWithDateKey>();
   }
}

public class ProjectDto : INotifyPropertyChanged
{
   public string ProjectName { get; set; }
   public string ProjectReference { get; set; }
   public string CustomerName { get; set; }
   public string FullAddress { get; set; }
   public DateTime StartDate { get; set; }
   public string StartTime { get; set; }

   public event PropertyChangedEventHandler PropertyChanged;
}

class ObservableCollectionWithDateKey : ObservableCollection<ProjectDto>
{
   public DateTime Key { get; set; }
}

I use Task.Delay(5000) to simulate a response from the server, but I do not think, it matters.

UPDATE

I figured out, the problem was in my ScrollTo-call, where ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false); was called with the Key instead of just the group.

If you create the grouping first (without adding it to the ViewModel), you have to find the correct model in the ViewModel afterwards. As it otherwise does not find the correct ObservableCollection

Upvotes: 2

Views: 787

Answers (1)

Nico Zhu
Nico Zhu

Reputation: 32775

I have tested your code and reproduced your issue. The problem is you have passed the wrong parameter to ScrollTo method.

ProjectsListView.ScrollTo(group.First(), group.Key, ScrollToPosition.Start, false);

The group parameter of ScrollTo method is the group from your ListView.ItemsSource. But your passed a group.Key. So the method will not be excited as expect. Please modify the code like following.

Device.BeginInvokeOnMainThread(() =>
 {
     foreach (ProjectDto project in orderedProjects)
     {
         var coll = viewModel.Projects.FirstOrDefault(c => c.Key == project.StartDate);

         if (coll == null)
             viewModel.Projects.Add(coll = new ObservableCollectionWithDateKey { Key = project.StartDate });

         coll.Add(project);
     }
 var group = viewModel.Projects.Last();
 if (group != null)
     ProjectsListView.ScrollTo(group.First(), group, ScrollToPosition.Start, false);
});

Upvotes: 4

Related Questions