PrintableCal API

Overview

The PrintableCal API is provided as a free library compatible with .NET Framework 4.0 or later. You can code PrintableCal extensions using C#, VB.NET, or managed C++. The PrintableCal API utilizes the Managed Extensibility Framework (MEF) to enable PrintableCal to be extended to support additional calendar data sources and template styles. All the calendar data sources and template styles that come installed with PrintableCal were created using the PrintableCal API and MEF. When PrintableCal loads, it will search its Addins folder (using the MEF composition engine) to find extensions. As a developer, you can create your own extensions to PrintableCal, enabling PrintableCal to generate new calendar styles or import data from sources that aren't supported by the default installation.

Download the PrintableCal API

You can download the API by clicking the button below. The API includes the required assemblies as well as documentation and examples built using Visual Studio 2015.

Version 1.9.4 - October 15, 2016

Template Styles

Throughout this documentation we'll refer to "template styles". These are not calendar templates. Rather, they are styles or "categories" of calendar templates. End-users can define and customize their own calendar templates using the editing features of Excel, without needing to know how to write code. When a calendar template is saved, it is assigned a template style, such as "Month" or "Year". These template styles were created by VueSoft and included in the PrintableCal installation. As a developer, the PrintableCal API can be used to create entirely new template styles that VueSoft hasn't thought to include in PrintableCal. 

Want a template that shows calendar data in a layout or style that isn't possible with any of the provided template styles? That's where the PrintableCal API comes in. You can extend PrintableCal to support just about any type of calendar template.

Calendar Data Sources

Calendar data sources are files, websites, databases, programs, etc., that PrintableCal can be configured to access for importing the events, tasks, and notes that are included in generated calendars. As with template styles, VueSoft includes support for a number of calendar data sources with the PrintableCal installation. However, many businesses keep calendar data in proprietary databases or non-standard files that VueSoft doesn't know about and couldn't add to PrintableCal. If you can access your calendar data from managed .NET code, then you can extend PrintableCal to support importing that data.

Basic Steps For Extending PrintableCal

You can extend PrintableCal by following these basic steps...

  1. Download the PrintableCal API. The API includes the required assemblies, documentation, and some example projects to help you get started.
  2. Create a new project. Add references to the assemblies Vuesoft.PrintableCal.Common.dll and Vuesoft.PrintableCal.Interfaces.dll.
  3. Write a class for your extension that implements the appropriate interface defined by the PrintableCal API.
    1. For a calendar data source, the class will need to implement the Vuesoft.PrintableCal.Interfaces.ICalendarSource interface. 
    2. For a template style, the class will need to implement the Vuesoft.PrintableCal.Interfaces.ITemplate interface. The abstract Vuesoft.PrintableCal.Common.TemplateBase class implements most of this interface and can be used as the base class for template style add-ins.
  4. Add Export and ExportMetadata attributes to your class. These attributes are defined in the System.ComponentModel.Composition namespace. The MEF composition engine will look for types with these attributes when determining which extensions are available to PrintableCal. 
    1. A calendar data source requires a metadata key of SourceName
      [Export(typeof(ICalendarSource))]
      [ExportMetadata("SourceName", "Your Calendar Data Source Name Goes Here")]
      public class YourDataSourceClassName : ICalendarSource
      { ... } 
    2. A template style required a metadata key of Style.
      [Export(typeof(ITemplate))]
      [ExportMetadata("Style", "Your Template Style Name Goes Here")]
      public class YourTemplateStyleClassName : TemplateBase
      {
         ...
      }
      
  5. Change the output folder for your project to be in the folder where PrintableCal searches for add-ins. On Windows 7 and 8, this is under C:\Users\YourLoginName\AppData\Roaming\PrintableCal\Addins
  6. Build your project and then start Word or Excel. PrintableCal will automatically load your extension. There are no configuration files to mess with, nothing to change in the registry, or any other hacks to make it work. This is the beauty of MEF.

Creating a Calendar Data Source Add-In

Calendar data source add-ins consist of a class implementing the Vuesoft.PrintableCal.Interfaces.ICalendarSource interface and a Form to provide the GUI for the user to add calendars of the source type. See the calendar data source project in the downloaded API for a working example.

