Skip to main content

FullCalendar

FullCalendar is a full-size event calendar. It wraps the open-source FullCalendar JavaScript library, version 3.9.0, and the optional Scheduler 1.9.4 plug-in for resource timelines. It shows events in month, week, day and list views. Users can drag and resize events, and every change comes back to the server as a typed .NET event.

Use it for appointment books, room and equipment bookings, shift plans, or anywhere a user needs to see and move events on a calendar.

A FullCalendar control in the agenda week view, with one column per day from Sunday to Saturday, an all-day row at the top and hourly time slots below

The agenda week view, with the all-day row and 30-minute time slots.

Installation​

Add the Wisej-4-FullCalendar package to your Wisej.NET project, either with the NuGet Package Manager or from the command line:

dotnet add package Wisej-4-FullCalendar

The package targets .NET Framework 4.8 and .NET 8 or later (including the -windows, Android, iOS and Mac Catalyst targets). After a rebuild, FullCalendar appears in the Visual Studio toolbox.

Everything the calendar needs on the client is embedded in the assembly: FullCalendar 3.9.0, jQuery 3.1.1, moment.js 2.17.1 with its locales and, when you enable it, the Scheduler plug-in. There's nothing to load from a CDN.

Scheduler license

The core calendar is MIT-licensed. The Scheduler plug-in, which provides the four Timeline* views and the resources, is a separately licensed FullCalendar product. To enable it, set SchedulerLicenseKey:

  • "GPL-My-Project-Is-Open-Source" for projects released under the GPL;
  • "CC-Attribution-NonCommercial-NoDerivatives" for non-commercial projects;
  • the key of a commercial license, bought from fullcalendar.io/scheduler, for everything else.

A Wisej.NET license doesn't cover the Scheduler plug-in.

Getting started​

This example creates a calendar in the agenda week view, adds a timed event and an all-day event, and shows the title of an event when the user clicks it.

using Wisej.Web.Ext.FullCalendar;

var calendar = new FullCalendar
{
Dock = DockStyle.Fill,
View = ViewType.AgendaWeek
};

// A one-hour event today at 9:00.
calendar.Events.Add(new Event(DateTime.Today.AddHours(9), TimeSpan.FromHours(1)) { Title = "Stand-up" });

// An all-day event tomorrow. Add() returns the new event.
calendar.Events.Add(DateTime.Today.AddDays(1)).Title = "Company holiday";

calendar.EventClick += (s, e) => AlertBox.Show(e.Event.Title);

this.Controls.Add(calendar);
No built-in toolbar

The calendar doesn't show FullCalendar's own header toolbar with the title and the prev / next / today buttons. Build the toolbar you want with regular Wisej.NET controls and call the navigation methods described in Views and navigation. That way the toolbar matches the rest of your application and can be localized and themed with it.

Views and navigation​

View selects one of the ViewType values. The default is Month. Changing the view after the calendar is created switches it on the client without rebuilding it.

ViewTypeWhat it shows
MonthA month grid, one cell per day.
BasicWeek / BasicDayA week or a day as simple day cells, without time slots.
AgendaWeek / AgendaDayA week or a day with an all-day row and vertical time slots.
ListYear / ListMonth / ListWeek / ListDayA flat list of the events in the period. NoEventsMessage is shown when there are none.
TimelineDay / TimelineWeek / TimelineMonth / TimelineYearA horizontal time axis with one row per resource. Requires the Scheduler plug-in.

To move through time, call Previous() and Next() (one month, week or day, depending on the view), PreviousYear() and NextYear(), Today() and GotoDate(), or set CurrentDate. Each move fires CurrentDateChanged, which is the place to update a title label.

private void buttonPrev_Click(object sender, EventArgs e) => this.fullCalendar1.Previous();
private void buttonNext_Click(object sender, EventArgs e) => this.fullCalendar1.Next();
private void buttonToday_Click(object sender, EventArgs e) => this.fullCalendar1.Today();
private void buttonWeek_Click(object sender, EventArgs e) => this.fullCalendar1.View = ViewType.AgendaWeek;

private void fullCalendar1_CurrentDateChanged(object sender, EventArgs e)
{
this.labelTitle.Text = this.fullCalendar1.CurrentDate.ToString("MMMM yyyy");
}
Navigation is asynchronous

Previous(), Next(), Today() and the other navigation methods run on the client. CurrentDate changes only when the client reports the new date, so it still holds the old value right after the call returns. Read it in CurrentDateChanged, not in the next line of code.

TodayDate is the date the calendar treats as "today": the highlighted day, the current-time marker (ShowCurrentTime) and the target of Today(). Until you set it, it's DateTime.Now on the server.

Users in other time zones

