Back to Devexpress

Appointment Forms and Tooltips in Blazor Scheduler

blazor-404564-components-scheduler-customization-appointment-forms-and-tooltips.md

latest50.9 KB
Original Source

Appointment Forms and Tooltips in Blazor Scheduler

  • Jan 20, 2026
  • 20 minutes to read

The DevExpress Blazor Scheduler allows you to customize appointment forms and tooltips. This topic describes default forms and tooltips, how to customize their appearance, and embed predefined/custom layout items and buttons.

Run Demo: Scheduler - Custom Appointment Form

Run Demo: Scheduler - Appointment Tooltip Template

YouTube video

Default Appointment Tooltip

The DevExpress Blazor Scheduler shows a tooltip when a user clicks an appointment. The tooltip’s header contains the following buttons: Show Edit Form, Delete Appointment, Close Appointment, and Restore Occurence (for appointments with changed occurence). The tooltip’s body can display the appointment subject, start date, end date, location, description, and resource (if these properties are specified).

You can customize the tooltip’s body and header.

You can also hide the tooltip (set the ShowAppointmentTooltip property to false).

Custom Appointment Tooltip

You can use AppointmentTooltipTemplate and AppointmentTooltipHeaderTemplate properties to customize a tooltip’s body and header. These properties accept a SchedulerAppointmentTooltipInfo object as the context parameter. Use the context parameter to access appointment data.

You can add predefined or custom buttons to appointment tooltip templates.

The following code snippet creates a custom tooltip. The tooltip header displays the appointment’s subject (context.Appointment.Subject), the accepted status if it is true, and predefined buttons. The tooltip body displays the appointment’s subject (context.Appointment.Subject) and resource (context.Resource.Caption).

razor
@using Data

<DxScheduler StartDate="@DateTime.Today" 
             DataStorage="@DataStorage"
             GroupType="SchedulerGroupType.Date"
             ShowAppointmentTooltip = "true">
    <Views>
        <DxSchedulerDayView DayCount="2" ShowWorkTimeOnly="true"/>
    </Views>
    <AppointmentTooltipHeaderTemplate>
        <div class="tooltip-text-header">@context.Appointment.Subject</div>
        @if (IsAccepted(context))
        {
            <div class="my-margin">(Accepted)</div>
        }
        <DxSchedulerShowAppointmentCompactFormButton></DxSchedulerShowAppointmentCompactFormButton>
        <DxSchedulerDeleteAppointmentButton></DxSchedulerDeleteAppointmentButton>
        <DxSchedulerCloseAppointmentButton></DxSchedulerCloseAppointmentButton>
    </AppointmentTooltipHeaderTemplate>
    <AppointmentTooltipTemplate>
        <span class="my-align">
            @context.Appointment.Subject
        </span>
        @if (context.Resource != null)
        {
            <span class="my-align">
                (@context.Resource.Caption)
            </span>
        }
    </AppointmentTooltipTemplate>
</DxScheduler>

@code {
    DxSchedulerDataStorage DataStorage = new DxSchedulerDataStorage() {
            AppointmentsSource = ResourceAppointmentCollection.GetAppointments(),
            AppointmentMappings = new DxSchedulerAppointmentMappings() {
                Type = "AppointmentType",
                Start = "StartDate",
                End = "EndDate",
                Subject = "Caption",
                AllDay = "AllDay",
                Location = "Location",
                Description = "Description",
                LabelId = "Label",
                StatusId = "Status",
                RecurrenceInfo = "Recurrence",
                ResourceId = "ResourceId",
                CustomFieldMappings = new List<DxSchedulerCustomFieldMapping> {
                new DxSchedulerCustomFieldMapping{ Name = "Accepted", Mapping = "Accepted" }
            }
            },
            ResourcesSource = ResourceAppointmentCollection.GetResourcesForGrouping(),
            ResourceMappings = new DxSchedulerResourceMappings() {
                Id = "Id",
                Caption = "Name",
                BackgroundCssClass = "BackgroundCss",
                TextCssClass = "TextCss"
            }
        };
    bool IsAccepted(SchedulerAppointmentTooltipInfo tooltipInfo) => 
        (bool)tooltipInfo.CustomFields["Accepted"];
}
csharp
using System;

namespace Scheduler.Data
{
    public class ResourceAppointment {
        public ResourceAppointment() { }
        public int AppointmentType { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public string Caption { get; set; }
        public string Description { get; set; }
        public string Location { get; set; }
        public int? Label { get; set; }
        public int Status { get; set; }
        public bool AllDay { get; set; }
        public string Recurrence { get; set; }
        public int? ResourceId { get; set; }
        public bool Accepted { get; set; }
    }
}
csharp
using System;
using System.Collections.Generic;
using System.Linq;

namespace Scheduler.Data {