The following properties and methods need to be implemented by the add-in developer. More detailed documentation is included in the API download. 

  • Create - Creates and returns a new instance of the calendar data source.
  • CalendarName - Gets or sets the calendar name. This is the name selected by the user or acquired from the external data source.
  • CalendarId - Gets or sets the calendar identifier. This identifier must be unique and not used by other calendar data sources.
  • IsOnline - Gets whether the calendar data source is online (requires an internet connection). Return false if the calendar data source is located on a local drive or network.
  • DataLocation - Gets the file path or URL of the calendar data source.
  • CalendarOptions - Gets a collection of options applicable to the calendar data source, or null if there aren't any.
  • CalendarIcon - Gets an icon for the calendar data source. It should be sized 16x16 pixels.
  • Items - Gets schedule items associated with the calendar data source. This property isn't valid until the InitializeItems method has been commanded by PrintableCal.
  • ItemAppearance - Gets or sets the appearance settings to be used for items associated with this calendar data source.
  • ItemPrefix - Gets or sets the prefix (if any) to be included on all events, tasks, or notes associated with the data source when generating a calendar. The prefix can be entered by the user.
  • IsSelected - Gets or sets whether the data source was selected by the user for including in generated calendars.
  • ShowAddCalendarForm - Shows a dialog that allows the user to add new calendar data sources. The dialog will need to be defined within the calendar data source add-in.
  • InitializeItems - Initializes this calendar data source with all items occurring between the specified start and end dates. This method is commanded by PrintableCal before generating a calendar.
  • SaveSettings - Saves user-entered settings unique to the calendar data source, such as authentication information.
  • LoadSettings - Loads previously saved settings that are unique to the calendar data source, such as authentication information.

If your calendar data source is iCalendar (ics) file-based, most of the heavy-lifting (such as ICS file parsing) is already implemented in the Vuesoft.PrintableCal.Common namespace. You can inherit from the Vuesoft.PrintableCal.Common.CalendarSourceICS class and override the default implementation where needed.

Creating a Template Style Add-In

Template style add-ins inherit from the Vuesoft.PrintableCal.Common.TemplateBase class, which provides a default implementation of most the required properties and methods, except for the following.

  • Icon - Gets the icon associated with the template type. The icon should be sized 16x16 pixels.
  • Generate - Generates a calendar to a specified workbook, using specified calendar data sources. This is where most of the template code will be located.

See the template style project in the downloaded API for a working example.

Example Code

The API includes a complete example that can be compiled and loaded into PrintableCal. The sample code below shows how a calendar data source can be implemented. We added a lot of comments to help explain how it works.

AccessDbCalendarSource.cs

//-----------------------------------------------------------------------------
// Copyright 2014 by VueSoft LLC. All rights reserved.
// http://www.printablecal.com
//-----------------------------------------------------------------------------
//
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, 
//    this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
// 
// 3. Neither the name of "PrintableCal" nor the names of its contributors may 
//    be used to endorse or promote products derived from this software without
//    specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Data;
using System.Data.OleDb;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using Vuesoft.PrintableCal.Common;
using Vuesoft.PrintableCal.Interfaces;

namespace AccessDbCalendarSource
{
   /// <summary>
   /// This class provides a simple example of how PrintableCal can be extended to support additional data sources. In this
   /// example, the data source comes from an Access database. The same basic concepts would apply to any other type of data
   /// source. 
   /// <remarks>
   /// When defining a custom data source, make sure to add the Export and ExportMetadata attributes. These are required for
   /// PrintableCal to recognize the type is an extension. 
   /// </remarks>
   /// </summary>
   [Export(typeof(ICalendarSource))]
   [ExportMetadata("SourceName", AccessDbCalendarSource.SOURCE_NAME)]
   public class AccessDbCalendarSource : ICalendarSource
   {
      /// <summary>
      /// The source name. This name is used in the PrintableCal calendar list and when comparing calendar data sources to
      /// determine which ones are implemented by this class. The name must be unique!
      /// </summary>
      public const string SOURCE_NAME = "Example Access Database";

      /// <summary>
      /// The calendar name. It doesn't need to be unique. It comes from the CalendarName field in the Calendars table of the
      /// example database.
      /// </summary>
      private string m_calendarName;

