Articles in this section
Category / Section

How to add custom appointment editor in WPF Scheduler (Calendar)

4 mins read

Scheduler allows to add and edit the appointments using custom editor with the help of AppointmentEditorOpening event.

 

XAML

Custom appointment editor with default AppointmentEditor options.

<Window x:Class="WpfScheduler.Helper.AppointmentEditor"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:syncfusion="http://schemas.syncfusion.com/wpf" 
        mc:Ignorable="d"
        HorizontalAlignment="Center"
        VerticalAlignment="Center"
        ResizeMode="NoResize"
        Title="New Event" Height="500" Width="500">
    <Window.Resources>
        <syncfusion:ReminderTimeIntervalConverter x:Key="ReminderTimeIntervalConverter"/>
    </Window.Resources>
    <Grid Margin="5">
        <Grid.RowDefinitions>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition />
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <StackPanel Grid.Row="0" Margin="5" Grid.ColumnSpan="2" Orientation="Vertical">
            <Label x:Name="TitleLabel" 
               Content="Subject" />
            <TextBox x:Name="Subject" 
                 HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch"
                     Height="25"
                 />
        </StackPanel>
        <StackPanel Grid.Row="0" Margin="5" Grid.Column="2" Grid.ColumnSpan="2" Orientation="Vertical">
            <Label x:Name="LocationLabel" 
               Content="Location" />
            <TextBox x:Name="location" 
                 HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch"
                     Height="25"
                 />
        </StackPanel>
        <StackPanel Grid.Row="1" Margin="5" Grid.ColumnSpan="2" Orientation="Vertical">
            <Label x:Name="StartLabel"  Content="Start Time" />
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="100"/>
                </Grid.ColumnDefinitions>
                <syncfusion:SfDatePicker x:Name="StartDatePicker" 
                                     VerticalAlignment="Top"
                                     HorizontalAlignment="Left"/>
                <GridSplitter Grid.Column="1" IsEnabled="False"/>
                <syncfusion:SfTimePicker x:Name="StartTimePicker"
                                     VerticalAlignment="Top"
                                         Grid.Column="2"
                                     HorizontalAlignment="Right"/>
            </Grid>
        </StackPanel>
        <StackPanel Grid.Row="1" Margin="5" Grid.Column="2" Grid.ColumnSpan="2" Orientation="Vertical">
            <Label x:Name="EndLabel" Content="End Time" />
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="100"/>
                </Grid.ColumnDefinitions>
                <syncfusion:SfDatePicker x:Name="EndDatePicker" 
                                     VerticalAlignment="Top"
                                     HorizontalAlignment="Left"/>
                <GridSplitter Grid.Column="1" IsEnabled="False"/>
                <syncfusion:SfTimePicker x:Name="EndTimePicker" 
                                     VerticalAlignment="Top"
                                         Grid.Column="2"
                                     HorizontalAlignment="Right"/>
            </Grid>
        </StackPanel>
        <CheckBox x:Name="allDay" Grid.Row="2" HorizontalAlignment="Stretch"  VerticalAlignment="Center" Margin="5" Content="All Day" />
        <CheckBox x:Name="timeZone" Grid.Row="2" Margin="5" Grid.Column="1" Checked="OnTimeZoneChecked" HorizontalAlignment="Stretch"  VerticalAlignment="Center" Content="Time Zone"/>
        <StackPanel x:Name="TimeZoneMenuPanel" Grid.Row="3" Grid.ColumnSpan="4" Visibility="Collapsed">
            <syncfusion:ComboBoxAdv x:Name="TimeZoneMenu"  
                                    Margin="8, 0, 0, 0" 
                                    Width="200"
                                    Height="24"
                                    HorizontalAlignment="Left"
                                    VerticalAlignment="Center"/>
        </StackPanel>
        <Grid Grid.Row="4" Grid.ColumnSpan="4">
            <Grid.RowDefinitions>
                <RowDefinition Height="*"/>
                <RowDefinition Height="auto"/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition />
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <Button x:Name="addRememainder" Grid.Row="0" Grid.ColumnSpan="1" Margin="5" Height="30" Click="OnAddRememainderClicked" Content="Add Remainder"/>
            <ListView x:Name="ReminderList"
                      Grid.Row="1" Grid.ColumnSpan="4"
                     Margin="7, 8, 0, 8"
                     Background="Transparent"
                     BorderThickness="0"
                     BorderBrush="Transparent">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel x:Name="ReminderStack" 
                                    Orientation="Horizontal" 
                                    Margin="3,0,0,0"              
                                    Background="Transparent">
                            <syncfusion:UpDown x:Name="ReminderTimeInterval"
                                    Margin="5,0,0,0" 
                                    Height="24"
                                    Width="100" 
                                    IsScrollingOnCircle="True"
                                    MinValue="0"
                                    Step="1" 
                                    NumberDecimalDigits="0"
                                    Value="{Binding Converter={StaticResource ReminderTimeIntervalConverter}, Path=ReminderTimeInterval, Mode=OneWay, ConverterParameter=TimeInterval}"/>
                            <syncfusion:ComboBoxAdv x:Name="ReminderTimeIntervalMenu"
                                    Margin="8, 0, 0, 0" 
                                    Width="80"
                                    Height="24"
                                    HorizontalAlignment="Left"
                                    VerticalAlignment="Center"
                                    SelectedIndex="{Binding Converter={StaticResource ReminderTimeIntervalConverter}, Path=ReminderTimeInterval, Mode=OneWay, ConverterParameter=TimeIntervalMenu}" />
                            <Button Height="24" x:Name="RemoveReminder"
                                    Width="25" Background="Transparent"
                                    Margin="6,0,0,0"
                                    Click="OnRemoveReminderClicked" >
                                <Image Height="19" Source="D:\2021Incident\May10KB1\new\SchedulerWPF\SchedulerWPF\Resource\delete.png" Width="19"/>
                            </Button>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </Grid>
        <StackPanel Grid.Row="5" Grid.ColumnSpan="4" Margin="5" Orientation="Vertical">
            <Label x:Name="descriptionLabel" 
               Content="Description" />
            <TextBox x:Name="description" 
                 HorizontalAlignment="Stretch" 
                 VerticalAlignment="Stretch"
                     Height="25"
                 />
        </StackPanel>
        <StackPanel Orientation="Horizontal" Margin="5" Grid.Row="7" Grid.ColumnSpan="4" HorizontalAlignment="Right" VerticalAlignment="Bottom">
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="100"/>
                    <ColumnDefinition Width="5"/>
                    <ColumnDefinition Width="100"/>
                </Grid.ColumnDefinitions>
                <Button x:Name="Save" Height="30" Grid.Column="0" Content="Save" Click="OnSaveClicked"/>
                <GridSplitter Grid.Column="1" IsEnabled="False"/>
                <Button x:Name="Delete" Height="30" Grid.Column="2" Content="Delete" Click="OnDeleteClicked"/>
                <GridSplitter Grid.Column="3" IsEnabled="False"/>
                <Button x:Name="Cancel" Height="30" Grid.Column="4" Content="Cancel" Click="OnCancelClicked"/>
            </Grid>
        </StackPanel>
    </Grid>
