New to Telerik UI for WinForms? Download free 30-day trial

How to Save Custom Backgrounds in RadScheduler when Exporting to ICal

Environment

Product Version Product Author
2022.1.222 RadScheduler for WinForms Desislava Yordanova

Description

RadScheduler offers a predefined list of available statuses and backgrounds for its appointments. There are cases when none of the available options is suitable for your specific scenario. You can add your custom status or background. However, the custom added backgrounds or statuses are not automatically saved when exporting to ICal.

SchedulerICalendarExporter is purposed to export these statuses and backgrounds that are available by default. For any custom added statuses/backgrounds, it is necessary to adopt the SchedulerICalendarExporter and cover these cases. This article demonstrates a sample approach how to do it.

Solution

Let's start with providing some information about the default export logic that the SchedulerICalendarExporter class offers:

WriteAdditionalDataForAppointment method


protected virtual void WriteAdditionalDataForAppointment(IEvent appointment, CalObject calObject)
{
    if (appointment == null)
    {
        return;
    }

    if (appointment.Description != null)
    {
        calObject.AddProperty(CalendarConstants.Description, CalHelper.CalTextEncode(appointment.Description));
    }

    object idObject = appointment.UniqueId.KeyValue;
    string idString = (idObject != null) ? idObject.ToString() : string.Empty;
    calObject.ReplaceProperty(CalendarConstants.UniqueId, idString);

    if (appointment.Location != null)
    {
        calObject.AddProperty(CalendarConstants.Location, appointment.Location);
    }

    calObject.AddProperty(CalendarConstants.MSStatus, CalHelper.GetAppointmentStatusName((AppointmentStatus)appointment.StatusId));

    calObject.AddProperty(CalendarConstants.Background, CalHelper.GetAppointmentBackgroundName((AppointmentBackground)appointment.BackgroundId));

    IRemindObject remindObject = appointment as IRemindObject;
    if (remindObject != null && remindObject.Reminder.HasValue && !remindObject.Dismissed)
    {
        CalObject alarmObject = new CalObject(CalendarConstants.Alarm);
        alarmObject.AddProperty(CalendarConstants.Action, CalendarConstants.DisplayValue);
        alarmObject.AddProperty(CalendarConstants.Description, "Reminder");
        alarmObject.AddProperty(CalendarConstants.Trigger, CalHelper.GetReminderString(remindObject.Reminder.Value));
        calObject.Children.Add(alarmObject);
    }
}

GetAppointmentStatusName method


public static string GetAppointmentStatusName(AppointmentStatus status)
{
    string name = "BUSY";
    switch (status)
    {
        case AppointmentStatus.Free:
            name = "FREE";
            break;
        case AppointmentStatus.Tentative:
            name = "TENTATIVE";
            break;
        case AppointmentStatus.Unavailable:
            name = "UNAVAILABLE";
            break;
    }

    return name;
}

GetAppointmentBackgroundName method


public static string GetAppointmentBackgroundName(AppointmentBackground background)
{
    string name = "BUSY";
    switch (background)
    {
        case AppointmentBackground.Anniversary:
            name = "ANNIVERSARY";
            break;
        case AppointmentBackground.Birthday:
            name = "BIRTHDAY";
            break;
        case AppointmentBackground.Business:
            name = "BUSINESS";
            break;
        case AppointmentBackground.Important:
            name = "IMPORTANT";
            break;
        case AppointmentBackground.MustAttend:
            name = "MUSTATTEND";
            break;
        case AppointmentBackground.NeedsPreparation:
            name = "NEEDSPREPARATION";
            break;
        case AppointmentBackground.Personal:
            name = "PERSONAL";
            break;
        case AppointmentBackground.PhoneCall:
            name = "PHONECALL";
            break;
        case AppointmentBackground.TravelRequired:
            name = "TRAVELREQUIRED";
            break;
        case AppointmentBackground.Vacation:
            name = "VACATION";
            break;
    }

    return name;
}

AppointmentStatus