      /// <summary>
      /// The database file location, including the full path. This value is used to build the connection string.
      /// </summary>
      private string m_dataLocation = "";

      /// <summary>
      /// The calendar ID. It must be unique. It comes from the CalendarID field of the Calendars table in the example database.
      /// </summary>
      private string m_calendarId = new Guid().ToString();

      /// <summary>
      /// The prefix to be added to items in generated calendars. This value is entered by the user when the calendar is selected
      /// in the calendar list.
      /// </summary>
      private string m_itemPrefix = "";

      /// <summary>
      /// A list of all schedule items associated with this calendar data source. This list is initialized by the 
      /// InitializeItems function, which gets commanded by PrintableCal before generating a calendar. The list is populated by
      /// querying the Events table of the example database.
      /// </summary>
      private List<IScheduleItem> m_items = new List<IScheduleItem>();

      /// <summary>
      /// The appearance to use for events associated with this calendar data source. The appearance will default to a random
      /// appearance. It can be modified by the user by clicking the calendar's name in the calendar list to access the calendar
      /// properties.
      /// </summary>
      private IAppearance m_itemAppearance = null;

      /// <summary>
      /// A flag indicating whether this calendar data source is selected for inclusion in generated calendars. A calendar data
      /// source is "selected" by the user checking the box next to the calendar name.
      /// </summary>
      private bool m_isSelected = true;

      /// <summary>
      /// These are example options. Options for this calendar data source will appear in the Options window, accessed from the
      /// PrintableCal tab on the ribbon. Options like this apply to all calendar data sources of a certain type.
      /// </summary>
      private CalendarSourceOptionCheckbox m_exampleOption1 = new CalendarSourceOptionCheckbox("Example option 1", true);
      private CalendarSourceOptionComboBox m_exampleOption2 = new CalendarSourceOptionComboBox(
         "Example option 2", "Alpha", new object[] { "Alpha", "Beta", "Gamma" });

      /// <summary>
      /// This is a collection of options applicable to this calendar data source.
      /// </summary>
      private List<ICalendarSourceOption> m_options = null;

      /// <summary>
      /// Gets the icon associated with this calendar data source. The icon is shown in the main PrintableCal user interface, 
      /// when listing the calendar data sources that have been added and when displaying the drop-down menu after the user clicks
      /// the "Add" button.
      /// </summary>
      public System.Drawing.Image CalendarIcon
      {
         get { return Properties.Resources.icon; }
      }

      /// <summary>
      /// The calendar ID. It must be unique. It comes from the CalendarID field of the Calendars table in the example database.
      /// </summary>
      public string CalendarId
      {
         get { return m_calendarId;  }
         set { m_calendarId = value; }
      }

      /// <summary>
      /// The calendar name. It doesn't need to be unique. It comes from the CalendarName field in the Calendars table of the
      /// example database.
      /// </summary>
      public string CalendarName
      {
         get { return m_calendarName; }
         set { m_calendarName = value; }
      }

      /// <summary>
      /// Creates and returns a new instance of the calendar data source. This function is commanded by FormAddCalendar.
      /// </summary>
      /// <returns>Returns a new instance of AccessDbCalendarSource.</returns>
      public ICalendarSource Create()
      {
         return new AccessDbCalendarSource();
      }

      /// <summary>
      /// The database file location, including the full path. This value is used to build the connection string.
      /// </summary>
      public string DataLocation
      {
         get { return m_dataLocation; }
         set { m_dataLocation = value; }
      }

      /// <summary>
      /// Gets the options associated with this calendar data source. If your calendar data source doesn't require any 
      /// special options, return null.
      /// </summary>
      public List<ICalendarSourceOption> CalendarOptions
      {
         get { return m_options; }
      }

