ListViews and The Task Store in a Universal Windows App


In my prior articles, I’ve gone through data bindingand responsive design techniquesfor 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 Createmethod 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.csfile:

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 1998namespace 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.csas 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.