</Window>

C#

Handles custom appointment editor options.

public partial class AppointmentEditor : Window
{
    private SfScheduler scheduler;
    private ScheduleAppointment appointment;
 
    public AppointmentEditor(SfScheduler scheduler, ScheduleAppointment appointment, DateTime dateTime)
    {
        InitializeComponent();
        GetTimeZone();
        this.scheduler = scheduler;
        this.appointment = appointment;
        if (appointment != null)
        {
            this.Subject.Text = appointment.Subject;
            this.StartDatePicker.Value = appointment.StartTime.Date;
            this.EndDatePicker.Value = appointment.EndTime.Date;
            this.StartTimePicker.Value = appointment.StartTime;
            this.EndTimePicker.Value = appointment.EndTime;
            this.location.Text = appointment.Location;
            this.description.Text = appointment.Notes;
            this.allDay.IsChecked = appointment.IsAllDay;
            this.ReminderList.ItemsSource = (IList)appointment.Reminders;
            this.ReminderList.ItemContainerGenerator.StatusChanged += this.OnListViewItemGeneratorStatusChanged;
            this.timeZone.IsChecked = (appointment.StartTimeZone != null);
            if((bool)this.timeZone.IsChecked)
            {
                this.TimeZoneMenu.Text = appointment.StartTimeZone.ToString();
            }
        }
        else
        {
            this.StartDatePicker.Value = dateTime.Date;
            this.EndDatePicker.Value = dateTime.Date;
            this.StartTimePicker.Value = dateTime;
            this.EndTimePicker.Value = dateTime.AddHours(1);
        }
    }
 