      /// <summary>
      /// This function initializes this calendar data source with a list of events occurring within the specified date range.
      /// This function is commanded by PrintableCal before a calendar is generated. The Events table of the example database 
      /// is queried to fill the event list. The CustomData field of the database is appended to event descriptions to provide
      /// an example of how additional custom fields could be generically integrated into the generated output.
      /// </summary>
      /// <param name="startRange">The start date.</param>
      /// <param name="endRange">The end date.</param>
      /// <param name="errors">Any errors will be appended to this string. Make sure to use \r\n for new lines.</param>
      /// <returns>True is returned if no errors were encountered.</returns>
      public bool InitializeItems(DateTime startRange, DateTime endRange, ref string errors)
      {
         bool success = true;

         try
         {
            // clear any items if this is not the first time the function was commanded.
            this.Items.Clear();

            // use the data location to create a connection string
            string connectionString = @"Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + m_dataLocation;

            using (OleDbConnection connection = new OleDbConnection(connectionString))
            {
               connection.Open();

               // fill the dataset
               DataSet dataSet = new DataSet();

               using (OleDbDataAdapter dataAdapter = new OleDbDataAdapter("SELECT * FROM Events WHERE CalendarID='" + m_calendarId + "'", connection))
               {
                  dataAdapter.Fill(dataSet, "Events");

                  foreach (DataRow eventRow in dataSet.Tables["Events"].Rows)
                  {
                     DateTime startDateTime = Convert.ToDateTime(eventRow["StartDateTime"]);
                     DateTime endDateTime = Convert.ToDateTime(eventRow["EndDateTime"]);
                     bool allDay = Convert.ToBoolean(eventRow["AllDay"]);

                     if (allDay)
                     {
                        // adjust the start and end values if the event is all-day. The start value must begin at the start of the
                        // day and the end value must fill the entire day, ending 1 second before midnight.
                        startDateTime = startDateTime.Date;
                        endDateTime = endDateTime.Date.AddSeconds(86399);
                     }

                     // check if the event start/end time intersects the specified date range - only add the event if it's within range
                     if (CalendarUtils.TimeRangesIntersect(startRange, endRange, startDateTime, endDateTime))
                     {
                        string eventId = eventRow["EventID"].ToString();
                        string title = eventRow["Title"].ToString();
                        string location = eventRow["Location"].ToString();
                        string description = eventRow["Description"].ToString();
                        string customData = eventRow["CustomData"].ToString();

                        IScheduleItem item = new AccessDbScheduleItem(this, title, description, startDateTime, endDateTime, allDay, location, customData);
                        Items.Add(item);
                     }
                  }
               }

               // close the connection
               connection.Close();
            }
         }
         catch (Exception ex)
         {
            success = false;

            errors += "An error was encountered while processing the calendar '" + this.CalendarName + "':" + 
               Environment.NewLine + ex.Message.ToString() + Environment.NewLine + Environment.NewLine;
         }

         return success;
      }

      /// <summary>
      /// Always false, since the example database is stored locally.
      /// </summary>
      public bool IsOnline
      {
         get { return false; }
      }

      /// <summary>
      /// Get/sets a flag indicating whether this calendar data source is selected for inclusion in generated calendars. A calendar data
      /// source is "selected" by the user checking the box next to the calendar name.
      /// </summary>
      public bool IsSelected
      {
         get { return m_isSelected; }
         set { m_isSelected = value; }
      }

      /// <summary>
      /// Gets/sets the appearance to use for events associated with this calendar data source. The appearance will default to a random
      /// appearance. It can be modified by the user by clicking the calendar's name in the calendar list to access the calendar
      /// properties.
      /// </summary>
      public IAppearance ItemAppearance
      {
         get { return m_itemAppearance; }
         set { m_itemAppearance = value; }
      }

      /// <summary>
      /// Gets/sets the prefix to be added to items in generated calendars. This value is entered by the user when the calendar is selected
      /// in the calendar list.
      /// </summary>
      public string ItemPrefix
      {
         get { return m_itemPrefix; }
         set { m_itemPrefix = value; }
      }

      /// <summary>
      /// Gets a list of all schedule items associated with this calendar data source. This list is initialized by the 
      /// InitializeItems function, which gets commanded by PrintableCal before generating a calendar. The list is populated by
      /// querying the Events table of the example database.
      /// </summary>
      public List<IScheduleItem> Items
      {
         get { return m_items; }
      }

      /// <summary>
      /// The default constructor. A list of options for this calendar data source is initialized.
      /// </summary>
      public AccessDbCalendarSource()
      {
         m_options = new List<ICalendarSourceOption>();
         m_options.Add(m_exampleOption1);
         m_options.Add(m_exampleOption2);
      }

