0

I am attempting to make a simple filter menu using a ListView with Grouping enabled. Currently I'm doing this by generating a modal page from the parent page, which is passed an object I need to populate the menu, and which contains a few buttons plus the list page. I am unable to get even the simplest grouping ListView appearing however. I've followed the tutorial here to get to where I'm at.

I'll include the XAML, codebehind, and ViewModel for the Filter Page, as well as the models described in the tutorial for the filter groups and filter options. Please know that although the page is passed an object in my code, I'm not using it in any way yet with what I'm posting here.

XAML

    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:d="http://xamarin.com/schemas/2014/forms/design"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             x:Class="MyProject.Views.FilterMenu">
        <ContentPage.Content>

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

                <Button Text="Close"
                        Grid.Column="0"
                        Grid.Row="0"
                        HorizontalOptions="FillAndExpand"
                        Clicked="CloseButton_OnClicked"/>

                <Button Text="Clear Filter"
                        Grid.Column="1"
                        Grid.Row="0"
                        HorizontalOptions="FillAndExpand"
                        Clicked="ClearButton_OnClicked"/>

                <Label Text="Filter By"
                       Grid.Column="0"
                       Grid.ColumnSpan="2"
                       Grid.Row="1"
                       VerticalOptions="FillAndExpand" 
                       HorizontalOptions="StartAndExpand"
                       HorizontalTextAlignment="Start"/>


                <StackLayout Grid.Column="0"
                             Grid.ColumnSpan="2"
                             Grid.Row="2">
                    <ListView ItemsSource="{Binding FilterOptionList}"
                              IsGroupingEnabled="True"
                              GroupDisplayBinding="{Binding Name}">
                        <ListView.ItemTemplate>
                            <DataTemplate>
                                <TextCell Text="{Binding Name}"></TextCell>
                            </DataTemplate>
                        </ListView.ItemTemplate>
                    </ListView>
                </StackLayout>
                <Button
                    Text="Apply Filter"
                    Grid.Column="0"
                    Grid.ColumnSpan="2"
                    Grid.Row="3"
                    Clicked="ApplyFilterButton_OnClicked"/>
            </Grid>

        </ContentPage.Content>
    </ContentPage>

Code Behind

using System;
    using MyProject.Models;
    using MyProject.ViewModels;
    using MvvmHelpers;
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    namespace MyProject.Views
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class FilterMenu : ContentPage
        {
            public FilterMenu(ObservableRangeCollection<Unit> units)
            {
                InitializeComponent();
                BindingContext = new FilterMenuViewModel(Navigation, units);
            }
    
            private void CloseButton_OnClicked(object sender, EventArgs e)
            {
                (BindingContext as FilterMenuViewModel)?.Close();
            }
    
            private void ClearButton_OnClicked(object sender, EventArgs e)
            {
                (BindingContext as FilterMenuViewModel)?.ClearFilter();
            }
    
            private void ApplyFilterButton_OnClicked(object sender, EventArgs e)
            {
                (BindingContext as FilterMenuViewModel)?.ApplyFilter();
            }
        }
    }

ViewModel

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using MyProject.Annotations;
using MyProject.Models;
using Microsoft.Extensions.Options;
using Xamarin.Forms;

namespace MyProject.ViewModels
{
    public class FilterMenuViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<Unit> Units { get; private set; }
        public ObservableCollection<FilterOptionGroup> FilterOptionList = new ObservableCollection<FilterOptionGroup>();
        private readonly INavigation _navigation;
        public event PropertyChangedEventHandler PropertyChanged;       

        public FilterMenuViewModel(INavigation navigation, ObservableCollection<Unit> units )
        {
            _navigation = navigation;
            this.Units = units;

            PopulateCategories();
            FilterOptionList.Add(new FilterOptionGroup("Group One", new []
            {
                new FilterOption
                {
                    Name = "Option One", IsFiltered = false
                },
                new FilterOption
                {
                    Name = "Option One", IsFiltered = false
                }
            }));

            FilterOptionList.Add(new FilterOptionGroup("Group Two", new[]
            {
                new FilterOption
                {
                    Name = "Option One", IsFiltered = false
                },
                new FilterOption
                {
                    Name = "Option Two", IsFiltered = false
                },
                new FilterOption
                {
                    Name = "Option Three", IsFiltered = false
                }
            }));
        }

