ListViews and The Task Store in a Universal Windows App

In my prior articles, I’ve gone through data binding and responsive design techniques for Universal Windows Apps. I bumped into a bug and it bit me badly. Here is the short version. I have a nice new task workflow – I enter some text in the new task area and click on Save and a new task is created. The actual code for this is fairly simple – it’s mostly in the backing file MainPage.xaml.cs:

        private async void SaveTaskButton_Click(object sender, RoutedEventArgs e)
        {
            await store.Create(new TaskItem { Title = NewTaskContent.Text.Trim() });
            NewTaskContent.Text = "";
        }

        private void NewTaskContent_TextChanged(object sender, TextChangedEventArgs e)
        {
            TextBox box = (TextBox)sender;
            SaveTaskButton.IsEnabled = (box.Text.Trim().Length > 0);
        }

I’ve wired this up in the XAML code like this:

        <Grid x:Name="NewTaskPanel" Grid.Row="1" Background="#00abec">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>

            <TextBox x:Name="NewTaskContent" Grid.Column="0" Margin="10,4,10,4" PlaceholderText="Enter a new task..." TextChanged="NewTaskContent_TextChanged"/>
            <Button x:Name="SaveTaskButton" Grid.Column="1" Margin="10,4,10,4" IsEnabled="False" Click="SaveTaskButton_Click">Save</Button>
        </Grid>

However, when I ran this code and added a new task, nothing happened. The new task box cleared, but the new task was not added to the list of tasks as I was expecting. My list, if you will remember, is just a standard List and that is bound to a ListView object using the ItemsSource property. A little bit of investigation – primarily by setting a breakpoint on the Create method in the TaskStore – showed that the task was actually being added to the store. The UI was not being updated.

So what is going wrong?

It turns out that it was a fairly simple problem, and one that seems to bite a lot of people. Stack Overflow to the rescue! The problem is that the ListView expects to be able to observe by registering a notify event handler on the object. My object did not implement that pattern. Fortunately, there is an ObservableCollection – I can make my store a version of the ObservableCollection and then assign the ItemsSource to the TaskStore directly.

First the Models/TaskStore.cs file:

using System;
using System.Collections.ObjectModel;
using System.Threading.Tasks;

// This file includes non-await code for an async interface
// it will execute synchronously - we are ok with that right
// now as the whole system will be replaced with an async
// version later on
#pragma warning disable 1998
namespace QuickStart.UWP.Models
{
    /// <summary>
    /// Implementation of the TaskStore - this is a basic
    /// CRUD type store.
    /// </summary>
    class TaskStore : ObservableCollection<TaskItem>
    {
        public TaskStore()
        {
            Add(new TaskItem { Id = Guid.NewGuid().ToString(), Title = "Task 1" });
            Add(new TaskItem { Id = Guid.NewGuid().ToString(), Title = "Task 2" });
        }

        public async Task Create(TaskItem item)
        {
            item.Id = Guid.NewGuid().ToString();
            this.Add(item);
        }

        public async Task Update(TaskItem item)
        {
            for (var idx = 0; idx < this.Count; idx++)
            {
                if (this.Items[idx].Id.Equals(item.Id))
                {
                    this.Items[idx] = item;
                }
            }
        }

        public async Task Delete(TaskItem item)
        {
            this.Remove(item);
        }

    }
}

Since my new code is an extension of an ObservableCollection, I don’t need to handle the retrieve or the retrieveAll methods – there are methods and properties already defined for this. I did want to create a couple of default tasks and handle Create, Update and Delete explicitly. Delete is just an async version of Remove; Create adds a Guid and Update has to find the right record before updating it.

With this new class, I can update the MainPage.xaml.cs as follows. Firstly, I removed the RefreshItems() method – it is no longer required. As long as I use the ObservableCollection, I can rely on the refresh. In the OnNavigatedTo() method, I just need to assign the TaskStore to the ItemsSource:

        /// <summary>
        /// Refresh the contents of the list when the page is loaded
        /// </summary>
        /// <param name="e"></param>
        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            ListItems.ItemsSource = store;
        }

With this code, I can now add records to my in-memory data store and I can mark them as completed. Now I can get back to the filtering question I was handling for this! In the mean time, here is the code on my GitHub Repository.