      /// <summary>
      /// This function is not required in this example. It could be used if there were additional settings required for 
      /// connecting to the data source, such as a username and password.
      /// </summary>
      /// <param name="settings">The settings collection</param>
      public void LoadSettings(Dictionary<string, object> settings)
      {
         // no custom settings are required for this data source
      }

      /// <summary>
      /// This function is not required in this example. It could be used if there were additional settings required for 
      /// connecting to the data source, such as a username and password.
      /// </summary>
      /// <param name="settings">The settings collection</param>
      public void SaveSettings(Dictionary<string, object> settings)
      {
         // no custom settings are required for this data source
      }

      /// <summary>
      /// This function is commanded when the user clicks the "Add -> Example Access Database" command  to add a new calendar
      /// data source. A form is displayed allowing the user to select which data source(s) they'd like to add. The example 
      /// database supports any number of different calendars.
      /// </summary>
      /// <param name="sources">The collection data sources, updated by the form if the user adds more calendars and clicks OK./</param>
      /// <returns>Returns DialogResult.OK if the user clicks the OK button on the displayed form.</returns>
      public DialogResult ShowAddCalendarForm(ICalendarCollection sources)
      {
         DialogResult result = DialogResult.Cancel;

         using (FormAddCalendar form = new FormAddCalendar(sources))
         {
            result = form.ShowDialog();
         }

         return result;
      }

      /// <summary>
      /// Gets the source name. This name is used in the PrintableCal calendar list and when comparing calendar data sources to
      /// determine which ones are implemented by this class. The name must be unique!
      /// </summary>
      public string SourceName
      {
         get { return SOURCE_NAME; }
      }

      /// <summary>
      /// Disposes references so garbage collection can eventually do its thing. Since this example class doesn't hold on to any
      /// references that need to be disposed, this function doesn't do anything. 
      /// </summary>
      public void Dispose()
      {
         // nothing to dispose
      }
   }
}

The example code above is for a calendar data source. PrintableCal can also be extended to support additional template styles. The example code below (also included in the example project of the API) shows how the Agenda template style works. This is the actual code PrintableCal is using for the Agenda template style. It demonstrates the concepts needed for extending PrintableCal to support other template styles.

AgendaTemplate.cs

//-----------------------------------------------------------------------------
// Copyright 2014 by VueSoft LLC. All rights reserved.
// http://www.printablecal.com
//-----------------------------------------------------------------------------
//
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, 
//    this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
// 
// 3. Neither the name of "PrintableCal" nor the names of its contributors may 
//    be used to endorse or promote products derived from this software without
//    specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//
//-----------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Drawing;
using System.Globalization;
using System.Linq;
using System.Text;

using Excel = Microsoft.Office.Interop.Excel;

using Vuesoft.PrintableCal.Common;
using Vuesoft.PrintableCal.Common.Validation;
using Vuesoft.PrintableCal.Interfaces;

namespace Vuesoft.PrintableCal.Addins.Templates.AgendaTemplate
{
   /// <summary>
   /// This class provides an example template style. In this case, its the "Agenda" template style that is included with the 
   /// PrintableCal installation. The same techniques could be used to extend PrintableCal with entirely new template styles.
   /// 
   /// <remarks>
   /// When defining a template style, make sure to add the Export and ExportMetadata attributes. These are required for
   /// PrintableCal to recognize the type is an extension. 
   /// </remarks>
   /// </summary>
   [Export(typeof(ITemplate))]
   [ExportMetadata("Style", AgendaTemplate.STYLE)]
   public class AgendaTemplate : TemplateBase
   {
      /// <summary>
      /// The template style name. This name is used to group templates in the PrintableCal user interface. It also is used to
      /// determine which saved template file goes with which template style implementation. The style name must be unique!
      /// </summary>
      public const string STYLE = "Agenda";

      /// <summary>
      /// The identifier and label for the starting date template option.
      /// </summary>
      private static string OPTION_START_DATE = "Start date:";

      /// <summary>
      /// The identifier and label for the number of days option. This value is used as a key for persisting the option.
      /// </summary>
      private static string OPTION_NUMBER_OF_DAYS = "Number of days:";

      /// <summary>
      /// The identifier and label for the time format option. This value is used as a key for persisting the option.
      /// </summary>
      private static string OPTION_TIME_FORMAT = "Time format:";

