Skip to main content

DevExpress v24.2 Update — Your Feedback Matters

Our What's New in v24.2 webpage includes product-specific surveys. Your response to our survey questions will help us measure product satisfaction for features released in this major update and help us refine our plans for our next major release.

Take the survey Not interested

Google Calendars

  • 9 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).

Useful resources:

#Limitations

Synchronization between Scheduler and Google calendars has the following limitations:

  • Google calendar Event colors do not automatically synchronize with appointment Labels and Statuses.You can, however, handle the DXGoogleCalendarSync.EventValuesRequested and DXGoogleCalendarSync.AppointmentValuesRequested events to do this manually. See this GitHub example for more information.
  • You can synchronize only Event objects (regular appointments), Task and Reminder objects are not supported.
  • Google users can have separate calendars. Their visibility can be manually toggled to display or hide Events that belong to these calendars.

    Scheduler - Google Multi Calendars

    DXGoogleCalendarSync can manage data from only one calendar at a time - the calendar’s ID is assigned to the component’s DXGoogleCalendarSync.CalendarId property. However, you can manually implement a calendar selector (see the example in this article).

#Tutorial

This tutorial builds a sample WinForms 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 based on the “Scheduling Application” project template.

    Tip

    Use the DevExpress Template Kit to quickly create DevExpress-powered apps that target .NET 8+ based on pre-built project templates. The Scheduling Application template creates a WinForms application with an integrated calendar/scheduler, navigation bar, and Ribbon UI.

    Refer to the following help topic for information on how to download and install the DevExpress Template Kit: Install DevExpress Template Kit.

    Note

    Use the DevExpress Project Template Gallery to create applications based on templates that target the .NET Framework.

  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. Install the following dependency library (NuGet package): Google.Apis.Calendar.v3 1.35.1.1321+
  5. 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 obtain conflict details.

#Examples