    public static partial class ResourceAppointmentCollection {
        public static List<ResourceAppointment> GetAppointments() {
            DateTime date = DateTime.Now.Date;
            var dataSource = new List<ResourceAppointment>() {
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Install New Router in Dev Room",
                    StartDate = date + (new TimeSpan(0, 10, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 12, 0, 0)),
                    Status = 1,
                    ResourceId = 0
                },
                new ResourceAppointment {
                    Caption = "Upgrade Personal Computers",
                    Accepted = false,
                    StartDate = date + (new TimeSpan(0, 13, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 14, 30, 0)),
                    Status = 1,
                    ResourceId = 0
                },
                new ResourceAppointment {
                    Caption = "Website Redesign Plan",
                    Accepted = false,
                    StartDate = date + (new TimeSpan(1, 9, 30, 0)),
                    EndDate = date + (new TimeSpan(1, 11, 30, 0)),
                    Status = 1,
                    ResourceId = 0
                },
                new ResourceAppointment {
                    Caption = "New Brochures",
                    Accepted = true,
                    StartDate = date + (new TimeSpan(1, 13, 30, 0)),
                    EndDate = date + (new TimeSpan(1, 15, 15, 0)),
                    Status = 1,
                    ResourceId = 0
                },
                new ResourceAppointment {
                    Caption = "Book Flights to San Fran for Sales Trip",
                    Accepted = false,
                    StartDate = date + (new TimeSpan(1, 12, 0, 0)),
                    EndDate = date + (new TimeSpan(1, 13, 0, 0)),
                    AllDay = true,
                    Status = 1,
                    ResourceId = 0
                },
                new ResourceAppointment {
                    Caption = "Approve Personal Computer Upgrade Plan",
                    Accepted = true,
                    StartDate = date + (new TimeSpan(0, 10, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 12, 0, 0)),
                    Status = 1
                },
                new ResourceAppointment {
                    Caption = "Final Budget Review",
                    Accepted = true,
                    StartDate = date + (new TimeSpan(0, 13, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 15, 0, 0)),
                    Status = 1,
                    ResourceId = 1
                },
                new ResourceAppointment {
                    Caption = "Install New Database",
                    Accepted = false,
                    StartDate = date + (new TimeSpan(0, 9, 45, 0)),
                    EndDate = date + (new TimeSpan(1, 11, 15, 0)),
                    Status = 1,
                    ResourceId = 1
                },
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Approve New Online Marketing Strategy",
                    StartDate = date + (new TimeSpan(1, 12, 0, 0)),
                    EndDate = date + (new TimeSpan(1, 14, 0, 0)),
                    Status = 1,
                    ResourceId = 1
                },
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Customer Workshop",
                    StartDate = date + (new TimeSpan(0, 11, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 12, 0, 0)),
                    AllDay = true,
                    Status = 1,
                    ResourceId = 2
                },
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Prepare 2021 Marketing Plan",
                    StartDate = date + (new TimeSpan(0, 11, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 13, 30, 0)),
                    Status = 1,
                    ResourceId = 2
                },
                new ResourceAppointment {
                    Accepted = false,
                    Caption = "Brochure Design Review",
                    StartDate = date + (new TimeSpan(0, 14, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 15, 30, 0)),
                    Status = 1,
                    ResourceId = 2
                },
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Create Icons for Website",
                    StartDate = date + (new TimeSpan(1, 10, 0, 0)),
                    EndDate = date + (new TimeSpan(1, 11, 30, 0)),
                    Status = 1,
                    ResourceId = 1
                },
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Launch New Website",
                    StartDate = date + (new TimeSpan(1, 12, 20, 0)),
                    EndDate = date + (new TimeSpan(1, 14, 0, 0)),
                    Status = 1,
                    ResourceId = 2
                },
                new ResourceAppointment {
                    Accepted = false,
                    Caption = "Upgrade Server Hardware",
                    StartDate = date + (new TimeSpan(1, 9, 0, 0)),
                    EndDate = date + (new TimeSpan(1, 12, 0, 0)),
                    Status = 1,
                    ResourceId = 2
                },
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Book Flights to San Fran for Sales Trip",
                    StartDate = date + (new TimeSpan(0, 14, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 17, 0, 0)),
                    Status = 1,
                    ResourceId = 3
                },
                new ResourceAppointment {
                    Accepted = true,
                    Caption = "Approve New Online Marketing Strategy",
                    StartDate = date + (new TimeSpan(0, 12, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 15, 0, 0)),
                    Status = 1,
                    ResourceId = 4
                }
            };
            return dataSource;
        }

        public static List<Resource> GetResourcesForGrouping() {
            return GetResources().Take(3).ToList();
        }

        public static List<Resource> GetResources() {
            return new List<Resource>() {
                new Resource() { Id=0 , Name="John Heart", GroupId=100, BackgroundCss="dxbl-green-color", TextCss="text-white" },
                new Resource() { Id=1 , Name="Samantha Bright", GroupId=101, BackgroundCss="dxbl-orange-color", TextCss="text-white" },
                new Resource() { Id=2 , Name="Arthur Miller", GroupId=100, BackgroundCss="dxbl-purple-color", TextCss="text-white" },
                new Resource() { Id=3 , Name="Robert Reagan", GroupId=101, BackgroundCss="dxbl-indigo-color", TextCss="text-white" },
                new Resource() { Id=4 , Name="Greta Sims", GroupId=100, BackgroundCss="dxbl-red-color", TextCss="text-white" }
            };
        }

        public static List<Resource> GetResourceGroups() {
            return new List<Resource>() {
                new Resource() { Id=100, Name="Sales and Marketing", IsGroup=true },
                new Resource() { Id=101, Name="Engineering", IsGroup=true }
            };
        }
    }
}
csharp
namespace Scheduler.Data {
    public class Resource {
        public int Id { get; set; }
        public int? GroupId { get; set; }
        public string Name { get; set; }
        public bool IsGroup { get; set; }
        public string TextCss { get; set; }
        public string BackgroundCss { get; set; }
        public string ImageFileName => $"employees/{Id + 1}.jpg";
        public override bool Equals(object obj) {
            Resource resource = obj as Resource;
            return resource != null && resource.Id == Id;
        }
        public override int GetHashCode() {
            return Id;
        }
    }
}
css
.tooltip-text-header {
    margin-right: auto;
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
}
.my-margin {
    margin-right: .3em;
}
.my-align {
    vertical-align: middle;
}

