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:
ListViewon the main page is bound to an
SelectedIndexproperty of the list view is bound to the integer field
- When the user selects an activity,
SelectedActivityIndexis 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
TextBoxon the main page is bound to
SelectedActivity.Name, the name of the current activity, using
TwoWaybinding. 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
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
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
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
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
To encapsulate the validation functionality, I created a new view model class called
ValidationMessageViewModel, which contains the following:
- A private
ValidationMessageclass, with the message itself (e.g., “Please enter a valid start date and time”) and a boolean indicating whether the message should be displayed.
SetFieldInvalidmethods to indicate whether a field has been found valid or invalid.
ValidationMessagesproperty containing all of the active validation messages (for fields that have been found invalid).
SetValidationMessagesmethod 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
EndTime setters use the following process:
- Set the current field to valid (assume it’s valid before doing anything).
- Inside a
tryblock, try to parse the date/time string in the bound text box.
- If a
FormatExceptionoccurs, set the current field to invalid.
- In the
SetValidationMessagesto refresh the validation messages.
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.