Quickstart: Managing app calendars (XAML)

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

Windows Phone Store apps can use the APIs in the Windows.ApplicationModel.Appointments namespace to create and manage one or more calendars. You can choose to allow your calendars to be displayed by the built-in Calendar app or by other apps on the device that use calendar data. You can also choose to allow users to edit your appointments within the built-in calendar app.

This feature is recommended for apps that host their own calendar data, such as an app that has a web service where users can create appointments. If you only want to display calendar data from other apps, see Quickstart: Reading data from app calendars. Here, we'll show you how to create and configure an app calendar, how to create and modify appointments, and how to track changes made to appointments locally so that you can sync the changes to your server.

Prerequisites

  • We recommend that you be familiar with Microsoft Visual Studio and its associated templates.
  • We recommend that you be familiar with C# development.

Add the Appointment capability to your app manifest

Before you can use any of the app calendar APIs you need to update your app manifest. In Solution Explorer, double-click Package.appxmanfest to open the manifest designer. On the Capabilities tab, check the Appointments capability.

Get an instance of the AppointmentStore object

You get access to the device's calendars through the AppointmentStore class. Get an instance of this object by calling RequestStoreAsync, using the value AppCalendarsReadWrite to indicate that you want read and write access to the calendars that are owned by your app. It's a good idea to save the appointment store to a class variable, because you will need it for other app calendar operations

The first time your app is run, you should create your app calendars and sync the calendar data from your web service. Calling Enable tells the system to track local changes to appointments in your app calendars so you can sync them to your remote service. Reset clears all existing change records.

protected async override void OnNavigatedTo(NavigationEventArgs e)
{
    appointmentStore = await AppointmentManager.RequestStoreAsync(AppointmentStoreAccessType.AppCalendarsReadWrite);

    if (! ApplicationData.Current.LocalSettings.Values.ContainsKey("FirstRun"))
    {
        
        await CheckForAndCreateAppointmentCalendars();
        await SyncAppointmentsFromAppServer();

        appointmentStore.ChangeTracker.Enable();
        appointmentStore.ChangeTracker.Reset();

        ApplicationData.Current.LocalSettings.Values["FirstRun"] = false;
    }
}

Create your app calendars

Because you requested the AppointmentStore using the AppCalendarsReadWrite option, calling FindAppointmentCalendarsAsync will only return calendars owned by your app. You can get an instance of a calendar you are looking for if it is in the returned list. If it's not, you can create a new app calendar by calling CreateAppointmentCalendarAsync and passing in the name of your new calendar.

Set OtherAppReadAccess to specify whether other apps can view the data from your app calendar. Set OtherAppWriteAccess to specify whether appointments on your calendar can be modified by the built-in Calendar app. The SummaryCardView property determines whether your app or the built-in Calendar app will be launched to display the summary card for appointments in the calendar app.

Call SaveAsync to save the changes to your app calendar.

async public Task CheckForAndCreateAppointmentCalendars()
{

    IReadOnlyList<AppointmentCalendar> appCalendars = 
        await appointmentStore.FindAppointmentCalendarsAsync(FindAppointmentCalendarsOptions.IncludeHidden);

    AppointmentCalendar appCalendar = null;

    
    // Apps can create multiple calendars. This example creates only one.
    if (appCalendars.Count == 0)
    {
        appCalendar = await appointmentStore.CreateAppointmentCalendarAsync("Example App Calendar");

    }else
    {
        appCalendar = appCalendars[0];
    }
    

    appCalendar.OtherAppReadAccess = AppointmentCalendarOtherAppReadAccess.Full;
    appCalendar.OtherAppWriteAccess = AppointmentCalendarOtherAppWriteAccess.SystemOnly;

    // This app will show the details for the appointment. Use System to let the system show the details.
    appCalendar.SummaryCardView = AppointmentSummaryCardView.App;

    await appCalendar.SaveAsync();

    currentAppCalendar = appCalendar;
}

Create an appointment

Typically, an app will retrieve one or more appointments from its web service and then create a local appointment by creating a new Appointment object and then calling SaveAppointmentAsync on the AppointmentCalendar object representing the calendar where the appointment should be saved.

async public Task CreateNewAppointment(MyRemoteAppointment myRemoteAppointment)
{
    Appointment newAppointment = new Appointment();

    newAppointment.Subject = myRemoteAppointment.Title;
    newAppointment.StartTime = myRemoteAppointment.StartTime;
    newAppointment.Duration = myRemoteAppointment.Length;
    newAppointment.Location = myRemoteAppointment.Place;
    newAppointment.RoamingId = myRemoteAppointment.ID;

    //save appointment to calendar
    await currentAppCalendar.SaveAppointmentAsync(newAppointment);
}