Run Demo: Scheduler - Appointment Tooltip Template

Default Appointment Form

A Scheduler can show two forms when users create and edit appointments:

  • The compact edit form - Appears when a user creates or edits an appointment.
  • The extended edit form - Opens when a user clicks the expand button in the compact edit form.

Extended Appointment Form

Compact Appointment Form

Use the AppointmentFormMode property to specify which form is available for users.

razor
@using Data

<DxScheduler StartDate="@DateTime.Today"
             DataStorage="@DataStorage"
             AppointmentFormMode="SchedulerAppointmentFormMode.EditForm">
    <DxSchedulerWeekView/>
</DxScheduler>

@code {
    DxSchedulerDataStorage DataStorage = new DxSchedulerDataStorage() {
        AppointmentsSource = AppointmentCollection.GetAppointments(),
        AppointmentMappings = new DxSchedulerAppointmentMappings() {
            Type = "AppointmentType",
            Start = "StartDate",
            End = "EndDate",
            Subject = "Caption",
            AllDay = "AllDay",
            Location = "Location",
            Description = "Description",
            LabelId = "Label",
            StatusId = "Status",
            RecurrenceInfo = "Recurrence"
        }
    };
}
csharp
using System;

namespace Scheduler.Data
{
    public class Appointment {
        public Appointment() { }
        public int AppointmentType { get; set; }
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public string Caption { get; set; }
        public string Description { get; set; }
        public string Location { get; set; }
        public int? Label { get; set; }
        public int Status { get; set; }
        public bool AllDay { get; set; }
        public string Recurrence { get; set; }
        public int? ResourceId { get; set; }
        public bool Accepted { get; set; }
    }
}
csharp
using System;
using System.Collections.Generic;
using System.Globalization;