If the server and the users aren't in the same time zone, set TodayDate to the user's local time. Otherwise "today" and the current-time marker follow the server's clock.

Events​

The calendar's Events collection holds Event objects. Adding, removing or changing an event reloads the visible range on the client after the current request, so you can change many events in one go without calling anything else.

EventCollection.Add has overloads for the usual cases, and each returns the new event: a single day (an all-day event), a start and an end, or a start and a TimeSpan. Each can also take an ID first. An event without an ID gets one automatically. Adding an event with the ID of an existing one replaces it, and the string indexer finds it again:

this.fullCalendar1.Events.Add("meeting1", new DateTime(2026, 10, 5, 10, 0, 0), TimeSpan.FromMinutes(90)).Title = "Design review";
this.fullCalendar1.Events.Add(new Event("vacation", new DateTime(2026, 10, 12)) { Title = "Vacation", BackgroundColor = Color.SeaGreen });

var ev = this.fullCalendar1.Events["meeting1"];
if (ev != null)
ev.Start = ev.Start.AddHours(1);

Event properties​

PropertyWhat it does
IdUnique key within the calendar. Assigned automatically when empty.
TitleThe text shown on the event.
Start / EndWhen the event starts and ends. End is exclusive: an all-day event on October 5 and 6 ends on October 7. Both are updated when the user drags or resizes the event.
AllDayWhether the event covers whole days. The single-day constructors create all-day events.
EditableOverrides the calendar's Editable for this event.
BackgroundColor / BorderColor / TextColorColors for this event. They override the calendar-wide EventBackgroundColor, EventBorderColor and EventTextColor.
ClassNameOne or more CSS classes added to the event's element. Define them in a style sheet your application loads.
ToolTipTextThe tooltip, shown when ShowEventToolTips is true. It falls back to Title.
ResourceIdThe scheduler resource the event belongs to.
UserDataA dynamic object for your own data, such as the database key of the appointment.
Keep your key in UserData

Store your record's key in UserData (for example ev.UserData.AppointmentId = 1234) and read it back in EventClick or EventChanged. You don't have to encode it in Id or look the event up by title.

Drag, resize and EventChanged​

When Editable is true (the default), users can move events and change their duration. The calendar updates the event's Start, End and AllDay, then fires EventChanged with the EventValueEventArgs: the event, plus its OldStartDate and OldEndDate. That's where you save the change, or reject it:

private void fullCalendar1_EventChanged(object sender, EventValueEventArgs e)
{
// TryMove is your own booking logic.
if (!this.bookings.TryMove(e.Event.UserData.BookingId, e.Event.Start, e.Event.End))
{
// Put the event back where it was.
e.Event.Start = e.OldStartDate;
e.Event.End = e.OldEndDate;
AlertBox.Show("That slot is already taken.", MessageBoxIcon.Warning);
}
}
note

EventChanged also fires when your code changes Start or End of an event in the collection, including the two assignments in the example above. Guard against re-entering your save logic if that matters.

Loading events on demand​

For a large calendar, don't fill Events with every appointment you have. Set VirtualMode to true and supply only the events of the visible range when the calendar asks for them:

  • VirtualEventsNeeded fires each time the visible range changes. Its arguments give StartDate and EndDate, and you set Events to the events in that range.
  • RetrieveVirtualEvent fires when the calendar needs one event by ID or by index, which happens whenever the user clicks, drags or resizes an event. Set e.Event.
this.fullCalendar1.VirtualMode = true;

this.fullCalendar1.VirtualEventsNeeded += (s, e) =>
{
// LoadAppointments is your own data access method.
e.Events = LoadAppointments(e.StartDate, e.EndDate)
.Select(a => new Event(a.Id.ToString(), a.Start, a.End) { Title = a.Subject });
};

this.fullCalendar1.RetrieveVirtualEvent += (s, e) =>
{
var a = LoadAppointment(e.EventID);
e.Event = new Event(a.Id.ToString(), a.Start, a.End) { Title = a.Subject };
};
Handle both events in virtual mode

In virtual mode the Events collection stores nothing and can't be modified, and turning VirtualMode on clears it. If RetrieveVirtualEvent isn't handled, clicking or dragging an event has no event to work with. Set VirtualSize if you also read Events.Count or use the index-based indexer.

Time slots and layout​

The views with time slots (agenda and timeline) are shaped by these properties. Most of them rebuild the calendar on the client when changed, so set them in the designer or before the page is shown.

