Custom Appointment
A very common scenario when using RadScheduler is the usage of custom appointments. When you create a Custom Appointment class you gain the ability to append additional properties to the base Appointment class, to add them to your custom AppointmentItem and optionally to its ToolTip, display them in the EditAppointment dialog while supporting the cancelation of editing.
Creating a Custom Appointment Class
To create a custom appointment class you can start off with either of the following approaches : you can implement the IAppointment interface or you can inherit from one of the classes that already implements this interface – AppointmentBase (the base implementation of the interface) or Appointment (an extended implementation). It is very important to provide your own implementations for the Copy and CopyFrom methods as they are used intensively in the editing process of the Scheduler control. If you choose to implement the interface, the Copy and CopyFrom methods will be automatically implemented for you, but if you take the second approach and inherit from one of the base classes, you should keep this in mind.
Let's create a simple task tracking system. For our Custom Appointment class we will inherit from Appointment. Our tracking system will need to show an additional field for the task progress – an indication of whether the task has finished or not. In order to enable editing in transactions of the new property we need to use the Storage method of the AppointmentBase class to access the instance which owns the fields. We will name our custom appointment class Task. Example 1 shows the creation of the Custom Appointment class:
When inheriting the AppointmentBase class it is required to create a parameter-less constructor for the the custom class.
Example 1: Create Custom Appointment
public class Task:Appointment
{
private bool isDone;
public bool IsDone
{
get
{
return this.Storage<Task>().isDone;
}
set
{
var storage = this.Storage<Task>();
if (storage.isDone != value)
{
storage.isDone = value;
this.OnPropertyChanged(() => this.IsDone);
}
}
}
public override IAppointment Copy()
{
var newAppointment = new Task();
newAppointment.CopyFrom(this);
return newAppointment;
}
public override void CopyFrom(IAppointment other)
{
var task = other as Task;
if (task != null)
{
this.IsDone = task.IsDone;
}
base.CopyFrom(other);
}
}
Example 2: Create the TasksCollection
public MainWindow()
{
InitializeComponent();
// "this.scheduler" refers to the RadScheduler instance that we are targetting
this.scheduler.AppointmentsSource = new TasksCollection();
}
public class TasksCollection : BindableCollection<Task>
{
public TasksCollection()
{
DateTime today = DateTime.Today;
foreach (Task t in Enumerable.Range(9, 14).Select(i =>
new Task
{
Start = today.AddMinutes(i * 60 + 15),
End = today.AddMinutes((i + 1) * 60),
Subject = string.Format("Task num. {0}",i),
IsDone = today.AddMinutes((i + 1) * 60) < DateTime.Now
}))
{
this.Add(t);
}
}
}
Figure 1: Result from Example 2
Creating a Custom Appointment Dialog
In order to create a custom appointment dialog we are going to modify the EditAppointmentDialogStyle property of RadScheduler control. The DataContext of the SchedulerDialog, which is the TargetType of this style is an AppointmentDialogViewModel object. This class contains all needed data for editing an appointment including the Appointment itself. It can be reached by using the Occurrence property of the ViewModel and subsequently the Appointment property of Occurrence.
Now that we have our custom IsDone property, let's add a CheckBox for it and bind to it. In order to do that, you need to extract and modify the default ControlTemplate of the EditAppointmentDialog with x:Key="EditAppointmentTemplate".
After you have extracted the default EditAppointmentTemplate, you can add a CheckBox as demonstrated in Example 3 which can replace the CheckBox with x:Name="AllDayEventCheckbox".
Example 3: Bind the IsDone property
<Page.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
<!-- Other merged dictionaries here -->
<ResourceDictionary Source="ms-appx:///Telerik.WinUI.Controls/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>
<!-- Other app resources here -->
<ControlTemplate x:Key="EditAppointmentTemplate" TargetType="telerik:SchedulerDialog">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<StackPanel x:Name="AppointmentToolbar"
AutomationProperties.Name="Appointment"
Background="{ThemeResource SystemChromeLowColor}"
Orientation="Horizontal" Padding="0 2">
<telerik:RadButton x:Name="EditRecurrenceButton"
Background="{x:Null}" Padding="2 4"
AutomationProperties.AutomationId="EditRecurrenceButton"
Command="{core:Static Member=telerik:RadSchedulerCommands.EditRecurrenceRule}"
VerticalAlignment="Center"
Visibility="{Binding CanEditParentAppointment, Converter={StaticResource BooleanToVisibilityConverter},ConverterParameter=True}"
Margin="10 0 0 0">
<ToolTipService.ToolTip>
<ToolTip core:LocalizationManager.Key="Scheduler_EditRecurrence" core:LocalizationManager.PropertyName="Content" />
</ToolTipService.ToolTip>
<StackPanel Orientation="Horizontal" Margin="2 0">
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}" Glyph=""/>
<TextBlock Margin="5 0"
core:LocalizationManager.Key="Scheduler_EditRecurrence"
core:LocalizationManager.PropertyName="Text" />
</StackPanel>
</telerik:RadButton>
<telerik:RadButton x:Name="EditParentAppointmentButton"
Background="{x:Null}" Padding="2 4"
AutomationProperties.AutomationId="EditParentAppointmentButton"
IsEnabled="{Binding IsReadOnly, Converter={StaticResource InvertedBooleanConverter}}"
Command="{core:Static Member=telerik:RadSchedulerCommands.EditParentAppointment}"
VerticalAlignment="Center"
Visibility="{Binding CanEditParentAppointment, Converter={StaticResource BooleanToVisibilityConverter}}"
Margin="10 0 0 0">
<ToolTipService.ToolTip>
<TextBlock core:LocalizationManager.Key="Scheduler_EditParentAppointment" core:LocalizationManager.PropertyName="Text" />
</ToolTipService.ToolTip>
<TextBlock Margin="5 0" core:LocalizationManager.Key="Scheduler_EditParentAppointment" core:LocalizationManager.PropertyName="Text" />
</telerik:RadButton>
<Rectangle Width="1" Fill="{ThemeResource TelerikScheduler_BorderBrush}" VerticalAlignment="Stretch" Margin="2 0 0 0"/>
<TextBlock x:Name="ShowAs" Margin="10 0"
core:LocalizationManager.Key="Scheduler_ShowAs"
core:LocalizationManager.PropertyName="Text"
VerticalAlignment="Center"/>
<ComboBox x:Name="PART_TimeMarkers" AutomationProperties.Name="TimeMarkers"
AutomationProperties.AutomationId="PART_TimeMarkers" Width="130"
IsEnabled="{Binding IsReadOnly, Converter={StaticResource InvertedBooleanConverter}}"
ItemsSource="{Binding TimeMarkers}" PlaceholderText="TimeMarkers"
SelectedItem="{Binding Occurrence.Appointment.TimeMarker, Mode=TwoWay}"
ItemTemplate="{StaticResource TimeMarkerComboBoxItemContentTemplate}" />
<ComboBox x:Name="PART_Categories" AutomationProperties.Name="Categories"
AutomationProperties.AutomationId="PART_Categories" Margin="10 0 0 0" Width="130"
IsEnabled="{Binding IsReadOnly, Converter={StaticResource InvertedBooleanConverter}}"
ItemsSource="{Binding Categories}" PlaceholderText="Categorize"
SelectedItem="{Binding Occurrence.Appointment.Category, Mode=TwoWay}"
ItemTemplate="{StaticResource CategoryComboBoxItemContentTemplate}" />
<ToggleButton x:Name="HighImportaceButton"
Background="Transparent" Width="22" Height="22"
Padding="0" Margin="5 0 0 0"
AutomationProperties.AutomationId="HighImportaceButton"
IsEnabled="{Binding IsReadOnly, Converter={StaticResource InvertedBooleanConverter}}"
IsChecked="{Binding IsHighImportance, Mode=TwoWay}">
<ToolTipService.ToolTip>
<ToolTip core:LocalizationManager.Key="Scheduler_HighImportance" core:LocalizationManager.PropertyName="Content" />
</ToolTipService.ToolTip>
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}" FontSize="16" Glyph="" Foreground="#D90000"/>
</ToggleButton>
<ToggleButton x:Name="LowImportaceButton"
Background="Transparent" Width="22" Height="22"
Padding="0" Margin="5 0 0 0"
AutomationProperties.AutomationId="LowImportaceButton"
IsEnabled="{Binding IsReadOnly, Converter={StaticResource InvertedBooleanConverter}}"
IsChecked="{Binding IsLowImportance, Mode=TwoWay}">
<ToolTipService.ToolTip>
<ToolTip core:LocalizationManager.Key="Scheduler_LowImportance" core:LocalizationManager.PropertyName="Content" />
</ToolTipService.ToolTip>
<FontIcon FontFamily="{ThemeResource SymbolThemeFontFamily}" FontSize="16" Glyph=""/>
</ToggleButton>
</StackPanel>
<Border x:Name="AppointmentCategory" Grid.Row="1" Height="20" Margin="6 6"
CornerRadius="3" Background="{Binding SelectedItem.CategoryBrush, ElementName=PART_Categories}"
Visibility="{Binding SelectedItem, ElementName=PART_Categories, Converter={StaticResource NullToVisibilityConverter}}">
<TextBlock Margin="6 0" Text="{Binding SelectedItem.DisplayName, ElementName=PART_Categories}" VerticalAlignment="Center" />
</Border>
<Grid x:Name="Details" Grid.Row="2" Margin="0 10 0 0" Padding="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90" />
<ColumnDefinition Width="" />
<ColumnDefinition Width="" />
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Margin="0 5"
core:LocalizationManager.Key="Scheduler_Subject"
core:LocalizationManager.PropertyName="Text"/>
<TextBox AutomationProperties.AutomationId="SubjectTextBox"
AutomationProperties.Name="Subject"
Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="2" Margin="0 5"
IsReadOnly="{Binding IsReadOnly}"
Text="{Binding Occurrence.Appointment.Subject, Mode=TwoWay}" />
<TextBlock Grid.Row="1" Grid.Column="0" Margin="0 5"
core:LocalizationManager.Key="Scheduler_Body"
core:LocalizationManager.PropertyName="Text" />
<TextBox AutomationProperties.AutomationId="DescriptionTextBox"
AutomationProperties.Name="Description"
Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="2" Margin="0 5"
Height="70" IsReadOnly="{Binding IsReadOnly}"
VerticalAlignment="Stretch" VerticalContentAlignment="Stretch"
Text="{Binding Occurrence.Appointment.Body, Mode=TwoWay}" TextWrapping="Wrap" />
<TextBlock Grid.Row="2" Grid.Column="0" Margin="0 5"
core:LocalizationManager.Key="Scheduler_StartTime"
core:LocalizationManager.PropertyName="Text" />
<StackPanel x:Name="StartTime" Grid.Row="2" Grid.Column="1" Grid.ColumnSpan="2"
Orientation="Horizontal" Margin="0 5">
<DatePicker x:Name="startDatePicker"
SelectedDate="{Binding ActualStartDatePart, Mode=TwoWay, Converter={StaticResource DateTimeToDateTimeOffsetConverter}}"
AutomationProperties.Name="StartDate"
AutomationProperties.AutomationId="StartDatePicker"
DayFormat="{}{day.integer} ({dayofweek.abbreviated})"
IsEnabled="{Binding IsNotRecurrent}"/>
<TimePicker x:Name="startTimePicker"
SelectedTime="{Binding ActualStartTimeOfDayPart, Mode=TwoWay}"
AutomationProperties.Name="StartTime"
AutomationProperties.AutomationId="StartTimePicker"
MinuteIncrement="15" Margin="2 0 0 0"
IsEnabled="{Binding IsNotRecurrent}"/>
</StackPanel>
<TextBlock Grid.Row="3" Grid.Column="0" Margin="0 5"
core:LocalizationManager.Key="Scheduler_EndTime"
core:LocalizationManager.PropertyName="Text" />
<StackPanel x:Name="EndTime" Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
Orientation="Horizontal" Margin="0 5">
<DatePicker x:Name="endDatePicker"
SelectedDate="{Binding ActualEndDatePart, Mode=TwoWay, Converter={StaticResource DateTimeToDateTimeOffsetConverter}}"
AutomationProperties.Name="EndDate"
AutomationProperties.AutomationId="EndDatePicker"
DayFormat="{}{day.integer} ({dayofweek.abbreviated})"
IsEnabled="{Binding IsNotRecurrent}"/>
<TimePicker x:Name="endTimePicker"
SelectedTime="{Binding ActualEndTimeOfDayPart, Mode=TwoWay}"
AutomationProperties.Name="EndTime"
AutomationProperties.AutomationId="EndTimePicker"
MinuteIncrement="15" Margin="2 0 0 0"
IsEnabled="{Binding IsNotRecurrent}"/>
</StackPanel>
<!--<CheckBox x:Name="AllDayEventCheckbox"
Grid.Row="4" Grid.Column="1"
VerticalAlignment="Center"
AutomationProperties.Name="AllDayEvent"
AutomationProperties.AutomationId="AllDayEventCheckbox"
IsChecked="{Binding Path=IsAllDayEvent, Mode=TwoWay}"
IsEnabled="{Binding IsNotRecurrent}"
core:LocalizationManager.Key="Scheduler_AllDayEvent"
core:LocalizationManager.PropertyName="Content" />-->
<CheckBox Grid.Row="4" Grid.Column="1" Margin="3" Content="Is done?" IsChecked="{Binding Occurrence.Appointment.IsDone, Mode=TwoWay}"/>
</Grid>
<!-- Resource Editor -->
<Grid Grid.Row="3" Visibility="{Binding ResourceTypesVisibility}" Padding="10">
<ItemsControl x:Name="PART_Resources" IsTabStop="false"
BorderBrush="{x:Null}" BorderThickness="0"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
IsEnabled="{Binding IsReadOnly, Converter={StaticResource InvertedBooleanConverter}}"
ItemsSource="{Binding ResourceTypes}"
ItemTemplateSelector="{StaticResource ResourcesEditorItemTemplateSelector}"
Visibility="{Binding ResourceTypesVisibility}" />
<TextBlock HorizontalAlignment="Left" Text="You can only change the resources by opening the series"
Visibility="{Binding ResourceTypesVisibility, Converter={StaticResource InvertedVisibilityConverter}}" />
</Grid>
<StackPanel Grid.Row="4" Margin="0 0 10 10" HorizontalAlignment="Right" Orientation="Horizontal">
<telerik:RadButton AutomationProperties.AutomationId="OKButton"
CornerRadius="2" MinWidth="80"
Command="{core:Static Member=telerik:WindowCommands.Confirm}"
telerik:RadWindow.ResponseButton="Accept"
core:LocalizationManager.Key="Scheduler_Ok"
core:LocalizationManager.PropertyName="Content">
<ToolTipService.ToolTip>
<TextBlock core:LocalizationManager.Key="Scheduler_SaveAndClose" core:LocalizationManager.PropertyName="Text" />
</ToolTipService.ToolTip>
</telerik:RadButton>
<telerik:RadButton AutomationProperties.AutomationId="CancelButton"
Margin="10 0 0 0" CornerRadius="2" MinWidth="80"
Command="{core:Static Member=telerik:WindowCommands.Cancel}"
telerik:RadWindow.ResponseButton="Cancel"
core:LocalizationManager.Key="Scheduler_Cancel"
core:LocalizationManager.PropertyName="Content" />
</StackPanel>
</Grid>
</ControlTemplate>
<Style x:Key="EditAppointmentDialogStyle" TargetType="telerik:SchedulerDialog" BasedOn="{StaticResource EditAppointmentDialogStyle}">
<Setter Property="Template" Value="{StaticResource EditAppointmentTemplate}" />
</Style>
</ResourceDictionary>
</Page.Resources>
<Grid>
<telerik:RadScheduler x:Name="scheduler" EditAppointmentDialogStyle="{StaticResource EditAppointmentDialogStyle}">
<telerik:RadScheduler.ViewDefinitions>
<telerik:DayViewDefinition />
<telerik:WeekViewDefinition />
<telerik:TimelineViewDefinition />
</telerik:RadScheduler.ViewDefinitions>
</telerik:RadScheduler>
</Grid>
Figure 2: Result from Example 3
The important thing to note here is that we can bind to our new properties using Occurrence.Appointment.
Changing the Style of the AppointmentItem
Next, we are going to change the ContentTemplate of the AppointmentItem to reflect which tasks are done and which are not. We will do that by using the AppointmentItemContentTemplate property of the RadScheduler. Example 4 demonstrates how to add an ellipse inside the ContentTemplate indicating the status of the task.
Example 4: Indicate the status of an appointment with an Ellipse
<Page.Resources >
<core:BoolToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<DataTemplate x:Key="AppointmentItemContentTemplate">
<StackPanel Orientation="Horizontal">
<Ellipse Fill="Green" Width="12" Height="12" VerticalAlignment="Top" Margin="0 5 5 0" Visibility="{Binding Appointment.IsDone, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBlock Text="{Binding DisplayText}" TextWrapping="Wrap" TextTrimming="WordEllipsis" />
</StackPanel>
</DataTemplate>
</Page.Resources>
Customizing the Appointment ToolTip
The customization of the Appointment ToolTip is achieved by setting the ToolTipTemplate property of RadScheduler. The DataContext in this template is of type AppointmentProxy so we can use the same approach we used in Example 4.
Example 5 demonstrates how you can modify the Appointment ToolTipTemplate in order to add the text (Done) only for the tasks which are already done:
Example 5: Define a ToolTipTemplate
<Page.Resources >
<core:BoolToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
<DataTemplate x:Key="AppointmentItemContentTemplate">
<StackPanel Orientation="Horizontal">
<Ellipse Fill="Green" Width="12" Height="12" VerticalAlignment="Top" Margin="0 5 5 0" Visibility="{Binding Appointment.IsDone, Converter={StaticResource BooleanToVisibilityConverter}}" />
<TextBlock Text="{Binding DisplayText}" TextWrapping="Wrap" TextTrimming="WordEllipsis" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="ToolTipTemplate">
<StackPanel Orientation="Horizontal" MinWidth="140" Margin="0 5">
<TextBlock MaxWidth="200" TextWrapping="Wrap" Text="{Binding Subject}"/>
<TextBlock Text="(Done)" Grid.Row="1" Margin="5 0 5 0" Foreground="#FF191D1A" Visibility="{Binding Appointment.IsDone, Converter={StaticResource BooleanToVisibilityConverter}}" FontStyle="Italic" />
</StackPanel>
</DataTemplate>
</Page.Resources>
<telerik:RadScheduler x:Name="scheduler" AppointmentItemContentTemplate="{StaticResource AppointmentItemContentTemplate}" ToolTipTemplate="{StaticResource ToolTipTemplate}">
<telerik:RadScheduler.ViewDefinitions>
<telerik:DayViewDefinition />
<telerik:WeekViewDefinition />
<telerik:MonthViewDefinition />
<telerik:TimelineViewDefinition />
</telerik:RadScheduler.ViewDefinitions>
</telerik:RadScheduler>
Figure 3: Result from Examples 4 and 5
By completing this last modification, we have reached the end of the process needed to create a custom appointment in RadScheduler control.