    private void GetTimeZone()
    {
        this.TimeZoneMenu.ItemsSource = new List<string>()
        {
        "Samoa Standard Time",
        "Dateline Standard Time",
        "UTC-11",
        "Hawaiian Standard Time",
        "Alaskan Standard Time",
        "Pacific Standard Time",
        "Pacific Standard Time (Mexico)",
        "Mountain Standard Time",
        "Mountain Standard Time (Mexico)",
        "US Mountain Standard Time",
        "Canada Central Standard Time",
        "Central America Standard Time",
        "Central Standard Time",
        "Eastern Standard Time",
        "SA Pacific Standard Time",
        "US Eastern Standard Time",
        "Venezuela Standard Time",
        "Atlantic Standard Time",
        "Central Brazilian Standard Time",
        "Pacific SA Standard Time",
        "Paraguay Standard Time",
        "SA Western Standard Time",
        "Newfoundland Standard Time",
        "Argentina Standard Time",
        "Bahia Standard Time",
        "Greenland Standard Time",
        "E. South America Standard Time",
        "Montevideo Standard Time",
        "SA Eastern Standard Time",
        "UTC-02",
        "(UTC - 01:00) Azores Standard Time",
        "(UTC - 01:00) Cape Verde Standard Time",
        "(UTC) GMT Standard Time",
        "(UTC) Greenwich Standard Time",
        "(UTC) Morocco Standard Time",
        "(UTC) UTC",
        "Magadan Standard Time",
        "New Zealand Standard Time",
        "Russia Time Zone 11",
        "UTC+12",
        "Line Islands Standard Time",
        "Tonga Standard Time",
        };
    }
 
    private void OnCancelClicked(object sender, RoutedEventArgs e)
    {
        this.Close();
    }
 
    private void OnSaveClicked(object sender, RoutedEventArgs e)
    {
        if (appointment == null)
        {
            var scheduleAppointment = new ScheduleAppointment();
            scheduleAppointment.Subject = this.Subject.Text;
            scheduleAppointment.StartTime = this.StartDatePicker.Value.Value.Date.Add(this.StartTimePicker.Value.Value.TimeOfDay);
            scheduleAppointment.EndTime = this.EndDatePicker.Value.Value.Date.Add(this.EndTimePicker.Value.Value.TimeOfDay);
            scheduleAppointment.Location = this.location.Text;
            scheduleAppointment.IsAllDay = (bool)this.allDay.IsChecked;
            scheduleAppointment.Notes = this.description.Text;
            scheduleAppointment.Reminders=(ObservableCollection<SchedulerReminder>)this.ReminderList.ItemsSource;
                
            if ((bool)this.timeZone.IsChecked)
            {
                scheduleAppointment.StartTimeZone = this.TimeZoneMenu.Text;
                scheduleAppointment.EndTimeZone = this.TimeZoneMenu.Text;
            }
            if (this.scheduler.ItemsSource == null)
            {
                this.scheduler.ItemsSource = new ScheduleAppointmentCollection();
            }
 
            (this.scheduler.ItemsSource as ScheduleAppointmentCollection).Add(scheduleAppointment);
        }
        else
        {
            appointment.Subject = this.Subject.Text;
            appointment.StartTime = this.StartDatePicker.Value.Value.Date.Add(this.StartTimePicker.Value.Value.TimeOfDay);
            appointment.EndTime = this.EndDatePicker.Value.Value.Date.Add(this.EndTimePicker.Value.Value.TimeOfDay);
            appointment.Location = this.location.Text;
            appointment.IsAllDay = (bool)this.allDay.IsChecked;
            appointment.Notes = this.description.Text;
            appointment.Reminders = (ObservableCollection<SchedulerReminder>)this.ReminderList.ItemsSource;
            appointment.StartTimeZone = this.TimeZoneMenu.Text;
            appointment.EndTimeZone = this.TimeZoneMenu.Text;
        }
        this.Close();
    }
 
