Edit this page

Custom Appointment

A very common scenario when using RadScheduleView 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.

In this article we will explore the process of using custom appointments in RadScheduleView. We will go through the following steps:

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 implement 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 ScheduleView 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. Out 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. Here is 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.

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);
    }
}
Public Class Task
 Inherits Appointment
 Private m_isDone As Boolean
 Public Property IsDone() As Boolean
  Get
   Return Me.Storage(Of Task)().m_isDone
  End Get
  Set
   Dim storage = Me.Storage(Of Task)()
   If storage.m_isDone <> value Then
    storage.m_isDone = value
    Me.OnPropertyChanged("IsDone")
   End If
  End Set
 End Property
 Public Overrides Function Copy() As IAppointment
  Dim newAppointment = New Task()
  newAppointment.CopyFrom(Me)
  Return newAppointment
 End Function
 Public Overrides Sub CopyFrom(other As IAppointment)
  Dim task = TryCast(other, Task)
  If task IsNot Nothing Then
   Me.IsDone = task.IsDone
  End If
  MyBase.CopyFrom(other)
 End Sub
End Class

For the next step, it is important to set the AppointmentsSource of RadScheduleView to be of type IList, because this way the ScheduleView knows that our custom appointments should be of type Task. Let's create an ObservableCollection using the following approach:

public class TasksCollection : ObservableCollection<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);
         }
    }
}
Dim today = DateTime.Today
Dim data = New ObservableCollection(Of Task)(Enumerable.Range(9, 14).Select)
Me.DataContext = data

And here is the result so far:

Creating a custom Appointment Dialog

In order to create a custom appointment dialog we are going to modify the EditAppointmentDialogStyle property of RadScheduleView control. The DataContext of 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. First, you need to generate the code for the EditAppointment dialog from Expression Blend. Then, add the following snippet in the ControlTemplate of the dialog:

<CheckBox Grid.Row="4" Grid.Column="1" Margin="3" Content="Is done?" IsChecked="{Binding Occurrence.Appointment.IsDone, Mode=TwoWay}"/>

Here is our customized EditAppointment dialog:

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 ControlTemplate of the AppointmentItem to reflect which tasks are done and which are not. We will do that by using the AppointmentItemStyleSelector property of the RadScheduleView:

The DataContext in the AppointmentItem ContentTemplate represents an AppointmentProxy, which holds the most important properties of the Appointment and the Appointment itself. We want to display a green dot for all tasks that are done. We will achieve this by adding a green dot in the ContentTemplate and then binding its Visibility to the IsDone property of the Task through a converter. Here is the code for this:

<Ellipse Fill="Green" Width="12" Height="12" VerticalAlignment="Top" Margin="10 5 5 5" HorizontalAlignment="Left" Visibility="{Binding Appointment.IsDone, Converter={StaticResource BooleanToVisibilityConverter}}" />

Customizing the Appointment ToolTip

This step is, of course, optional. The customization of the Appointment ToolTip is achieved by the ToolTipTemplate property of RadScheduleView.The DataContext in this template is once again of type AppointmentProxy so we can use the same approach we used in the AppointmentItem ContentTemplate. Generate the ToolTipTemplate:

Now we will add in the Appointment ToolTip the text (Done) only for the tasks which are already done:

<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>

This is what the Appointment ToolTip should look like now:

By completing this last modification, we have reached the end of the process needed to create a custom appointment in RadScheduleView control.

See Also