Google Calendars

  • 8 minutes to read

DevExpress Scheduler can synchronize its regular, recurring and all-day Appointments (in Google terminology, "Events") with Google Calendars. The synchronization process works in a two-way mode: you cannot only import or export data to the control/Google calendar.

The Scheduler control uses the DXGoogleCalendarSync component to transfer data between a Google calendar and a control storage (both SchedulerDataStorage and its predecessor SchedulerStorage are supported).

In this document:

Useful resources:

Limitations

Currently, synchronization between Scheduler and Google calendars has the following limitations.

Tutorial

The steps below illustrate how to build a sample application that exchanges Appointments/Events between an unbound SchedulerControl and Google calendars.

Scheduler - Google Synch - Sample Thumb

To browse complete sample code and download the Visual Studio project, visit the How To: Synchronize Scheduler Control Appointments with Google Calendar Events GitHub example.

Prerequisites

  1. Start a new Visual Studio project and select the "Scheduling Application" template from the DevExpress Template Gallery.
  2. Create a new Ribbon group and add a regular push button (BarButtonItem) and an edit item (BarEditItem) with the RepositoryItemComboBox editor within.

    Scheduler - Google Sync - Add Ribbon Items

  3. Change the button name to "bbiSynchronize" and RepositoryItem name to "ricbCalendarList" to avoid errors when you copy-and-paste code snippets from this article.
  4. Invoke the SchedulerControl's smart-tag menu and choose "Add DX Google Calendar Synchronizer". This link invokes a confirmation dialog that asks for the permission to install several NuGet packages.

    Scheduler - Google Sync - Add component

    Click "Install" to add the DXGoogleCalendarSync component and all required libraries to the project.

Turn on the Google Calendar API

Open the Google Calendar API .NET Quickstart documentation article and follow steps 1 to 3b to acquire the "credentials.json" file and include it into your project.

Scheduler - Google Sync - Credentials file

Set up the sample

  1. Override the OnLoad method to specify the DXGoogleCalendarSync component's DXGoogleCalendarSync.CalendarService property and log into Google services using the credentials file you downloaded.

    Show Code
    
    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Calendar.v3;
    using Google.Apis.Services;
    using Google.Apis.Util.Store;
    using System;
    using System.IO;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace GCSync {
        public partial class Form1 : RibbonForm {
            UserCredential credential;
            CalendarService service;
            bool allowEventLoad;
    
            public Form1() {
                InitializeComponent();
            }
    
            #region Authorization
            async protected override void OnLoad(EventArgs e) {
                base.OnLoad(e);
                try {
                    this.credential = await AuthorizeToGoogle();
                    this.service = new CalendarService(new BaseClientService.Initializer() {
                        HttpClientInitializer = this.credential,
                        ApplicationName = "GoogleCalendarSyncSample"
                    });
                    this.dxGoogleCalendarSync1.CalendarService = this.service;
                }
                this.allowEventLoad = true; 
                catch (Exception ex) {
                    MessageBox.Show(ex.Message);
                }
            }
    
            async Task<UserCredential> AuthorizeToGoogle() {
                using (FileStream stream = new FileStream("credentials.json", FileMode.Open, FileAccess.Read)) {
                    string credPath = Environment.GetFolderPath(
                        Environment.SpecialFolder.Personal);
                    credPath = Path.Combine(credPath, ".credentials/GoogleSchedulerSync.json");
                    return await GoogleWebAuthorizationBroker.AuthorizeAsync(
                        GoogleClientSecrets.Load(stream).Secrets,
                        new String[] { CalendarService.Scope.Calendar },
                        "user",
                        CancellationToken.None,
                        new FileDataStore(credPath, true));
                }
            }
            #endregion
        }
    }
    
  2. Add the following code to extract the calendar list and populate the combo box editor in the Ribbon with corresponding items. The "UpdateBbiAvailability" method disables the "Sync" button if there is no selected calendar.

    Scheduler - Google Sync - Retrieve calendars

    Show Code
    
    using Google.Apis.Calendar.v3.Data;
    using System.Linq;
    //...
    
    CalendarList calendarList;
    string activeCalendarId;
    
    //...
    
    async Task UpdateCalendarListUI() {
        CalendarListResource.ListRequest listRequest = this.service.CalendarList.List();
        this.calendarList = await listRequest.ExecuteAsync();
        this.ricbCalendarList.Items.Clear();
        foreach (CalendarListEntry item in this.calendarList.Items)
            this.ricbCalendarList.Items.Add(item.Summary);
        if (!String.IsNullOrEmpty(this.activeCalendarId)) {
            CalendarListEntry itemToSelect = this.calendarList.Items.FirstOrDefault(x => x.Id == this.activeCalendarId);
            this.dxGoogleCalendarSync1.CalendarId = this.activeCalendarId;
            if (this.ricbCalendarList.Items.Contains(itemToSelect.Summary)) {
                this.beiCalendarList.EditValue = itemToSelect.Summary;
            }
            else
                this.activeCalendarId = String.Empty;
        }
        UpdateBbiAvailability();
    }
    
    void UpdateBbiAvailability() {
        this.bbiSynchronize.Enabled = !String.IsNullOrEmpty(this.activeCalendarId) && this.allowEventLoad;
    }
    
    async protected override void OnLoad(EventArgs e) {
        //...
            await UpdateCalendarListUI();
            this.allowEventLoad = true;
            UpdateBbiAvailability();
        //...
    }
    
  3. When the combo box shows available calendars, you can handle the item selection, retrieve the selected calendar's ID and pass it to the component DXGoogleCalendarSync.CalendarId property.

    Show Code
    
    ricbCalendarList.SelectedIndexChanged += RicbCalendarList_SelectedIndexChanged;
    
    private void RicbCalendarList_SelectedIndexChanged(object sender, EventArgs e) {
        ComboBoxEdit edit = (ComboBoxEdit)sender;
        string selectedCalendarSummary = (string)edit.SelectedItem;
        CalendarListEntry selectedCalendar = this.calendarList.Items.FirstOrDefault(x => x.Summary == selectedCalendarSummary);
        this.activeCalendarId = selectedCalendar.Id;
        this.dxGoogleCalendarSync1.CalendarId = selectedCalendar.Id;
        UpdateBbiAvailability();
    }
    
  4. To start synchronization, call the DXGoogleCalendarSync.Synchronize method.

    
    bbiSynchronize.ItemClick += BbiSynchronize_ItemClick;
    
    private void BbiSynchronize_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) {
        this.dxGoogleCalendarSync1.Synchronize();
    }
    
  5. Specify the DXGoogleCalendarSync.Storage property to bind Google Calendar to a SchedulerDataStorage or SchedulerStorage object.

    
    async protected override void OnLoad(EventArgs e) {
        //...
        this.dxGoogleCalendarSync1.Storage = schedulerStorage;
        //...
    }
    
  6. The sample application is now ready. At first launch, it will ask you to sign in with any Google account (this does not have to be the same account you used to download the "credentials.json" file).

    Scheduler - Google Sync - Login

    In case of a successful sign-in, user credentials are saved to the "%Documents%\.credentials\GoogleSchedulerSync.json\Google.Apis.Auth.OAuth2.Responses.TokenResponse-user" file.