namespace Scheduler.Data
{
    public static partial class AppointmentCollection {
        public static List<Appointment> GetAppointments()
        {
            DateTime date = DateTime.Today;
            var dataSource = new List<Appointment>() {
               new Appointment {
                   Caption = "Upgrade Personal Computers",
                   StartDate = date + (new TimeSpan(0, 14, 0, 0)),
                   EndDate = date + (new TimeSpan(0, 16, 30, 0)),
                   Label = 1,
                   Status = 1
               },
               new Appointment {
                   Caption = "Install New Router in Dev Room",
                   StartDate = date + (new TimeSpan(0, 11, 30, 0)),
                   EndDate = date + (new TimeSpan(0, 13, 30, 0)),
                   Label = 6,
                   Status = 1
               },
               new Appointment {
                   Caption = "New Brochures",
                   StartDate = date + (new TimeSpan(1, 15, 00, 0)),
                   EndDate = date + (new TimeSpan(1, 16, 45, 0)),
                   Label = 8,
                   Status = 1
               },
               new Appointment {
                   Caption = "Approve Personal Computer Upgrade Plan",
                   StartDate = date + (new TimeSpan(3, 13, 30, 0)),
                   EndDate = date + (new TimeSpan(3, 16, 0, 0)),
                   Label = 1,
                   Status = 1
               },
               new Appointment {
                   Caption = "Customer Workshop",
                   StartDate = date + (new TimeSpan(4, 11, 0, 0)),
                   EndDate = date + (new TimeSpan(4, 12, 0, 0)),
                   AllDay = true,
                   Label = 8,
                   Status = 1
               },
               new Appointment {
                   Caption = "Upgrade Server Hardware",
                   StartDate = date + (new TimeSpan(6, 11, 0, 0)),
                   EndDate = date + (new TimeSpan(6, 13, 30, 0)),
                   Label = 6,
                   Status = 1
               },
               new Appointment {
                   AppointmentType = 1,
                   Caption = "Daily Meeting",
                   StartDate = date + (new TimeSpan(0, 9, 00, 0)),
                   EndDate = date + (new TimeSpan(0, 10, 00, 0)),
                   Label = 10,
                   Status = 1,
                   Recurrence = string.Format("<RecurrenceInfo Type=\"0\" Start=\"{0}\" Range=\"1\" OccurrenceCount=\"10\" Frequency =\"1\" Id=\"72e3db8f-cdb6-4aaa-afe1-e3c6b80ce995\"/>", ToString(date + (new TimeSpan(0, 9, 00, 0))))
               }
           };
            return dataSource;
        }

        private static string ToString(DateTime dateTime) {
            return dateTime.ToString(CultureInfo.InvariantCulture);
        }
    }
}

You can change the Repeat item list in default appointment forms. Refer to Specify Available Recurrence Modes.

You can also create a custom appointment form.

Custom Appointment Form

Use the following properties to create a custom appointment edit form:

You can use predefined and custom layout items to construct the layout in the same way as when you use the DxFormLayout component.

You can also add predefined and custom buttons to appointment forms and their headers.

Run Demo: Scheduler - Custom Appointment Form

View Example: Customize Appointment Forms

YouTube video

Predefined Layout Items

You can add the following predefined layout items to set up the layout. They correspond to the items of the default appointment form.

razor
<DxScheduler StartDate="@DateTime.Today"
             DataStorage="@DataStorage"
             ActiveViewType="SchedulerViewType.WorkWeek">
    <Views>
        <DxSchedulerWorkWeekView VisibleTime="@(new DxSchedulerTimeSpanRange(TimeSpan.FromHours(8), 
                                 TimeSpan.FromHours(19)))">
            @*...*@
        </DxSchedulerWorkWeekView>
    </Views>
    <AppointmentFormLayout >
        <DxSchedulerSubjectFormLayoutItem/>
        <DxSchedulerAllDayFormLayoutItem/>
        <DxSchedulerStartDateFormLayoutItem/>
        <DxSchedulerStartTimeFormLayoutItem/>
        <DxSchedulerEndDateFormLayoutItem/>
        <DxSchedulerEndTimeFormLayoutItem/>
        <DxSchedulerRepeatFormLayoutItem/>
        <DxSchedulerLocationFormLayoutItem/>
        <DxSchedulerLabelFormLayoutItem/>
        <DxSchedulerStatusFormLayoutItem/>
        <DxSchedulerResourceFormLayoutItem/>
        <DxSchedulerDescriptionFormLayoutItem/>
    </AppointmentFormLayout>
</DxScheduler>

You can customize settings of these predefined items. For instance, show or hide an icon, assign a custom icon, change a caption, and much more. Refer to an item’s member list for additional information.

Custom Layout Items

If predefined items do not suit your requirements, you can use a custom layout item. Add a DxSchedulerCustomFormLayoutItem to the form layout and use its Template property to define item content. For example, you can display an editor for a custom appointment property.

AppointmentFormLayout and AppointmentCompactFormLayout accept a SchedulerAppointmentFormInfo object as the Context parameter. You can use this parameter in the layout item template to obtain information about appointment settings.

If appointments contain custom properties, implement a SchedulerAppointmentFormInfo descendant and declare the corresponding properties in the class. Handle the AppointmentFormShowing event and assign a custom descendant instance to the event’s FormInfo argument.

