Editing an Existing To-Do Item
The TodoItemViewModel
can be in one of two modes: creation mode or edit mode. Either a new
item is to be created, or an existing to-do item is being edited. This
is indicated to the view using a VisualState
property, which is of type string
. By default, the viewmodel is in creation mode. When the user taps a to-do shell tile, or a to-do item on the TodoListView
page, the view is placed in edit mode.
If in edit mode, the viewmodel’s LoadItem
method is used to retrieve the TodoItem
with the specified Id
from the ITodoService
. The viewmodel’s TodoDescription
and TodoDueDate
are populated using the retrieved item’s values, as shown in the following excerpt:
TodoItem todoItem;
void LoadItem(int itemId)
{
try
{
todoItem = todoService.GetTodoItem(itemId);
}
catch (KeyNotFoundException)
{
MessageService.ShowError("Item not found.");
}
TodoDescription = todoItem.Description;
TodoDueDate = todoItem.DueDate;
VisualState = "Update";
}
As you see later in this section, the VisualState
property is used by the TodoItemView
page to hide or reveal elements on the page.
When in edit mode, the user also has the ability to delete the to-do item. The DeleteItem
method uses the ITodoService
to perform the data operation. Any shell tiles whose NavigationUri
contains the query string corresponding to the to-do item are also removed. See the following excerpt:
void DeleteItem()
{
if (todoItem == null)
{
throw new InvalidOperationException("Not in edit mode.");
}
todoService.RemoveTodoItem(todoItem);
string tileQueryString = string.Format("{0}={1}",
TaskScheduler.TodoItemIdQueryKey,
todoItem.Id);
ShellTile tile = ShellTile.ActiveTiles.FirstOrDefault(
x => x.NavigationUri.ToString().Contains(tileQueryString));
if (tile != null)
{
tile.Delete();
}
Navigate(todoListUrl);
}
The TodoItemView
page uses the IoC container to resolve the ITodoService
instance. If it has not been defined in the container, a TodoService
is instantiated.
The following excerpt shows the TodoItemView
constructor and fields:
readonly TodoItemViewModel viewModel;
bool initialized;
public TodoItemView()
{
InitializeComponent();
var todoService = Dependency.Resolve<ITodoService, TodoService>();
DataContext = viewModel = new TodoItemViewModel(todoService);
}
The Id
of the TodoItem
is passed as a query string parameter, which causes the view to be placed in edit mode. This is determined in the OnNavigatedTo
method of the view. If the TodoItem Id
has been supplied, the viewmodel’s LoadItemCommand
is executed.
When the LoadItemCommand
completes, the visual state of the view is updated according to the VisualState
property of the viewmodel, as shown in the following excerpt:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
if (initialized)
{
return;
}
initialized = true;
string itemId;
if (NavigationContext.QueryString.TryGetValue("TodoItemId", out itemId))
{
/* Custom commanding infrastructure performs automatic conversion
* from a string to an int. */
viewModel.LoadItemCommand.Execute(itemId);
}
VisualStateManager.GoToState(this, viewModel.VisualState, true);
}
The TodoItemView
page contains a TextBox
to edit the viewmodel’s TodoDescription
property and a Windows Phone Toolkit DatePicker
to edit the TodoDueDate
property.
To force the TextBox
to update the viewmodel when the text changes, the custom UpdateSourceTriggerExtender
is used. This prevents changes to the description from being missed if
the user taps an application bar item without first tapping elsewhere
on the page to lose focus. See the following excerpt:
<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<TextBlock Text="description"
Style="{StaticResource PhoneTextNormalStyle}" />
<!-- UpdateSourceTriggerExtended causes the value
to be updated in the viewmodel as soon as the text changes. -->
<TextBox Text="{Binding TodoDescription, Mode=TwoWay}"
u:UpdateSourceTriggerExtender.UpdateSourceOnTextChanged="True" />
<TextBlock Text="due"
Style="{StaticResource PhoneTextNormalStyle}" />
<toolkit:DatePicker Value="{Binding TodoDueDate, Mode=TwoWay}" />
</StackPanel>
The view contains an AppBar
with an AppBarHyperlinkButton
that links back to the TodoListView
page and AppBarIconButtons
that are bound to the various viewmodel commands, as shown:
<u:AppBar>
<u:AppBarHyperlinkButton
NavigateUri="/TodoList/TodoListView.xaml"
Text="Items"
IconUri="/Images/ApplicationBarIcons/List.png" />
<u:AppBarIconButton
x:Name="Button_Delete"
Command="{Binding DeleteCommand}"
Text="Delete"
IconUri="/Images/ApplicationBarIcons/Delete.png" />
<u:AppBarIconButton
Command="{Binding SaveCommand}"
CommandParameter="False"
Text="Save"
IconUri="/Images/ApplicationBarIcons/Save.png" />
<u:AppBarIconButton
Command="{Binding SaveCommand}"
CommandParameter="True"
Text="Save & Pin"
IconUri="/Images/ApplicationBarIcons/AddTile.png" />
</u:AppBar>
The visibility of the Delete button is determined by the VisualState
property of the viewmodel. When the VisualState
property is equal to Update, the Delete button is shown; if equal to Create, it is collapsed (see Figure 2). See the sample code if you are interested in the visual state group XAML.
FIGURE 2 Creating a new to-do item.
When tapping the Save & Pin button, the app is deactivated and the tile is displayed on the Start Experience (see Figure 3).
FIGURE 3. To-do item shell tile is displayed on the Start Experience.
Tapping the tile returns the user to the TodoItemView
, where the item can be edited or deleted.
When the tile
is overdue, a different image is displayed. Changing the properties of
the tile can be done from your foreground app or from a background
agent, which is demonstrated in the following section.