Modify an appointment

To modify an existing appointment, call GetAppointmentAsync with the LocalId of the appointment to be modified. If you want to modify a single instance of a recurring appointment, you can use GetAppointmentInstanceAsync. Use GetAppointmentCalendarAsync to get an instance of the calendar to which the appointment belongs and then call SaveAppointmentAsync to save the changes to the appointment.

public async void ModifyAppointment(String localAppointmentId, DateTimeOffset? originalStartTime)
{
    Appointment targetAppt;

    if (originalStartTime.HasValue)
    {
        // Call GetAppointmentAsync to get the target appointment
        targetAppt = await appointmentStore.GetAppointmentAsync(localAppointmentId);
    }
    else
    {
        // Call GetAppoinmentInstance to get an instance of a recurring appointment
        // Note that you must supply the OriginalStartTime for the instance, not the StartTime.
        targetAppt = await appointmentStore.GetAppointmentInstanceAsync(localAppointmentId, originalStartTime.Value);
        if(targetAppt == null)
        {
            // An instance wasn't found at this time
            return;
        }
    }

    // this example simply modifies the subject of the appointment or appointment instance
    targetAppt.Subject = "new subject";

    //save the modified appointment
    AppointmentCalendar targetCalendar = 
        await appointmentStore.GetAppointmentCalendarAsync(targetAppt.CalendarId);

    await targetCalendar.SaveAppointmentAsync(targetAppt);
}

Track local changes to app calendar appointments

If you enabled editing of appointments by the system calendar, you should also enable and process change tracking for your app calendar, you should periodically get the list of local changes to your appointments and send the changes to your remote server to keep the remote data in sync. Get an instance of the AppointmentStoreChangeReader for your app by calling GetChangeReader. ReadBatchAsync will get all of the changes that have occurred across all calendars owned by your app since the last time Reset was called. Loop through all of the AppointmentStoreChange objects in the returned list, determine what type of change occurred, and then upload the change to your server. It's a good idea to queue up a batch of changes and send them all at once rather than sending them one at a time.

When you have synced all of the changes, call AcceptChangesThrough to set change tracking to the last synced change.

async public void RevisionTracking()
{
    AppointmentStoreChangeReader changeReader = appointmentStore.ChangeTracker.GetChangeReader();

    IReadOnlyList<AppointmentStoreChange> appointmentChanges;

    AppointmentStoreChange lastChangeChecked = null;

    do
    {
        appointmentChanges = await changeReader.ReadBatchAsync();

        foreach (AppointmentStoreChange change in appointmentChanges)
        {
            switch (change.ChangeType)
            {
                case AppointmentStoreChangeType.AppointmentCreated:
                    QueueNewAppointmentForUpload(change.Appointment);
                    break;
                case AppointmentStoreChangeType.AppointmentModified:
                    QueueAppointmentModificationForUpload(change.Appointment);
                    break;
                case AppointmentStoreChangeType.AppointmentDeleted:
                    QueueAppointmentDeletedForUpload(change.Appointment.RoamingId);
                    break;
                case AppointmentStoreChangeType.ChangeTrackingLost:
                    await SyncAppointmentsFromAppServer();
                    appointmentStore.ChangeTracker.Reset();
                    return;
            }

            lastChangeChecked = change;
        }

    } while (appointmentChanges.Count != 0);

    if (lastChangeChecked != null)
    {
        UploadQueuedChanges();
        changeReader.AcceptChangesThrough(lastChangeChecked);
    }
}

Setting up recurrence with a time zone

The StartTime property of an Appointment represents a specific point in universal time, so setting the TimeZone property of an AppointmentRecurrence doesn't change what time the appointment will occur. The TimeZone property can be used by your app to simply display the time zone associated with the recurrence to the user, or you can use it to translate the start time into local time before you display it. The following example shows how to create an appointment in a time zone. Note that the appointment is created with a start time that is in universal time, calculated using the Calendar class. The time zone is then set on the recurrence.