public enum AppointmentStatus
{
    /// <summary>
    /// Specifies that the status of an appointment is Free
    /// </summary>
    Free = 1,
    /// <summary>
    ///  Specifies that the status of an appointment is Busy
    /// </summary>
    Busy,
    /// <summary>
    ///  Specifies that the status of an appointment is Unavailable
    /// </summary>
    Unavailable,
    /// <summary>
    ///  Specifies that the status of an appointment is Tentative
    /// </summary>
    Tentative
}

AppointmentBackground


public enum AppointmentBackground
{
    /// <summary>
    /// Specifies that no background fill is drawn
    /// </summary>
    None = 1,
    /// <summary>
    /// Specifies that the important type of background fill should be drawn
    /// </summary>
    Important,
    /// <summary>
    /// Specifies that the business type background fill should be drawn
    /// </summary>
    Business,
    /// <summary>
    /// Specifies that the personal type of background fill should be drawn
    /// </summary>
    Personal,
    /// <summary>
    /// Specifies that vacation type of background fill should be drawn
    /// </summary>
    Vacation,
    /// <summary>
    /// Specifies that the MustAttend type of background fill should be drawn
    /// </summary>
    MustAttend,
    /// <summary>
    /// Specifies that the TravelRequired type of background fill should be drawn
    /// </summary>
    TravelRequired,
    /// <summary>
    /// Specifies that the NeedsPreparation type of background fill should be drawn
    /// </summary>
    NeedsPreparation,
    /// <summary>
    /// Specifies that the Birthday type of background fill should be drawn
    /// </summary>
    Birthday,
    /// <summary>
    /// Specifies that the Anniversary type of background fill should be drawn
    /// </summary>
    Anniversary,
    /// <summary>
    /// Specifies that the PhoneCall type of background fill should be drawn
    /// </summary>
    PhoneCall
}

In the WriteAdditionalDataForAppointment method both, StatusId and BackgroundId are converted to AppointmentStatus and AppointmentBackground respectively. If you use a custom status/background, the ID wouldn't be available in the respective enum. Hence, the default value will be exported. This behavior is expected.

If you want to export the custom status/background, it would be necessary to create a derivative of the SchedulerICalendarExporter class and override its WriteAdditionalDataForAppointment method. You can refer to the above default logic and adjust this part of the method that adds the status/background to the cal object according to the custom requirement you have.

Custom SchedulerICalendarExporter


public class CustomSchedulerICalendarExporter : SchedulerICalendarExporter
{
    private RadScheduler Scheduler;

    public CustomSchedulerICalendarExporter(RadScheduler radScheduler)
    { 
        this.Scheduler = radScheduler;
    }
    protected override void WriteAdditionalDataForAppointment(IEvent appointment, CalObject calObject)
    {
        if (appointment == null)
        {
            return;
        }

        if (appointment.Description != null)
        {
            calObject.AddProperty("DESCRIPTION", CalTextEncode(appointment.Description));
        }

        object idObject = appointment.UniqueId.KeyValue;
        string idString = (idObject != null) ? idObject.ToString() : string.Empty;
        calObject.AddProperty("UID", idString);

        if (appointment.Location != null)
        {
            calObject.AddProperty("LOCATION", appointment.Location);
        }

        bool exists = Enum.IsDefined(typeof(AppointmentStatus), appointment.StatusId);
        if (exists)
        {
            calObject.AddProperty(@"X-MICROSOFT-CDO-BUSYSTATUS", CalHelper.GetAppointmentStatusName((AppointmentStatus)appointment.StatusId));
        }
        else
        { 
            calObject.AddProperty(@"X-MICROSOFT-CDO-BUSYSTATUS",GetAppointmentStatusName(appointment.StatusId));
        }

        exists= Enum.IsDefined(typeof(AppointmentBackground), appointment.BackgroundId);
        if (exists)
        {
            calObject.AddProperty("BACKGROUND", CalHelper.GetAppointmentBackgroundName((AppointmentBackground)appointment.BackgroundId));
        }
        else
        { 
           calObject.AddProperty("BACKGROUND", GetAppointmentBackgroundName(appointment.BackgroundId));
        }

        IRemindObject remindObject = appointment as IRemindObject;
        if (remindObject != null && remindObject.Reminder.HasValue && !remindObject.Dismissed)
        {
            CalObject alarmObject = new CalObject("VALARM");
            alarmObject.AddProperty("ACTION", "DISPLAY");
            alarmObject.AddProperty("DESCRIPTION", "Reminder");
            alarmObject.AddProperty("TRIGGER", CalHelper.GetReminderString(remindObject.Reminder.Value));
            calObject.Children.Add(alarmObject);
        }
    }