Identify Appointments

All Google Events have two properties that establish Event identities:

  • ID - an opaque Event identifier that remains constant throughout the Event life cycle;
  • ETag - a hash code that changes with every Event modification.

The DXGoogleCalendarSync component automatically adds two Appointment custom fields (the IPersistentObject.CustomFields collection) to store these identifiers: "gID" and "etag". You can use the DXGoogleCalendarSync.CustomFieldIdPropertyName and DXGoogleCalendarSync.CustomFieldETagPropertyName component properties to change these default custom field names.

If you want to store ID and ETag values in a data source bound to your storage, add corresponding custom field mappings.


schedulerStorage.Appointments.CustomFieldMappings.Add(new AppointmentCustomFieldMapping("gId", "database_ID_field_name"));
schedulerStorage.Appointments.CustomFieldMappings.Add(new AppointmentCustomFieldMapping("etag", "database_ETag_field_name"));

Skip Unwanted Appointments and Events

If for specific Scheduler Appointments no matching Google Calendar Events should be created (or vice versa), handle the DXGoogleCalendarSync.FilterAppointments event.

The sample below illustrates how to skip all appointments that have "test" in their descriptions, and all Google Events with the "tentative" status.

private void GcSyncComponent_FilterAppointments(object sender, FilterAppointmentsEventArgs e) {
    if (e.Appointment!=null && e.Appointment.Description.Contains("test"))
        e.Cancel = true;
    if (e.Event!=null && e.Event.Status == "tentative")
        e.Cancel = true;
}

Resolve Synchronization Conflicts

If you make different edits to both a Scheduler Appointment and a Google Calendar Event, the DXGoogleCalendarSynch component is unable to identify which of these objects holds valid data. To resolve such inconsistencies, the component fires DXGoogleCalendarSync.ConflictDetected events. Handle this event and set its ConflictDetectedEventArgs.GoogleEventIsValid parameter to false if a Scheduler Appointment is valid and should be copied into a Google Calendar.


private void dxGoogleCalendarSync1_ConflictDetected(object sender, ConflictDetectedEventArgs e) {
    if (
        //implement your condition here
        //you can read the e.Appointment and e.Event parameters to compare conflicting objects
        ) {
        e.GoogleEventIsValid = false;
    }
}

If the DXGoogleCalendarSync.ConflictDetected event is not handled, the DXGoogleCalendarSync component always prioritizes Google Events over Scheduler Appointments when conflicts emerge.

Critical conflicts raise a different event, DXGoogleCalendarSync.OnException. Handle this event and read its e.Exception parameter to learn about conflict details.