Time Tortoise: Time Segments in the UI

Time Segments 2

This is one in a series of articles about Time Tortoise, a Universal Windows Platform app for planning and tracking your work schedule. For more on the development of this app and the ideas behind it, see my Time Tortoise category page.

Earlier this month, I wrote about time segments, a fundamental part of time tracking. In that article, I described the implementation of the database schema, model, and view model for time segments. This week, I’ll be focusing on the user interface.

A Visual Studio Designer Bug?

For a while now I have been working around what seems to be a bug in the Visual Studio designer. I haven’t found a perfect solution (that would probably require a code fix in Visual Studio) but this week I found a better workaround.

The bug in question relates to a XAML feature that allows data from user-defined types to be displayed in a list. For example, the main Time Tortoise page shows a list of activities. Here’s how that list is set up in MainPage.xaml:

  • A ListView control on the page presents a list of clickable rows.
  • The attribute ItemsSource="{x:Bind Main.Activities, Mode=OneWay}" indicates that the ListView is bound to Main.Activities, an ObservableCollection that contains elements of type ActivityViewModel. It is bound in OneWay mode, meaning that the binding target (the ListView) is updated when the binding source (the ObservableCollection) raises an event indicating that it has changed. Since the binding is one-way, we can’t edit activity names directly in the ListView.

Because ActivityViewModel is a user-defined type, the ListView doesn’t automatically know how to display it. That’s where the ItemTemplate comes in. It is defined as follows:

<ListView.ItemTemplate>
    <DataTemplate x:DataType="viewModel:ActivityViewModel">
        <Grid>
            <TextBlock Text="{x:Bind Name, Mode=OneWay}" />
        </Grid>
    </DataTemplate>
</ListView.ItemTemplate>

x:DataType="viewModel:ActivityViewModel" specifies the type for which this template defines the display rules. ActivityViewModel is the type name. viewModel is a namespace name, defined as follows inside the Page element at the top of the .xaml file:

xmlns:viewModel="using:TimeTortoise.ViewModel"

<TextBlock Text="{x:Bind Name, Mode=OneWay}" /> indicates that, of the fields in ActivityViewModel, we only want to display the Name field. Since binding is again one-way, the name will be updated in the TextBlock (the target) whenever it’s updated in the view model Name field (the source).

All of this mostly works fine, except for one problem: the designer seems to get confused when ItemTemplate is used in a multi-project solution set up the way TimeTortoise.sln is. To maximize the benefits of the MVVM pattern, the Time Tortoise solution contains separate projects for the model, view, view model, and data access layer. This means that the ItemTemplate, which is contained on a page in the view project, refers to a second project when it uses a field in ActivityViewModel. Then the view model refers to a third project when it uses fields from the Activity model. It’s that third project that seems to trip up the designer.

Here’s the error message that occurs on the first line of the DataTemplate:

The name "ActivityViewModel" does not exist in the namespace
"using:TimeTortoise.ViewModel".

The error is clearly specific to the designer, because the project compiles and runs fine, and IntelliSense even correctly suggests the view model name. But unfortunately, the error causes the designer to crash with:

Invalid Markup. Check the Error List for more information.

This error makes it impossible to use the graphical designer to edit the page (though editing the XAML directly still works). My initial workaround was to remove the entire <ListView.ItemTemplate> section while using the designer for editing, and then add it back for testing.

When I upgraded Time Tortoise to VS2017, the problem didn’t go away as I had hoped. However, the designer did show the following more useful error message:

Microsoft.MetadataReader.UnresolvedAssemblyException
Type universe cannot resolve assembly: TimeTortoise.Model,
Version=1.0.0.0, Culture=neutral, PublicKeyToken=null.

Sure enough, when I added a reference from TimeTortoise.UWP to TimeTortoise.Model, the designer error disappeared. So even though the View project doesn’t directly reference anything in the Model project (and it shouldn’t, according to MVVM), the designer thinks it needs a direct reference to the Model, presumably because it sees references to a Model class in the ViewModel.

This extra reference is not ideal, since it could lead to unintentional use of Model classes directly in the View. But adding an extra reference in order to use the designer is better than deleting and re-adding code, so I’m sticking with it for now. This designer error seems to be a common one, and Stack Overflow lists many other potential solutions (e.g., in these questions). Since I haven’t tried all of them, it’s possible that a better solution exists.

Saving Parent and Child Objects using Entity Framework

In previous iterations of the app, with only activity names to save and retrieve, the Entity Framework add/save process worked as follows:

  • The user clicks the Add button on the main page. The button is bound to MainViewModel.Add().
  • The Add method creates a new Activity instance, adds it to the local activity collection, and calls IRepository.AddActivity().
  • The AddActivity method calls DbContext.Add(activity). This Add method is implemented by Entity Framework. It tells EF to start tracking the given activity. This means the activity will be inserted into the database when DbContext.SaveChanges() is called.
  • Eventually, the user clicks the Save button to save the activity name. The button is bound to MainViewModel.Save(), which calls IRepository.SaveActivity(), which calls DbContext.SaveChanges(), which updates the database.

The addition of the time segment class creates a parent/child relationship in the object graph. The parent is Activity, and it contains a list of time segments, so TimeSegment is the child class.

Conveniently, Entity Framework knows how to persist object graphs: the EF code used to save an Activity also saves its associated TimeSegment list. No new EF code is required for saving the time segments. Here’s how the process works:

  • The user selects an activity.
  • The user creates a time segment by clicking Start when they start working and Stop when they finish. The StartStop method in MainViewModel creates and updates the time segment in the TimeSegments collection.
  • DbContext.Add(activity) and DbContext.SaveChanges(), as described above, add and save both the Activity and its TimeSegments collection. This works whether the activity was previously saved in the database, or whether it is saved for the first time along with its time segments. The time segments don’t need to be explicitly referenced with DbConext.Add. When a parent activity is added to the context, any child segments are also added.

More Time Segment Features

Now that the basics of time segments are in place, I have a foundation to build other time segment features, including:

  • Showing a live timer when timing is active.
  • Showing duration values for each segment and for the total of all of an activity’s segments.
  • Editing segments (without allowing invalid data to be introduced).
  • Deleting segments.
  • Handling segments correctly when the app exits and restarts while timing is in progress.
  • Preventing multiple segments from being active simultaneously.

That all starts next week.