1
Vote

TimePicker throws InvalidOperationException: Cannot go back when CanGoBack is false

description

I'm writing a very simple alarm application in Windows Phone Silverlight 8.1 (because native Windows Phone 8.1 can't set alarms). I'm using Windows Phone Toolkit for the timepicker.

When a user sets an alarm and locks the phone, after the alarm has been dismissed and unlocks the phone and returns to the app, the next time the user interacts with the timepicker and chooses a time I get the following exception:
System.InvalidOperationException: Cannot go back when CanGoBack is false.
I added a tap handler to the timepicker control that displays a messagebox to report NavigationService.CurrentSource and it's always populated with "/MainPage.xaml" when I tap (initiate) the timepicker control.

The call stack includes DateTimePickerPageBase.OnClosedStoryboardCompleted() and GoBack(). I cannot understand why CanGoBack would be false (especially since the messagebox contents prove that the backstack is populated, right?).

MainPage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using DO_IT_ShiAlarm_LaBeouf.Resources;
using Microsoft.Phone.Scheduler;

namespace DO_IT_ShiAlarm_LaBeouf
{
    public partial class MainPage : PhoneApplicationPage
    {
        // Constants
        const String ALARM_NAME = "DO_IT";
        const String ALARM_CONTENT = "DO IT!";
        const String ALARM_SOUND = "/Assets/Sounds/DO IT.wav";

        // Constructor
        public MainPage()
        {
            InitializeComponent();

        }

        protected override void OnNavigatedTo(NavigationEventArgs e)
        {
            updateUI();
        }

        /// <summary>
        /// updateUI toggles the visibility of some elements, enables or disables other elements, and populates the
        /// alarmInfo textblock depending on whether or not an alarm is set.
        /// 
        /// If an alarm is already set for after the current time, the alarmInfo_panel stackpanel is visible as
        /// well as the alarmInfo textblock which is populated with the beginTime of the currently set alarm. The 
        /// appbarbutton to delete the alarm will be enabled. Other controls will be hidden or disabled.
        /// 
        /// If an alarm is not currently set for after the current time, the alarmInfo_panel stackpanel will be
        /// made visible and the appbarbutton to save the alarm will be enabled. Other controls will be hidden or
        /// disabled.
        /// 
        /// This function is called at OnNavigatedTo and any time an alarm is set or deleted, whenever the user
        /// interacts with the timepicker control.
        /// </summary>
        private void updateUI()
        {
            Alarm existingAlarm = (Alarm)ScheduledActionService.Find(ALARM_NAME);

            if (existingAlarm != null && existingAlarm.BeginTime > DateTime.Now)
            {
                DateTime beginTime = existingAlarm.BeginTime;
                String alarmInfo = String.Format("Alarm will sound at {0:t}.", beginTime);

                alarmInfo_textBlock.Text = alarmInfo;
                alarmInfo_panel.Visibility = Visibility.Visible;
                setAlarm_panel.Visibility = Visibility.Collapsed;
                ((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = false;
                ((ApplicationBarIconButton)ApplicationBar.Buttons[1]).IsEnabled = true;
            }
            else
            {
                // for convenience, set the timepicker to the time of the last set alarm
                //if (existingAlarm != null)
                //{
                //    alarmTimePicker.Value = existingAlarm.BeginTime;
                //}
                alarmInfo_panel.Visibility = Visibility.Collapsed;
                setAlarm_panel.Visibility = Visibility.Visible;
                ((ApplicationBarIconButton)ApplicationBar.Buttons[0]).IsEnabled = true;
                ((ApplicationBarIconButton)ApplicationBar.Buttons[1]).IsEnabled = false;
            }
        }

        private void ApplicationBarDeleteButton_Click(object sender, EventArgs e)
        {
            Alarm existingAlarm = (Alarm)ScheduledActionService.Find(ALARM_NAME);

            if (existingAlarm != null)
            {
                ScheduledActionService.Remove(ALARM_NAME);
            }
            updateUI();
        }

        /// <summary>
        /// The Simple Alarm Clock cannot set alarms more than 24 hours in the future. If the
        /// timepicker value reflects a time before the present, a day is added to the alarm's
        /// BeginTime property.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ApplicationBarSaveButton_Click(object sender, EventArgs e)
        {
            DateTime beginTime = (DateTime)alarmTimePicker.Value;

            if (beginTime < DateTime.Now)
                beginTime = beginTime.AddDays(1);

            DateTime expirationTime = beginTime.AddHours(1);

            RecurrenceInterval recurrence = RecurrenceInterval.None;

            Alarm alarm = new Alarm(ALARM_NAME);
            alarm.Content = ALARM_CONTENT;
            alarm.Sound = new Uri(ALARM_SOUND, UriKind.Relative);
            alarm.BeginTime = beginTime;
            alarm.ExpirationTime = expirationTime;
            alarm.RecurrenceType = recurrence;

            if (ScheduledActionService.Find(ALARM_NAME) != null)
            {
                ScheduledActionService.Remove(ALARM_NAME);
            }
            ScheduledActionService.Add(alarm);

            updateUI();
        }

        /// <summary>
        /// When the user taps on the alarmInfo stackpanel, the time remaining until alarm sound
        /// will be presented in a MessageBox.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void alarmInfo_panel_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            Alarm existingAlarm = (Alarm)ScheduledActionService.Find(ALARM_NAME);

            if (existingAlarm != null)
            {
                String alarmInfo;
                TimeSpan remainingTime = existingAlarm.BeginTime.Subtract(DateTime.Now);

                if (remainingTime.Hours > 0)
                {
                    alarmInfo = String.Format("Alarm will sound in {0} hours and {1} minutes.", remainingTime.Hours, remainingTime.Minutes);
                }
                else if (remainingTime.Minutes > 0)
                {
                    alarmInfo = String.Format("Alarm will sound in {0} minutes.", remainingTime.Minutes);
                }
                else
                {
                    alarmInfo = "Alarm will sound in less than one minute.";
                }

                MessageBox.Show(alarmInfo);
            }
        }

        private void alarmTimePicker_Tap(object sender, System.Windows.Input.GestureEventArgs e)
        {
            MessageBox.Show(NavigationService.CurrentSource.ToString());
        }
    }
}
excerpt from MainPage.xaml:
       <!--ContentPanel - place additional content here-->
        <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
            <StackPanel x:Name="setAlarm_panel" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
                <TextBlock Text="When are you going to" TextWrapping="Wrap" Style="{StaticResource PhoneTextTitle2Style}"/>
                <TextBlock Text="DO IT?" Style="{StaticResource PhoneTextExtraLargeStyle}" TextAlignment="Center" />
                <toolkit:TimePicker x:Name="alarmTimePicker" HorizontalAlignment="Center" VerticalAlignment="Center" Tap="alarmTimePicker_Tap"></toolkit:TimePicker>
            </StackPanel>
            <StackPanel x:Name="alarmInfo_panel" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center" Tap="alarmInfo_panel_Tap">
                <TextBlock x:Name="alarmInfo_textBlock" TextWrapping="Wrap" Text="ShiAlarm is set." Style="{StaticResource PhoneTextTitle2Style}" />
                <TextBlock TextWrapping="Wrap" Text="Tap for time remaining." Style="{StaticResource PhoneTextSubtleStyle}" />
            </StackPanel>
        </Grid>
    </Grid>

comments