        [NotifyPropertyChangedInvocator]
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        }

        public async void Close()
        {
            await _navigation.PopModalAsync();
        }

        public void ClearFilter()
        {
            Debug.WriteLine("Clear Filter Clicked.");
        }

        public void ApplyFilter()
        {
            Debug.WriteLine("Apply Filter Clicked.");
        }
    }
}

Filter Option GROUP Collection

using System.Collections.ObjectModel;
using System.Text;

namespace MyProject.Models
{
    public class FilterOptionGroup : ObservableCollection<FilterOption>
    {
        public string Name { get; private set; }

        public FilterOptionGroup(string name)
            : base()
        {
            Name = name;
        }

        public FilterOptionGroup(string name, IEnumerable<FilterOption> source)
            : base(source)
        {
            Name = name;
        }
    }
}

Filter Option Class

using System;
using System.Collections.Generic;
using System.Text;

namespace DormRemote.Models
{
    public class FilterOption
    {
        private string _name;
        private bool _isFiltered;

        public string Name
        {
            get { return _name; }
            set { _name = value; }
    }

        public bool IsFiltered
        {
            get => _isFiltered;
            set => _isFiltered = value;
        }
    }
}

As far as I can tell my binding is set up properly because my buttons print the corresponding debug text, and if I replace the current ListView with a simple ListView without Grouping enabled, and bind it to a simple List it displays correctly.

Additionally, I have tested that the FilterOptionList is being populated correctly as I have printed out the contents of each of its groups using debug code.

I'm not sure if I'm just missing something or if the implementation from this tutorial just doesn't work. Any assistance would be appreciated.

Community
  • 1
  • 1
Blake Simmons
  • 426
  • 1
  • 8
  • 23
  • first, you are binding to `FilterOptionList` which is not a public property of your VM. You can only bind to public properties – Jason Mar 15 '20 at 01:32
  • I'm confused. FilterOptionList is declared at the top as a public ObservableCollection. Am I misunderstanding you? – Blake Simmons Mar 15 '20 at 01:45
  • https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties – Jason Mar 15 '20 at 01:46
  • Could you clarify what it is in this documentation you're referring to? As far as I can tell my ItemsSource List is public and all their internal properties are public – Blake Simmons Mar 15 '20 at 01:50
  • https://stackoverflow.com/questions/295104/what-is-the-difference-between-a-field-and-a-property – Jason Mar 15 '20 at 01:51
  • I understand, I only create FilterOptionList as a "field" rather than as a property. But I've used binding to Lists without creating them as properties before and have had no trouble. Additionally the tutorial I liked creates the List exactly as I do as far as I can tell. I'll try it though. – Blake Simmons Mar 15 '20 at 01:56
  • 1
    in his tutorial `PhonesList` has a get; and set; – Jason Mar 15 '20 at 02:00
  • Ah yes, I missed that. I made that change and now the FilterOptions display, but the headers do not. Any thoughts as to what might be causing that? – Blake Simmons Mar 15 '20 at 02:05
  • It actually appears to be working in the Android build, but not in the GTK build I've been targeting. Seems like a platform issue. Thanks for you help. If you want to post the solution as an answer I'll accept it. – Blake Simmons Mar 15 '20 at 02:10
  • Last update if you're interested: Adding an explicit block fixed this problem in GTK. – Blake Simmons Mar 15 '20 at 02:16

1 Answers1

1

you are binding to FilterOptionList which is not a public property of your VM. You can only bind to public properties.

C# properties have a get; and/or set; Otherwise they are just fields.

Jason
  • 86,222
  • 15
  • 131
  • 146