async public void NewRecurrenceInTimeZone(String CalendarId, string TimeZoneString)
{

    var cal = new Windows.Globalization.Calendar();

    // An example TimeZone string is "Asia/Shanghai".
    cal.ChangeTimeZone(TimeZoneString);
    cal.Year = 2013;
    cal.Month = 7;
    cal.Day = 25;
    cal.Hour = 7;
    cal.Minute = 30;

    var appt = new Appointment();
    appt.StartTime = cal.GetDateTime();

    var recurrence = new AppointmentRecurrence();
    recurrence.Unit = AppointmentRecurrenceUnit.Monthly;
    recurrence.Interval = 1;
    recurrence.Day = (uint)cal.Day;
    recurrence.TimeZone = TimeZoneString;
    appt.Recurrence = recurrence;

    AppointmentCalendar calendar = await appointmentStore.GetAppointmentCalendarAsync(CalendarId);
    await calendar.SaveAppointmentAsync(appt);
}

Handling activation for showing appointment details

If you create an app calendar with the SummaryCardView set to App, your app will be activated if an app calls ShowAppointmentDetailsAsync for an appointment on your app calendar. To let the system know that your app supports this, you must add the following extension to your Package.appxmanfest file.

<m2:Extension Category="windows.appointmentsProvider">
  <m2:AppointmentsProvider>
    <m2:AppointmentsProviderLaunchActions>
      <m3:LaunchAction Verb="showAppointmentDetails" />
    </m2:AppointmentsProviderLaunchActions>
  </m2:AppointmentsProvider>
</m2:Extension>

Next you need to override the OnActivated method in your app.xaml.cs file. This method is called when the system launches your app to show appointment details. This method has to perform the basic initialization of your app's frame, which can be copied from the OnLaunched implementation included in the default project template.

To handle appointment activation, first you should check the ActivationKind passed in by the system. If this is AppointmentsProvider, then you should cast the ActivatedEventArgs object to IAppointmentsProviderActivatedEventArgs and check the AppointmentsProviderLaunchActionVerbs property. If the value is ShowAppointmentDetails, then cast the args object as AppointmentsProviderShowAppointmentDetailsActivatedEventArgs. Then you can get the LocalId, the InstanceStartDate, and the RoamingId of the appointment to be shown. Note that InstanceStartDate will be null for a non-recurring appointment.

Finally, navigate your app to the page you use to show appointment details and pass the data about the appointment as parameters.

protected override void OnActivated(IActivatedEventArgs args)
{
    AppointmentsProviderShowAppointmentDetailsActivatedEventArgs detailsArgs = null;
    if (args.Kind == ActivationKind.AppointmentsProvider)
    {
        var apArgs = (IAppointmentsProviderActivatedEventArgs)args;

        if (apArgs.Verb == AppointmentsProviderLaunchActionVerbs.ShowAppointmentDetails)
        {
            detailsArgs = args as AppointmentsProviderShowAppointmentDetailsActivatedEventArgs;
        }
    }

    if (null == detailsArgs)
    {
        Application.Current.Exit();
    }

#if DEBUG
    if (System.Diagnostics.Debugger.IsAttached)
    {
        this.DebugSettings.EnableFrameRateCounter = true;
    }
#endif

    Frame rootFrame = Window.Current.Content as Frame;

    // Do not repeat app initialization when the Window already has content,
    // just ensure that the window is active
    if (rootFrame == null)
    {
        // Create a Frame to act as the navigation context and navigate to the first page
        rootFrame = new Frame();

        // TODO: change this value to a cache size that is appropriate for your application
        rootFrame.CacheSize = 1;

        if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
        {
            // TODO: Load state from previously suspended application
        }

        // Place the frame in the current Window
        Window.Current.Content = rootFrame;
    }

    if (rootFrame.Content == null)
    {
        // Removes the turnstile navigation for startup.
        if (rootFrame.ContentTransitions != null)
        {
            this.transitions = new TransitionCollection();
            foreach (var c in rootFrame.ContentTransitions)
            {
                this.transitions.Add(c);
            }
        }

        rootFrame.ContentTransitions = null;
        rootFrame.Navigated += this.RootFrame_FirstNavigated;

        string param =
            "localid=" + detailsArgs.LocalId + "&" +
            "ost=" + detailsArgs.InstanceStartDate + "&" +
            "roamingid=" + detailsArgs.RoamingId;

        if (!rootFrame.Navigate(typeof(AppointmentDetails), param))
        {
            throw new Exception("Failed to create appointment page");
        }
    }

    // Ensure the current window is active
    Window.Current.Activate();
}

Note  If your app is running when it is activated through the appointments provider contract, the existing instance of your app is terminated and a new instance of your app is launched to handle the contract.

 

Summary and next steps

Now you have a basic understanding of how to create and manage app calendars.