razor
<DxScheduler StartDate="@DateTime.Today"
             DataStorage="@DataStorage"
             ActiveViewType="SchedulerViewType.WorkWeek"
             AppointmentFormShowing="OnAppointmentFormShowing">
    @*...*@
    <AppointmentFormLayout Context="formInfo">
        @*...*@
        <DxSchedulerCustomFormLayoutItem ColSpanMd="12">
            <Template>
                <div class="my-style">
                    <DxCheckBox @bind-Checked="@(((CustomAppointmentFormInfo)formInfo).IsAccepted)" 
                                Alignment="CheckBoxContentAlignment.Right">Accept</DxCheckBox>
                </div>
            </Template>
        </DxSchedulerCustomFormLayoutItem>
    </AppointmentFormLayout>
</DxScheduler>

@code {
    DxScheduler scheduler { get; set; }

    public class CustomAppointmentFormInfo : SchedulerAppointmentFormInfo {
        public CustomAppointmentFormInfo(DxSchedulerAppointmentItem AppointmentItem, 
                DxSchedulerDataStorage DataStorage, DxScheduler scheduler) : base(AppointmentItem, DataStorage, scheduler) { }

        public bool IsAccepted {
            get { return (bool)CustomFields["IsAccepted"]; }
            set { CustomFields["IsAccepted"] = value; }
        }
    }

    void OnAppointmentFormShowing(SchedulerAppointmentFormEventArgs args) {
        args.FormInfo = new CustomAppointmentFormInfo(args.Appointment, DataStorage, scheduler);
    }

    DxSchedulerDataStorage DataStorage = new DxSchedulerDataStorage() {
        AppointmentsSource = AppointmentCollection.GetAppointments(),
        AppointmentMappings = new DxSchedulerAppointmentMappings() {
            Type = "AppointmentType",
            Start = "StartDate",
            End = "EndDate",
            Subject = "Caption",
            AllDay = "AllDay",
            Location = "Location",
            Description = "Description",
            LabelId = "Label",
            StatusId = "Status",
            RecurrenceInfo = "Recurrence"
        }
    };
    // ...
}
css
.my-style {
    margin-left: auto; 
    margin-top: 14px;
}

Predefined Buttons

You can add the following predefined buttons to appointment forms, tooltips and their headers:

razor
<DxScheduler StartDate="@DateTime.Today"
             DataStorage="@DataStorage"
             ActiveViewType="SchedulerViewType.WorkWeek">
    <Views>
        <DxSchedulerWorkWeekView VisibleTime="@(new DxSchedulerTimeSpanRange(TimeSpan.FromHours(8), 
                                 TimeSpan.FromHours(19)))">
            @*...*@
        </DxSchedulerWorkWeekView>
    </Views>
    <AppointmentFormHeaderTemplate>
        <DxSchedulerSaveAppointmentChangesButton/>
        <DxSchedulerDeleteAppointmentButton/>
        <DxSchedulerDiscardAppointmentChangesButton/>
    </AppointmentFormHeaderTemplate>
</DxScheduler>

You can customize settings of these predefined buttons. For instance, you can show or hide an icon and change a button’s text or tooltip.

Custom Buttons

If predefined buttons do not suit your requirements, you can create a custom button. Add a DxButton to the form layout or form header, and handle its Click event. You can use the following methods in the handler:

The following code snippet creates a button that closes the edit form.

razor
<DxScheduler StartDate="@DateTime.Today"
             DataStorage="@DataStorage"
             @ref="Scheduler">
    <Views>
        <DxSchedulerWeekView ShowWorkTimeOnly="false"
                             TimeIndicatorVisibility="SchedulerTimeIndicatorVisibility.Never"
                             TimeScale="@(new TimeSpan(0,15,0))"
                             WorkTime="@(new DxSchedulerTimeSpanRange(TimeSpan.FromHours(9), TimeSpan.FromHours(18)))"
                             VisibleTime="@(new DxSchedulerTimeSpanRange(TimeSpan.FromHours(8), TimeSpan.FromHours(19)))">
        </DxSchedulerWeekView>
    </Views>
    <AppointmentFormHeaderTemplate>
        <div class="popup-text-header">@context.Subject</div>
        <DxButton Click="@(() => Scheduler.ClosePopupAsync())"
                  Text="Close"
                  IconCssClass="btn-icon-close"
                  RenderStyle="ButtonRenderStyle.None"
                  CssClass="custom-button">
        </DxButton>
    </AppointmentFormHeaderTemplate>
</DxScheduler>

@code {
    DxScheduler Scheduler { get; set; }
}

Form Validation

Follow the steps below to enable appointment form validation:

  1. Implement a SchedulerAppointmentFormInfo class descendant and mark appointment properties with data annotation attributes.
  2. Handle the AppointmentFormShowing event and assign a custom descendant instance to the event argument’s FormInfo property.
  3. Set the Scheduler’s ValidateEditForm property to true.
  4. Add a DxSchedulerCustomFormLayoutItem to the form layout and use the ValidationSummary component in the item template to display validation messages.

Important

