Time Tortoise: XAML Input Validation

Time Segment Validation

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.

The typical way to create a time segment in Time Tortoise is to select an activity, start the timer, work on the activity for a while, and then stop the timer. That causes the app to add a time segment to the selected activity, with the appropriate start and end times.

But sometimes you need more control over your time segments. If you’re working on activity A, and someone interrupts you to discuss activity B, you might forget to switch activities. In another instance, you might start timing the wrong activity, and not notice it for a while. Whatever the reason, if you care about timing accuracy (and if you don’t, why are you using a time tracker?) you need a way to manually create, edit, and delete time segments.

This week, I worked on the most basic of these operations, editing an existing time segment.

A Review of XAML Data Binding

Here are the steps you would use to edit a time segment:

  • Select a target activity and time segment.
  • Enter a new start time and/or end time.
  • Save the result.

Since Time Tortoise is a UWP app, accepting input from the user and displaying output to the screen happens through data binding. For example, here’s how data binding is configured to track the current activity:

  • A ListView on the main page is bound to an ObservableCollection of ActivityViewModel objects.
  • The SelectedIndex property of the list view is bound to the integer field SelectedActivityIndex.
  • When the user selects an activity, SelectedActivityIndex is updated (through data binding) to reflect the index of the selected list view row. Code in its setter then updates SelectedActivity, a property of type ActivityViewModel.
  • A TextBox on the main page is bound to SelectedActivity.Name, the name of the current activity, using TwoWay binding. This means the text box contents are updated when the user selects an activity, and the user can update the activity name by typing in the text box. When the user clicks Save, the activity name is then updated in the database through Entity Framework.

This week, I added two data-bound text boxes for updating time segment start and end values. Data binding for these two text boxes works the same way as for the activity name: there’s a ListView for the time segments, bound to an ObservableCollection of TimeSegmentViewModel objects. When you select an activity, the time segment list view displays time segments for that activity. A change to SelectedIndex on the list view keeps a property called SelectedTimeSegment up to date. And the text boxes are bound to the DateTime properties of SelectedTimeSegment.

UWP Input Validation

As soon as I wired up data binding for the start and end text boxes, I discovered that entering an invalid date/time string caused an exception. Since entering an invalid date or time is an easy mistake for the user to make, something clearly has to be fixed in order for the app to be usable.

A common solution to date/time input validation is to use a control that only allows the user to select valid dates and times. If I was targeting mobile devices, I would definitely use this approach. But Time Tortoise is intended for a programmer or other knowledge worker using a physical keyboard. In my experience, date/time selection controls are annoying in that environment, and I wouldn’t use any time tracking app where selection was the only option for entering dates and times.

Another idea is to find a UWP-compatible control that allows both selecting a valid date and time, and entering an arbitrary string to be validated by the control. That may eventually be the best option for Time Tortoise. But the standard XAML date and time picker controls from the Visual Studio Toolbox are oriented towards picking dates and times, not accepting and validating them. So I decided to go with the text box + validation approach.

Windows Presentation Foundation (WPF) data binding includes comprehensive data validation. But after unsuccessfully trying to get the examples from that article to work in my project, I did a bit of searching and found that built-in UWP input validation is only in the early design stages, and doesn’t currently work the same as WPF validation. UWP developers are instead using third-party solutions like Template10.

So I had another decision to make: whether or not to take a dependency on a UWP input validation library. While I’m in favor of adopting open-source components for the project, I decided for now to build my own very simple input validation system.

Simple Input Validation for Data-Bound Fields

Here’s a review of the problem to be solved: The TimeSegment class has a StartTime field and an EndTime field, both of type DateTime. These fields also appear in TimeSegmentViewModel. When the user clicks on a row in the time segment list view, the value of each field from the view model appears in its corresponding text box through data binding. We want to allow the user to edit the date/time information in each textbox, and save it back into the start/end fields.

XAML data binding supports data type conversion, and it looks like it would work for UWP. But since the theme of this week is building things from scratch, I’m going to handle my own conversion in conjunction with my homemade data validation. Migrating to a more standard approach could be a work item for the future, especially as better data validation comes to the UWP platform.

The data conversion for the time segment scenario takes place in the StartTime and EndTime getters and setters in TimeSegmentViewModel. The getters are simple: since the view model uses string (to match the Text property of the TextBox) and the model uses DateTime, we simply call ToString in the getter before returning each value.

In the setter, we need to convert in the other direction. Fortunately, there’s a DateTime.Parse method to convert the string representation of a date/time to an actual DateTime. If Parse can’t interpret the string representation, it throws a FormatException so we need to handle that case.

When WPF data validation detects a problem, it causes a validation message to appear near the field that didn’t validate. This is standard UX behavior in modern web pages as well. While I wait for UWP validation to catch up with WPF, I’m using the simplified approach of writing all of the validation messages to a single TextBlock.

To encapsulate the validation functionality, I created a new view model class called ValidationMessageViewModel, which contains the following:

  • A private ValidationMessage class, with the message itself (e.g., “Please enter a valid start date and time”) and a boolean indicating whether the message should be displayed.
  • SetFieldValid and SetFieldInvalid methods to indicate whether a field has been found valid or invalid.
  • A ValidationMessages property containing all of the active validation messages (for fields that have been found invalid).
  • A SetValidationMessages method to update the consolidated message, based on which fields are marked as invalid.

Like the other Time Tortoise view models, ValidationMessageViewModel inherits from NotificationBase, which means it can participate in data binding. The bound field is a text block on the main page, which binds to ValidationMessages and tells the user which fields need to be fixed.

With this validation view model in place, the StartTime and EndTime setters use the following process:

  • Set the current field to valid (assume it’s valid before doing anything).
  • Inside a try block, try to parse the date/time string in the bound text box.
  • If a FormatException occurs, set the current field to invalid.
  • In the finally block, call SetValidationMessages to refresh the validation messages.

Since ValidationMessages is a bound field, updating its value in the finally block causes the UI to update as well, which presents the user with the current validation messages. If any field contains a value that causes it to be marked as invalid, the corresponding message appears. Once the user corrects the value, that message disappears from the text block.

Next: Adding and Deleting Time Segments

Now that time segment editing is in place, adding the ability to add and delete time segments will be comparatively easy. And the validation infrastructure from this week can also be used to validate other fields, like activity name.