    private string GetAppointmentBackgroundName(int background)
    {
       foreach (AppointmentBackgroundInfo b in this.Scheduler.Backgrounds)
        {
            if (b.Id ==background)
            {
                return b.DisplayName;
            }
        }
        return "default background";
    }

    private string GetAppointmentStatusName(int status)
    {
        foreach (AppointmentStatusInfo s in this.Scheduler.Statuses)
        {
            if (s.Id ==status)
            {
                return s.DisplayName;
            }
        }
        return "default status";
    }

    internal string CalTextEncode(string value)
    {
        string v = NormalizeString(value);
        v = v.Replace(@"\", @"\\");
        v = v.Replace("\n", @"\n");
        v = v.Replace(";", @"\;");
        v = v.Replace(",", @"\,");

        return v;
    }

    private static string NormalizeString(string value)
    {
        string v = value;

        v = v.Replace("\r\n", @"\n");
        v = v.Replace("\r", @"\n");
        v = v.Replace("\n", @"\n");
        v = v.Replace(@"\n", "\n");
        v = v.Replace(@"\N", "\n");
        v = v.Replace(@"\;", ";");
        v = v.Replace(@"\,", ",");
        v = v.Replace("\\\"", "\"");
        v = v.Replace(@"\\", @"\");

        return v;
    }
}

Using the Custom SchedulerICalendarExporter


string fileName = @"schedule.ics";
using (FileStream stream = File.Create(fileName))
{
    this.radScheduler1.Export(stream, new CustomSchedulerICalendarExporter(this.radScheduler1));
}

Importing ICal data

Once the scheduler data is exported, it would be good to have it back by importing at a later moment. This requires creating a derivative of the SchedulerICalendarImporter class and overriding its ApplyAdditionalData method. It is necessary to find the correct ID of the custom status/background and apply it to the appointment:

Custom SchedulerICalendarImporter


public class CustomSchedulerICalendarImporter : SchedulerICalendarImporter
{
    private RadScheduler Scheduler;

    public CustomSchedulerICalendarImporter(RadScheduler radScheduler)
    { 
        this.Scheduler = radScheduler;
    }

    protected override void ApplyAdditionalData(IEvent appointment, CalObject calObject)
    {
        base.ApplyAdditionalData(appointment, calObject); 

        CalProperty prop = calObject[@"X-MICROSOFT-CDO-BUSYSTATUS"];
        if (prop != null)
        {
            appointment.StatusId = GetAppointmentStatus(CalProperty.ToText(prop));
        }

        prop = calObject["BACKGROUND"];
        if (prop != null)
        {
            appointment.BackgroundId = GetAppointmentBackground(CalProperty.ToText(prop));
        }
    }

    private int GetAppointmentBackground(string background)
    {
         foreach (AppointmentBackgroundInfo b in this.Scheduler.Backgrounds)
        {
            if (b.DisplayName == background)
            {
                return b.Id;
            }
        }
        return 0;
    }

    private int GetAppointmentStatus(string statusName)
    {
        foreach (AppointmentStatusInfo s in this.Scheduler.Statuses)
        {
            if (s.DisplayName == statusName)
            {
                return s.Id;
            }
        }
        return 0;
    }
}

Using the Custom SchedulerICalendarImporter


string fileName = @"schedule.ics";
using (FileStream stream = File.OpenRead(fileName))
{
    this.radScheduler1.Import(stream, new CustomSchedulerICalendarImporter(this.radScheduler1));
}