    protected override void OnClosing(CancelEventArgs e)
    {
        base.OnClosing(e);
        this.Save.Click -= this.OnSaveClicked;
        this.Cancel.Click -= this.OnCancelClicked;
        this.scheduler = null;
        this.appointment = null;
    }
 
    private void OnAddRememainderClicked(object sender, RoutedEventArgs e)
    {
 
        if (this.ReminderList != null)
        {
            var reminders = this.ReminderList.ItemsSource as IList;
            this.ReminderList.ItemContainerGenerator.StatusChanged += this.OnListViewItemGeneratorStatusChanged;
            if (reminders == null)
            {
                reminders = new ObservableCollection<SchedulerReminder>();
            }
            else if (reminders.Count == 5)
            {
                // Only maximum of 5 reminders allowed in editor window.
                return;
            }
            var newRemainder = new SchedulerReminder();
            reminders.Add(newRemainder);
            this.ReminderList.ItemsSource = reminders;
        }
    }
    private void OnListViewItemGeneratorStatusChanged(object sender, EventArgs e)
    {
        foreach (var reminder in this.ReminderList.Items)
        {
            var listViewItem = this.ReminderList.ItemContainerGenerator.ContainerFromItem(reminder) as ListViewItem;
            if (listViewItem == null)
            {
                continue;
            }
 
            //// Sets the reminder interval types.
            var reminderTimeIntervalMenu = VisualUtils.FindDescendant(listViewItem, typeof(ComboBoxAdv)) as ComboBoxAdv;
            if (reminderTimeIntervalMenu != null)
            {
                reminderTimeIntervalMenu.ItemsSource = new List<string>()
                {
                    SchedulerLocalizationResourceAccessor.Instance.GetString("MinutesIntervalTypeReminder", CultureInfo.CurrentUICulture),
                    SchedulerLocalizationResourceAccessor.Instance.GetString("HoursIntervalTypeReminder", CultureInfo.CurrentUICulture),
                    SchedulerLocalizationResourceAccessor.Instance.GetString("DaysIntervalTypeReminder", CultureInfo.CurrentUICulture),
                    SchedulerLocalizationResourceAccessor.Instance.GetString("WeeksIntervalTypeReminder", CultureInfo.CurrentUICulture),
                };
            }
        }
    } 
    private void OnRemoveReminderClicked(object sender, RoutedEventArgs e)
    {
        var button = sender as Button;
        var reminderCollection = this.ReminderList.ItemsSource as IList;
        reminderCollection.Remove(button.DataContext as SchedulerReminder);
    }
 
    private void OnTimeZoneChecked(object sender, RoutedEventArgs e)
    {
        if(this.timeZone.IsChecked==true)
        this.TimeZoneMenuPanel.Visibility = Visibility.Visible;
        else
            this.TimeZoneMenuPanel.Visibility = Visibility.Visible;
    }
 
    private void OnDeleteClicked(object sender, RoutedEventArgs e)
    {
        if(appointment != null)
        (this.scheduler.ItemsSource as ScheduleAppointmentCollection).Remove(appointment);
        this.Close();
    }
}

XAML

In AppointmentEditorOpening event, show the custom editor by cancelling the default AppointmentEditor.

private void Scheduler_AppointmentEditorOpening(object sender, AppointmentEditorOpeningEventArgs e)
{
    e.Cancel = true;
    var editor = new AppointmentEditor(this.scheduler, e.Appointment, e.DateTime);
    editor.ShowDialog();
}

View Sample in GitHub

A screenshot of a computer

Description automatically generated with medium confidence

 

Did you find this information helpful?
Yes
No
Help us improve this page
Please provide feedback or comments
Comments (0)
Please  to leave a comment
Access denied
Access denied