PropertyDefaultWhat it does
MinTime / MaxTime00:00 / 24:00The first and last (exclusive) time slot shown each day.
ScrollTime06:00The time the view is initially scrolled to.
SlotDuration30 minutesThe length of one slot.
SlotLabelInterval1 hourHow often the time axis is labeled.
SlotLabelFormat / TimeFormatlocalemoment.js format strings for the axis labels and for the time on each event, for example "HH:mm".
HeaderFormatslocalemoment.js formats for the column headers of each view.
FirstDayOfWeekDefaultThe first column of the week views.
BusinessHoursnoneTime ranges to emphasize, as an array of BusinessHours, each with Start, End and Days.
AllDaySlot / AllDayTexttrue / "All Day"Whether the agenda views show the all-day row, and its title.
SlotEventOverlaptrueWhether overlapping timed events overlap visually or sit side by side.
EventLimit0 (no limit)The maximum number of events per day. Extra events collapse into a "+2 more" link that opens a popover.
NextDayThreshold09:00How late an event must end on the next day to be drawn on that day.
// Working hours only, 15-minute slots, Monday to Friday 8:00–18:00 emphasized.
this.fullCalendar1.MinTime = new TimeSpan(7, 0, 0);
this.fullCalendar1.MaxTime = new TimeSpan(19, 0, 0);
this.fullCalendar1.ScrollTime = new TimeSpan(8, 0, 0);
this.fullCalendar1.SlotDuration = TimeSpan.FromMinutes(15);
this.fullCalendar1.SlotLabelFormat = "HH:mm";
this.fullCalendar1.BusinessHours = new[] {
new BusinessHours {
Start = new TimeSpan(8, 0, 0),
End = new TimeSpan(18, 0, 0),
Days = new[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }
}
};
note

BusinessHours and Resources are arrays. Assign a new array to apply a change: editing an item of the current array doesn't update the calendar.

Day and month names follow the Wisej.NET application's current language, and the calendar is mirrored when RightToLeft is set to Yes.

Scheduler resources​

With the Scheduler plug-in enabled, Resources defines the rows of the timeline views: rooms, people, machines. Each SchedulerResource has an Id and a Title, default colors for its events, and optional Children for grouped resources. An event goes on a resource's row through its ResourceId.

this.fullCalendar1.SchedulerLicenseKey = "GPL-My-Project-Is-Open-Source";
this.fullCalendar1.ResourceLabelText = "Rooms";
this.fullCalendar1.Resources = new[] {
new SchedulerResource("a") { Title = "Auditorium A", EventBackgroundColor = Color.SteelBlue },
new SchedulerResource("b") { Title = "Auditorium B", EventBackgroundColor = Color.IndianRed }
};
this.fullCalendar1.View = ViewType.TimelineDay;

this.fullCalendar1.Events.Add(DateTime.Today.AddHours(10), TimeSpan.FromHours(2)).ResourceId = "a";
Set the key first

The Scheduler scripts are added to the calendar's client packages the first time the packages are built. Set SchedulerLicenseKey in the designer, or in code before the calendar is created on the client, and before you switch View to a timeline view. Setting it later doesn't load the plug-in.

ResourceAreaWidth (a CSS width, "30%" by default) and ResourceLabelText shape the resource column. Changing a resource's properties later fires ResourceChanged but doesn't redraw the calendar.

Dropping controls on the calendar​

ItemDrop fires when the user drops a Wisej.NET control on the calendar, for example an entry of a list of unscheduled tasks. ItemDropEventArgs gives the dropped control as Item, the date and time under the pointer as Day and, in a timeline view, the ResourceId of the row. It uses the standard Wisej.NET drag and drop: set AllowDrag on the source and AllowDrop on the calendar. See Drag & Drop.

Events of the control​

EventWhen
EventClick / EventDoubleClickThe user clicks or double-clicks an event. e.Event is the event.
DayClick / DayDoubleClickThe user clicks an empty day or time slot. e.Day is the date and time, which is handy for "new appointment here".
EventChangedAn event was moved or resized, by the user or by code.
EventMouseEnter / EventMouseLeaveThe pointer enters or leaves an event.
CurrentDateChangedThe displayed date range changed.
ItemDropA Wisej.NET control was dropped on the calendar.
VirtualEventsNeeded / RetrieveVirtualEventVirtual mode needs events. See Loading events on demand.
ResourceChangedA scheduler resource changed.

Styling​

  • Calendar-wide event colors: EventBackgroundColor, EventBorderColor and EventTextColor, overridden per resource and per event.
  • CSS classes: Event.ClassName and SchedulerResource.EventClassName, for rules in your own style sheet.
  • ThemeSystem: Standard (the default) renders FullCalendar's own look. Bootstrap3 and jQueryUI use a Bootstrap 3 or jQuery UI theme instead, but you must load that CSS file into the page yourself.

The extension's FullCalendar.mixin.theme only gives the control the standard widget appearance. The calendar's inner look comes from FullCalendar's CSS.

API reference​