      /// <summary>
      /// The identifier and label for the option to show descriptions. This value is used as a key for persisting the option.
      /// </summary>
      private static string OPTION_SHOW_DESCRIPTIONS = "Show descriptions";

      /// <summary>
      /// Get the icon to be displayed in the template list group header. Icons must be sized 24x24 pixels. For consistency, 
      /// they should show a simplified or notional representation of what the template will generate.
      /// </summary>
      public override Image Icon
      {
         get { return Properties.Resources.agenda; }
      }

      /// <summary>
      /// The default constructor.
      /// </summary>
      public AgendaTemplate()
         : base(STYLE)
      {
         DateTime date = DateTime.Today;

         // Add the template options. These will appear in the main PrintableCal user interface, after an Agenda template is 
         // selected and the Next button clicked. There are a number of different template option control types provided in
         // the Vuesoft.PrintableCal.Common namespace, depending on the type of option required.
         this.TemplateOptions.Add(new TemplateOptionDate(OPTION_START_DATE, date, false));
         this.TemplateOptions.Add(new TemplateOptionComboBox(OPTION_NUMBER_OF_DAYS, 7, 1, 365));
         this.TemplateOptions.Add(new TemplateOptionTimeFormat(OPTION_TIME_FORMAT, TimeFormat.Default, true));
         this.TemplateOptions.Add(new TemplateOptionCheckbox(OPTION_SHOW_DESCRIPTIONS, true));

         // Add the validation rules. These rules ensure that template files created by the user will be compatible with the 
         // Generate function defined in this class.
         this.ValidationRules.Add(new NamedCellValidation(
            "DayName", 
            "This name is used to determine where the day-of-week title should be placed in relation to other text for each generated day.",
            ValidationResultType.Error));

         this.ValidationRules.Add(new NamedCellValidation(
            "Date", 
            "This name is used to determine where the date should be placed in relation to other text for each generated day.",
            ValidationResultType.Error));

         this.ValidationRules.Add(new NamedCellValidation(
            "CalendarColor",
            "This name is used to determine where an item's color should be be placed in relation to the item text in each generated day.", 
            ValidationResultType.Warning));

         this.ValidationRules.Add(new NamedCellValidation(
            "Time", 
            "This name is used to determine where each item time should be placed in relation to the item title and description in each generated day.", 
            ValidationResultType.Error));

         this.ValidationRules.Add(new NamedCellValidation(
            "Title", 
            "This name is used to determine where each item title should be placed in relation to the item time and description in each generated day.", 
            ValidationResultType.Error));

         this.ValidationRules.Add(new NamedCellValidation(
            "Description",
            "This name is used to determine where each item description should be placed in relation to the item time and title in each generated day.",
            ValidationResultType.Error));

         this.ValidationRules.Add(new CellPositionValidation(
            "DayName",
            Direction.Above,
            new string[] { "CalendarColor", "Time", "Title", "Description" },
            "Items are listed under a header for each day. The day name is part of the header and must be above cells used for each item in the day.", 
            ValidationResultType.Error));

         this.ValidationRules.Add(new CellPositionValidation(
            "Date", 
            Direction.Above,
            new string[] { "CalendarColor", "Time", "Title", "Description" },
            "Items are listed under a header for each day. The date text is part of the header and must be above cells used for each item in the day.",
            ValidationResultType.Error));

         this.ValidationRules.Add(new WorksheetCountValidation(
            1, 
            "The entire Agenda template is defined on a single worksheet. Additional worksheets are unnecessary and may clutter the generated output.", 
            ValidationResultType.Warning));
         
         // The default Word output options can be modified. In this case, it makes more sense to show the agenda template in 
         // portrait orientation, rather than landscape.
         this.WordOptions.IsOrientationPortrait = true;
      }