You should not rely on form validation alone to secure your Blazor-powered app. Form validation is designed to improve usability. A threat actor can bypass validation and send malicious data to the server. To minimize security related threats/risks, you must validate user input using multiple strategies. Refer to the following topic for additional information: Validate User Input.

razor
@using System.ComponentModel.DataAnnotations

<DxScheduler StartDate="@DateTime.Today"
             DataStorage="@DataStorage"
             ActiveViewType="SchedulerViewType.WorkWeek"
             AppointmentFormShowing="OnAppointmentFormShowing"
             ValidateEditForm="true">
    @*...*@
    <AppointmentFormLayout>
        @*...*@
        <DxSchedulerCustomFormLayoutItem ColSpanMd="12">
            <Template>
                <ValidationSummary />
            </Template>
        </DxSchedulerCustomFormLayoutItem>
    </AppointmentFormLayout>
</DxScheduler>

@code {
    DxScheduler scheduler { get; set; }

    public class CustomAppointmentFormInfo : SchedulerAppointmentFormInfo {
        public CustomAppointmentFormInfo(DxSchedulerAppointmentItem AppointmentItem, 
                DxSchedulerDataStorage DataStorage, DxScheduler scheduler) : base(AppointmentItem, DataStorage, scheduler) { }

        [Required]
        public override string Subject {
            get { return base.Subject; }
            set { base.Subject = value; }
        }
        // ...
    }

    void OnAppointmentFormShowing(SchedulerAppointmentFormEventArgs args) {
        args.FormInfo = new CustomAppointmentFormInfo(args.Appointment, DataStorage, scheduler);
    }
    // ...
}

Complete Example

The following example demonstrates how to:

  • Add the IsAccepted custom property to appointments as described in the following topic: Custom Fields.
  • Use AppointmentFormLayout and AppointmentCompactFormLayout properties to create a layout for the extended and compact edit forms.
  • Add predefined layout items ( Subject , All day , Start , End to both forms; Location and Description to the extended form).
  • Add two custom layout items: the first item displays the IsAccepted property’s editor, the second item shows validation summary.
  • Mark the Subject appointment property with the RequiredAttribute.
  • Set the Scheduler’s ValidateEditForm property to true to enable appointment form validation.

Extended Appointment Form

Compact Appointment Form

razor
@using System.ComponentModel.DataAnnotations

<DxScheduler StartDate="@DateTime.Today"
             DataStorage="@DataStorage"
             ActiveViewType="SchedulerViewType.WorkWeek"
             AppointmentFormShowing="OnAppointmentFormShowing"
             ValidateEditForm="true"
             CssClass="mw-1100">
    <Views>
        <DxSchedulerWorkWeekView VisibleTime="@(new DxSchedulerTimeSpanRange(TimeSpan.FromHours(8), 
                                 TimeSpan.FromHours(19)))">
            <HorizontalAppointmentTemplate>
                <div class="demo-sc-apt @((bool)context.CustomFields["IsAccepted"] ? 
                           "demo-sc-accepted " : "")">
                    <div class="card demo-apt-bg dxbl-purple-color"></div>
                    <div class="card shadow-sm p-1 demo-sc-apt-content text-white">
                        @context.Appointment.Subject
                    </div>
                </div>
            </HorizontalAppointmentTemplate>
            <VerticalAppointmentTemplate>
                <div class="shadow-sm demo-sc-apt @((bool)context.CustomFields["IsAccepted"] ? 
                           "demo-sc-accepted" : "")">
                    <div class="card demo-apt-bg dxbl-purple-color"></div>
                    <div class="card demo-sc-apt-content text-white">
                        <div class="card demo-sc-status-container">
                            <div class="card demo-apt-status dxbl-purple-color"></div>
                        </div>
                        <div class="demo-apt-subject">
                            @context.Appointment.Subject
                        </div>
                    </div>
                </div>
            </VerticalAppointmentTemplate>
        </DxSchedulerWorkWeekView>
    </Views>
    <AppointmentFormLayout Context="formInfo">
        <DxSchedulerSubjectFormLayoutItem/>
        <DxSchedulerAllDayFormLayoutItem/>
        <DxSchedulerStartDateFormLayoutItem/>
        <DxSchedulerStartTimeFormLayoutItem/>
        <DxSchedulerEndDateFormLayoutItem/>
        <DxSchedulerEndTimeFormLayoutItem/>
        <DxSchedulerLocationFormLayoutItem/>
        <DxSchedulerDescriptionFormLayoutItem/>
        <DxSchedulerCustomFormLayoutItem ColSpanMd="12">
            <Template>
                <div class="my-style">
                    <DxCheckBox @bind-Checked="@(((CustomAppointmentFormInfo)formInfo).IsAccepted)"
                        Alignment="CheckBoxContentAlignment.Right">Accept</DxCheckBox>
                </div>
            </Template>
        </DxSchedulerCustomFormLayoutItem>
        <DxSchedulerCustomFormLayoutItem ColSpanMd="12">
            <Template>
                <ValidationSummary />
            </Template>
        </DxSchedulerCustomFormLayoutItem>
    </AppointmentFormLayout>
    <AppointmentCompactFormLayout Context="formInfo">
        <DxSchedulerSubjectFormLayoutItem></DxSchedulerSubjectFormLayoutItem>
        <DxSchedulerAllDayFormLayoutItem></DxSchedulerAllDayFormLayoutItem>
        <DxSchedulerStartDateFormLayoutItem></DxSchedulerStartDateFormLayoutItem>
        <DxSchedulerStartTimeFormLayoutItem></DxSchedulerStartTimeFormLayoutItem>
        <DxSchedulerEndDateFormLayoutItem></DxSchedulerEndDateFormLayoutItem>
        <DxSchedulerEndTimeFormLayoutItem></DxSchedulerEndTimeFormLayoutItem>
        <DxSchedulerCustomFormLayoutItem ColSpanMd="12">
            <Template>
                <div class="my-style">
                    <DxCheckBox @bind-Checked="@(((CustomAppointmentFormInfo)formInfo).IsAccepted)"
                        Alignment="CheckBoxContentAlignment.Right">Accept</DxCheckBox>
                </div>
            </Template>
        </DxSchedulerCustomFormLayoutItem>
        <DxSchedulerCustomFormLayoutItem ColSpanMd="12">
            <Template>
                <ValidationSummary />
            </Template>
        </DxSchedulerCustomFormLayoutItem>
    </AppointmentCompactFormLayout>
