Need Help with ListView Grouping

someguy65someguy65 Member ✭✭

Hi there,

I am looking for some help with a list view that is grouping data pulled from SQLite database on the device. I am not able to successfully load the list view from the VM property I am binding against the list view when using the MVVM pattern. If I attempt to load the data in the xaml code page, it works fine.

What I am trying to get to is selecting a data (not group) row from the list view and give the end user a pop-up to make corrections if they need to. My goal is using the MVVM pattern to load the data and handle the action of the user selecting a row and then performing tasks based on that.

Any help would be appreciated.

Background:

The purpose of this page is to list history of activity by a user into groupings of shifts. For example a worker's activity for the week:

  1. Starts a shift (#1) and clocks in at 8:00 am then clocks out at 12:00 pm, then back in at 1:00 pm and clocks out at 5:30 pm
  2. Starts a shift (#2) and clocks in at 8:30 am then clocks out at 12:30 pm, then back in at 1:30 pm and clocks out at 6:00 pm
  3. Etc.

I want the grouping to look like this:

Shift #1 (Header)
8:00 am - 12:00 pm (Data)
1:00 pm - 5:30 pm (Data)
Shift #2 (Header)
8:30 am - 12:30 pm (Data)
1:30 pm - 6:00 pm (Data)
...Etc.

The data is derived from the following class:

TimeEntry.cs

            public class TimeEntry
            {
                [PrimaryKey, AutoIncrement]
                public int ID { get; set; }

                public DateTime? StartTime { get; set; }
                public DateTime? EndTime { get; set; }
        public int ShiftID {get; set; }
                ...(excluding other fields for brevity)
            }

I then grouped them into the following class:

ShiftHistory.cs

    public class ShiftHistory : IEnumerable<TimeEntry>
    {
        public int ShiftID { get; set; }

        public ObservableCollection<TimeEntry> TimeEntries { get; set; }

        IEnumerator<TimeEntry> IEnumerable<TimeEntry>.GetEnumerator()
        {
            return TimeEntries.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return TimeEntries.GetEnumerator();
        }
    }

I have loaded the data (poorly) because of numerous freezing problems with SQLite when running custom queries (I am very open to suggestions other than change where you are storing the data -I have plans to do that already). This Task returns data from the last 3 shifts.

Database.cs

    public async Task<List<ShiftHistory>> GetItemsAsync()
    {
        if (App.CurrentShift == null || App.CurrentShift.ID <= 0)
        {
            App.CurrentShift = new ShiftEntry();
            await SetCurrentShift();
        }

        int shiftQuery = App.CurrentShift.ID - 3;

        List<ShiftHistory> shiftHistory = new List<ShiftHistory>();
        ShiftHistory tempHistory = new ShiftHistory();
        ObservableCollection<SiteEntry> tempShiftEntry = new ObservableCollection<SiteEntry>();

        List<TimeEntry> timeEntries = await database.Table<TimeEntry>().Where(x => x.ShiftID > shiftQuery).OrderByDescending(s => s.TimeStamp).ToListAsync();

        int currentShiftID = 0;
        DateTime? currentShiftDate = null;

        foreach (SiteEntry siteEntry in siteEntries)
        {
            if ( currentShiftID != 0 && currentShiftID != siteEntry.ShiftID)
            {
                tempHistory.SiteEntries = tempShiftEntry;
                tempHistory.ShiftID = currentShiftID;

                shiftHistory.Add(tempHistory);

                tempShiftEntry = new ObservableCollection<SiteEntry>();
                tempHistory = new ShiftHistory();
            }

            tempShiftEntry.Add(siteEntry);

            currentShiftID = (int)siteEntry.ShiftID;
            currentShiftDate = siteEntry.StartTime;
        }

        if ( tempShiftEntry.Count > 0 )
        {
            tempHistory.SiteEntries = tempShiftEntry;
            tempHistory.ShiftID = currentShiftID;

            shiftHistory.Add(tempHistory);
        }

        return shiftHistory;
    }

Here's where it get funky. This data loads correctly when I attempt to load it from the page class:

ShiftHistory.xaml.cs

    public ShiftHistory()
    {
        var vm = new ViewModels.ShiftHistoryViewModel(App.NavigationService);
        vm.DisplayErrorReportPrompt += () => DisplayAlert("Question?", "Report Error on this entry?", "Yes", "No");

        InitializeComponent();
    }

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

        listView.ItemsSource = await App.Database.GetItemsAsync(); // works!
    }

ShiftHistory.xaml

<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" 
             x:Class="PlowTracker.Views.ShiftHistory"
             Title="Last Entries">
    <ScrollView Orientation="Vertical">
        <StackLayout Margin="20" VerticalOptions="StartAndExpand">
            <Button Text="Back" x:Name="btnBack" Command="{Binding BackCommand}"/>
            <ListView x:Name="listView" 
                      Margin="5" 
                      IsGroupingEnabled="true"
                      GroupDisplayBinding="{Binding ShiftID}"
                      ItemsSource="{Binding ShiftHistory}"
              SelectedItem="{Binding Shift}">
                <ListView.GroupHeaderTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout Margin="5,0,0,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
                                <Label Text="{Binding ShiftID, StringFormat='Shift #{0}'}"/>
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.GroupHeaderTemplate>
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <StackLayout Margin="5,0,0,0" Orientation="Horizontal" HorizontalOptions="FillAndExpand">
                                <Label Text="{Binding StartTime, StringFormat='{0:hh:mm tt}'}" VerticalTextAlignment="Center" HorizontalOptions="StartAndExpand" />
                                <Label Text="{Binding EndTime, StringFormat='{0:hh:mm tt}'}" VerticalTextAlignment="Center" HorizontalOptions="CenterAndExpand" />
                            </StackLayout>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    </ScrollView>
</ContentPage>

When I attempt to load it into a property in my View Model I get a blank page each time.

ShiftHistoryViewModel.cs

    private ObservableCollection<ShiftHistory> shiftHistory = new ObservableCollection<ShiftHistory>();
    public ObservableCollection<ShiftHistory> ShiftHistory
    {
        get { return shiftHistory; }
        set
        {
            shiftHistory = value;
            OnPropertyChanged("ShiftHistory");
        }
    }

    private ShiftHistory shift;
    public ShiftHistory Shift
    {
        get { return shift; }
        set
        {
            shift = value;
            OnPropertyChanged("Shift");

            if (shift != null)
                ItemSelectedCommand.Execute(null); // this will pop-up a message to the user if they want to change the entry...
        }
    }

//... (other code removed from brevity)

    private async Task LoadHistory()
    {
        List<ShiftHistory> loadHistory = await App.Database.GetItemsAsync();
        ShiftHistory = new ObservableCollection<ShiftHistory>(loadHistory);
    }

The above load does not error, it steps through an shows the property ShiftHistory containing the data. The ListView however is blank.

Best Answer

Answers

  • someguy65someguy65 Member ✭✭

    @TaylorD said:
    Don't forget to set your BindingContext in your view to the ShiftHistoryViewModel.cs view model.

    Oh boy, its been an very unfortunate day to miss that one. The data now loads, the selected item event isn't firing but I think that's likely a setup issue. Back at it again...

  • someguy65someguy65 Member ✭✭

    Found the issue for the selected item. I was trying to select at the group level instead of the individual item level. All good here, thanks for your help TaylorD

  • TaylorDTaylorD USMember ✭✭✭

    Glad you got it working. Good luck with your project!

Sign In or Register to comment.