      /// <summary>
      /// This function generates a calendar using the Agenda template style. The calendar is generated to the specified 
      /// workbook, using the specified calendar data sources.
      /// </summary>
      /// <param name="workbook">The output workbook.</param>
      /// <param name="calendars">The calendar data sources used to populate the output agenda.</param>
      /// <param name="status">The status object, updated with the current operation and percent completion as the template is
      /// being generated.</param>
      public override void Generate(Excel.Workbook workbook, ICalendarCollection calendars, IStatus status)
      {
         try
         {
            // get options
            DateTime startDate = (DateTime)GetTemplateOption(OPTION_START_DATE);
            int nDays = Convert.ToInt32(GetTemplateOption(OPTION_NUMBER_OF_DAYS));
            DateTime endDate = startDate.AddDays(nDays - 1);
            bool showDescriptions = Convert.ToBoolean(GetTemplateOption(OPTION_SHOW_DESCRIPTIONS));
            TimeFormat timeFormat = (TimeFormat)GetTemplateOption(OPTION_TIME_FORMAT);

            // initialize events
            status.StartOperation("Retrieving data from calendar sources...", 50);
            calendars.Initialize(startDate, endDate.AddDays(1).AddSeconds(-1), status);
            status.EndOperation();

            // figure out where values should be placed, using named range references
            status.StartOperation("Preparing workbook...", 60);
            Excel.Range templateCellDayName = workbook.Names.Item("DayName").RefersToRange;
            Excel.Range templateCellDate = workbook.Names.Item("Date").RefersToRange;
            Excel.Range templateCellCalendarColor = null;            
            Excel.Range templateCellTime = workbook.Names.Item("Time").RefersToRange;
            Excel.Range templateCellTitle = workbook.Names.Item("Title").RefersToRange;
            Excel.Range templateCellDescription = workbook.Names.Item("Description").RefersToRange;

            try
            {
               // the calendar color cell name is optional
               templateCellCalendarColor = workbook.Names.Item("CalendarColor").RefersToRange;
            }
            catch { }

            if (!showDescriptions)
            {
               templateCellDescription.Value = "";
               templateCellDescription = templateCellTitle;
            }

            DateTime currentDate = startDate;
            Excel.Worksheet ws = workbook.Sheets[1];

            int templateDateRowStart = MathUtils.MinValue(templateCellDayName.Row, templateCellDate.Row);
            int templateDateRowEnd = MathUtils.MaxValue(templateCellDayName.Row, templateCellDate.Row);
            int templateEventRowStart = 0;            
            int templateEventRowEnd = 0;

            if (templateCellCalendarColor != null)
            {
               templateEventRowStart = MathUtils.MinValue(
                  templateCellCalendarColor.Row, templateCellTime.Row, templateCellTitle.Row, templateCellDescription.Row);

               templateEventRowEnd = MathUtils.MaxValue(
                  templateCellCalendarColor.Row, templateCellTime.Row, templateCellTitle.Row, templateCellDescription.Row);
            }
            else
            {
               templateEventRowStart = MathUtils.MinValue(templateCellTime.Row, templateCellTitle.Row, templateCellDescription.Row);
               templateEventRowEnd = MathUtils.MaxValue(templateCellTime.Row, templateCellTitle.Row, templateCellDescription.Row);
            }

            int row = templateDateRowStart;
            status.EndOperation();

            double deltaPercent = 40.0 / ((endDate - startDate).TotalDays + 1);

            while (currentDate <= endDate)
            {
               status.StartOperation("Processing " + currentDate.ToLongDateString() + "...", Convert.ToInt32(status.PercentComplete + deltaPercent));
               List<IScheduleItem> items = calendars.GetItemsInRange(currentDate, currentDate.AddDays(1).AddSeconds(-1));

               if (items.Count > 0)
               {
                  // date row
                  if (row > templateDateRowStart)
                  {
                     Excel.Range copyRowStart = ws.Rows[templateDateRowStart];
                     Excel.Range copyRowEnd = ws.Rows[templateDateRowEnd];
                     ws.Range[copyRowStart.EntireRow, copyRowEnd.EntireRow].Copy(ws.Rows[row]);
                  }

                  // day name
                  Excel.Range dayNameCell = ws.Cells[row + templateCellDayName.Row - templateDateRowStart, templateCellDayName.Column];
                  dayNameCell.Value = currentDate;

                  // date
                  Excel.Range dateCell = ws.Cells[row + templateCellDate.Row - templateDateRowStart, templateCellDate.Column];
                  dateCell.Value = currentDate;

                  // padding
                  row += (templateDateRowEnd - templateDateRowStart) + 1;
                  Excel.Range paddingRow = ws.Rows[row];
                  paddingRow.EntireRow.RowHeight = 7.5;

                  // events
                  row += 1;

                  foreach (IScheduleItem item in items)
                  {
                     ScheduleItemCell calendarCell = new ScheduleItemCell();
                     calendarCell.Initialize(item, timeFormat, false, false, true, false);

                     if (row > templateEventRowStart)
                     {
                        Excel.Range copyRowStart = ws.Rows[templateEventRowStart];
                        Excel.Range copyRowEnd = ws.Rows[templateEventRowEnd];
                        ws.Range[copyRowStart.EntireRow, copyRowEnd.EntireRow].Copy(ws.Rows[row]);
                     }

                     // calendar color
                     if (templateCellCalendarColor != null)
                     {
                        Excel.Range calendarColorCell = ws.Cells[
                           row + templateCellCalendarColor.Row - templateEventRowStart, templateCellCalendarColor.Column];

                        calendarColorCell.Interior.Color = ColorTranslator.ToOle(calendarCell.Appearance.BackgroundColor);

                        if (calendarCell.Appearance.BorderColor.A != 0)
                        {
                           int borderColor = ColorTranslator.ToOle(calendarCell.Appearance.BorderColor);
                           calendarColorCell.Borders.LineStyle = Excel.XlLineStyle.xlContinuous;
                           calendarColorCell.Borders.Color = borderColor;
                        }
                     }

                     // time
                     Excel.Range timeCell = ws.Cells[row + templateCellTime.Row - templateEventRowStart, templateCellTime.Column];

                     if (item.StartDateTime.Date != item.EndDateTime.Date)
                     {
                        if (item.IsAllDay)
                        {
                           timeCell.Value = "All day";
                        }
                        else
                        {
                           if (currentDate == item.StartDateTime.Date)
                           {
                              timeCell.Value = CalendarUtils.GetTimeString(timeFormat, item.StartDateTime) + " ►";
                           }
                           else if (currentDate == item.EndDateTime.Date)
                           {
                              timeCell.Value = "◄ " + CalendarUtils.GetTimeString(timeFormat, item.EndDateTime);
                           }
                           else
                           {
                              timeCell.Value = "◄ " + CalendarUtils.GetTimeString(timeFormat, currentDate) + " - " + 
                                                      CalendarUtils.GetTimeString(timeFormat, currentDate.AddMinutes(1339)) + "  ►";
                           }
                        }
                     }
                     else if (item.IsAllDay)
                     {
                        timeCell.Value = "All day";
                     }
                     else
                     {
                        timeCell.Value = CalendarUtils.GetTimeString(timeFormat,item.StartDateTime) + " - " + 
                                         CalendarUtils.GetTimeString(timeFormat,item.EndDateTime);
                     }

                     // title
                     Excel.Range titleCell = ws.Cells[row + templateCellTitle.Row - templateEventRowStart, templateCellTitle.Column];
                     titleCell.Value = calendarCell.Text;

                     // description
                     Excel.Range descriptionCell = ws.Cells[row + templateCellDescription.Row - templateEventRowStart, templateCellDescription.Column];

                     if (showDescriptions)
                     {
                        if (!String.IsNullOrEmpty(item.Description))
                        {
                           descriptionCell.Value = item.Description;
                           descriptionCell.EntireRow.AutoFit();
                           row += 2;

                           // padding
                           if (templateEventRowStart != templateEventRowEnd)
                           {
                              paddingRow = ws.Rows[row];
                              paddingRow.EntireRow.RowHeight = 7.5;
                           }

                           row -= 1;
                        }
                        else
                        {
                           descriptionCell.Value = "";

                           if (templateEventRowStart != templateEventRowEnd)
                           {
                              descriptionCell.EntireRow.RowHeight = 7.5;
                           }
                        }
                     }
                     else
                     {
                        descriptionCell.Value = "";
                     }

                     row += (templateEventRowEnd - templateEventRowStart + 1);
                  }

                  row += 1;

                  // padding
                  paddingRow = ws.Rows[row];
                  paddingRow.EntireRow.RowHeight = 7.5;
               }

               currentDate = currentDate.AddDays(1);
               status.EndOperation();
            }
         }
         catch (Exception ex)
         {
            FormError.ShowError(ex);
         }
      }
   }
}



Version 1.9.4 - October 15, 2016

blog comments powered by Disqus