</DxScheduler>

@code {
    public class CustomAppointmentFormInfo : SchedulerAppointmentFormInfo {
        public CustomAppointmentFormInfo(DxSchedulerAppointmentItem AppointmentItem, DxSchedulerDataStorage DataStorage)
            : base(AppointmentItem, DataStorage) { }

        [Required]
        public override string Subject {
            get { return base.Subject; }
            set { base.Subject = value; }
        }

        public bool IsAccepted {
            get { return (bool)CustomFields["IsAccepted"]; }
            set { CustomFields["IsAccepted"] = value; }
        }
    }

    void OnAppointmentFormShowing(SchedulerAppointmentFormEventArgs args) {
        args.FormInfo = new CustomAppointmentFormInfo(args.Appointment, DataStorage);
    }

    DxSchedulerDataStorage DataStorage = new DxSchedulerDataStorage() {
        AppointmentsSource = AppointmentCollection.GetAppointments(),
        AppointmentMappings = new DxSchedulerAppointmentMappings() {
            Type = "AppointmentType",
            Start = "StartDate",
            End = "EndDate",
            Subject = "Caption",
            AllDay = "AllDay",
            Location = "Location",
            Description = "Description",
            CustomFieldMappings = new List<DxSchedulerCustomFieldMapping> {
                new DxSchedulerCustomFieldMapping { Name = "IsAccepted", Mapping = "Accepted" }
            }
        }
    };
}
csharp
public class Appointment {
    public Appointment() {}
    public int AppointmentType { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public string Caption { get; set; }
    public string Description { get; set; }
    public string Location { get; set; }
    public int Label { get; set; }
    public int Status { get; set; }
    public bool AllDay { get; set; }
    public string Recurrence { get; set; }
    public bool Accepted { get; set; }
}
csharp
public static partial class AppointmentCollection {
    public static List<Appointment> GetAppointments() {
        DateTime date = DateTime.Today;
        var dataSource = new List<Appointment>() {
                new Appointment {
                    Caption = "Install New Router in Dev Room",
                    StartDate = date + (new TimeSpan(0, 10, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 12, 30, 0)),
                    Label = 6,
                    Status = 4
                },
                new Appointment {
                    Caption = "Upgrade Personal Computers",
                    StartDate = date + (new TimeSpan(0, 13, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 15, 30, 0)),
                    Label = 1,
                    Status = 4
                },
                new Appointment {
                    Caption = "Website Redesign Plan",
                    StartDate = date + (new TimeSpan(1, 9, 30, 0)),
                    EndDate = date + (new TimeSpan(1, 12, 0, 0)),
                    Label = 1,
                    Status = 1,
                    Accepted = true
                },
                new Appointment {
                    Caption = "New Brochures",
                    StartDate = date + (new TimeSpan(1, 13, 30, 0)),
                    EndDate = date + (new TimeSpan(1, 15, 15, 0)),
                    Label = 8,
                    Status = 2,
                    Accepted = true
                },
                // ...
            };
        return dataSource;
    }
}
css
.my-style {
    margin-left: auto; 
    margin-top: 14px;
}

Run Demo: Scheduler - Custom Fields and Appointment Form

Recurrence Form

When a user creates or edits a recurring appointment and selects a value other than Never in the Appointment form’s Repeat section, the Recurrence form appears. This form has a different layout for each rule type: Daily, Weekly, Monthly, Yearly. When a user fills in form fields and clicks Save , field values are saved to the DxSchedulerRecurrenceInfo object.

In the Recurrence form, you can change the list of repeat end items and the list of week days available for Repeat Monthly and Repeat Yearly appointments. To do this, handle the Scheduler’s AppointmentFormShowing event, use the RecurrenceFormInfo property to get information about the Recurrence form, modify RepeatEndItems and WeekDayItems properties.

The following code snippet does the following:

razor
<DxScheduler DataStorage="@DataStorage" 
             AppointmentFormMode="SchedulerAppointmentFormMode.EditForm"
             AppointmentFormShowing="OnAppointmentFormShowing">
    <DxSchedulerWeekView ShowWorkTimeOnly="true" />
</DxScheduler>

@code {
    DxSchedulerDataStorage DataStorage = new DxSchedulerDataStorage() {
            AppointmentsSource = AppointmentCollection.GetAppointments(),
            AppointmentMappings = new DxSchedulerAppointmentMappings() {
                Start = "StartDate",
                End = "EndDate",
                Subject = "Caption",
                LabelId = "Label",
                StatusId = "Status"
            }
        };

    void OnAppointmentFormShowing(SchedulerAppointmentFormEventArgs args) {
        args.FormInfo.RecurrenceFormInfo.RepeatEndItems = new List<SchedulerRecurrenceRange>() {
            SchedulerRecurrenceRange.OccurrenceCount,
            SchedulerRecurrenceRange.EndByDate
        };

        args.FormInfo.RecurrenceFormInfo.WeekDayItems.Remove(SchedulerWeekDays.WeekendDays);
    }
}
csharp
public class Appointment {
    public Appointment() {}
    public int AppointmentType { get; set; }
    public DateTime StartDate { get; set; }
    public DateTime EndDate { get; set; }
    public string Caption { get; set; }
    public string Description { get; set; }
    public string Location { get; set; }
    public int Label { get; set; }
    public int Status { get; set; }
    public bool AllDay { get; set; }
    public string Recurrence { get; set; }
    public bool Accepted { get; set; }
}
csharp
public static partial class AppointmentCollection {
    public static List<Appointment> GetAppointments() {
        DateTime date = DateTime.Today;
        var dataSource = new List<Appointment>() {
                new Appointment {
                    Caption = "Install New Router in Dev Room",
                    StartDate = date + (new TimeSpan(0, 10, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 12, 30, 0)),
                    Label = 6,
                    Status = 4
                },
                new Appointment {
                    Caption = "Upgrade Personal Computers",
                    StartDate = date + (new TimeSpan(0, 13, 0, 0)),
                    EndDate = date + (new TimeSpan(0, 15, 30, 0)),
                    Label = 1,
                    Status = 4
                },
                new Appointment {
                    Caption = "Website Redesign Plan",
                    StartDate = date + (new TimeSpan(1, 9, 30, 0)),
                    EndDate = date + (new TimeSpan(1, 12, 0, 0)),
                    Label = 1,
                    Status = 1,
                    Accepted = true
                },
                new Appointment {
                    Caption = "New Brochures",
                    StartDate = date + (new TimeSpan(1, 13, 30, 0)),
                    EndDate = date + (new TimeSpan(1, 15, 15, 0)),
                    Label = 8,
                    Status = 2,
                    Accepted = true
                },
                // ...
            };
        return dataSource;
    }
}

You can also customize the Repeat field in default appointment forms. Refer to Specify Available Recurrence Modes.

Tooltip and Form Events

The DevExpress Blazor Scheduler component allows you to handle events that fire when users open/close tooltips and edit forms. You can cancel operations or execute additional logic.

EventDescription
AppointmentTooltipShowingFires before an appointment tooltip is shown.
AppointmentTooltipShownFires after an appointment tooltip is shown.
AppointmentTooltipClosingFires before an appointment tooltip is closed.
AppointmentTooltipClosedFires after an appointment tooltip is closed.
AppointmentFormShowingFires before the appointment form is shown.
AppointmentFormShownFires after an appointment form is shown.
AppointmentFormClosingFires before the appointment form is closed.
AppointmentFormClosedFires after the appointment form is closed.