Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / XAML

Scrollbar for appointments in Telerik's RadScheduler Timeline View

4.25/5 (3 votes)
9 Nov 2010CPOL5 min read 42.8K   842  
How to enable and tune scrollbar in Telerik's RadScheduler Timeline view

This article appears in the Third Party Products and Tools section. Articles in this section are for the members only and must not be used to promote or advertise products in any way, shape or form. Please report any spam or advertising.

Introduction

In this article, I’ll try to show how to beat the original Timeline view behavior (with appointments in viewport and “Show More” button) and make it scroll them.

Background

In my current project on Silverlight 3, I use Telerik’s control library that is really powerful and fancy. It provides you with a large number of different controls which supports themes, templates, etc. But as it often happens, you need something which goes against the idea of the library’s architects. In my case, it was an ability to show all appointments in timeline view and scroll them instead of default behavior which displays appointments in viewport only and provides you with “Show More” button.

I searched a lot for a solution, but even Telerik's support said that it is impossible in the current version and probably will be implemented in future.

The resulting solution looks like a combination of hacks that affect the harmony of code and performance of application, but it works and probably will help somebody to solve the same problem.

Steps of Solution

Originally Timeline view with a lot of appointments that do not fit to the viewport looks like this:

1.png

My goal was to receive the next view:

2.png

To achieve this, we should do the next steps:

  1. Remove “Show More” button
  2. Increase the height of appointments
  3. Enable vertical scroll
  4. Change scrolled control height according to the number of appointments

To resolve the first three problems without hacks in code, I decided to use custom theme, because theme was fixed in the project (Windows 7) and probably it is the only way to do it. The penalty of this solution is increased size of assembly (because of huge style) and inability to change themes dynamically for this control.

I made all changes in the original Window 7 theme which is distributing with the “RadControls for Silverlight” toolkit. Besides the above-listed steps, I added support for mouse wheel scrolling in ScrollViewer and removed default behavior which changes a week back and forward instead. All changes in theme are marked with CHANGE notice in comments.

Here is the result of the mentioned changes:

3.png

But the problem still exists. If you do not have enough space to present your appointments, they will be cut by viewport. The reason is that they are located in the AppointmentItemsControl which used VirtualizedAppointmentPanel (derived from VirtualizingPanel) that is always virtualized. It gets own height from TimeSlotItemsControl which is just stretched within the available space.

4.png

Thus we should change TimeSlotItemsControl’s height to archive the desired behavior. This height shall depend on maximum number of appointments per day for visible time range.

5.png

This behavior can be implemented by binding with special converter who will do the mentioned conversion from appointments source to desired height.

C#
public object Convert(object value, Type targetType, 
    object parameter, CultureInfo culture)
{ 
    const int appointmentHeight = 45;

    var presenter = (ScrollContentPresenter)parameter;
    var scheduler = presenter.GetVisualParent<RadScheduler>();
    var appointments = ((IEnumerable)value).Cast<Appointment>().
        Where(a => a.Start >= scheduler.VisibleRangeStart && 
        a.Start <= scheduler.VisibleRangeEnd);

    // Calculate maximum number of appointments per stack
    var maxAppointmentsPerStack = (appointments.Count() == 0) ? 0 :
        appointments.GroupBy(a => a.Start.Date).Max(g => g.Count());

    // Calculate desired size to show whole stack
    var possibleHeight = (maxAppointmentsPerStack + 1) * appointmentHeight;
    var presenterHeight = presenter.ViewportHeight;

    return (possibleHeight > presenterHeight) ? possibleHeight : double.NaN;
}

To get an access to TimeSlotItemsControl and ScrollContentPresenter, I managed to use DataTemplate for time slots and a simple TemplateSelector to wire it to the scheduler.

C#
private void onTimeSlotLoaded(object sender, RoutedEventArgs e)
{
    var scrollContentPresenter = 
    ((FrameworkElement)sender).GetVisualParent<ScrollContentPresenter>();
    var timeSlotItemsControl = 
    scrollContentPresenter.FindChildByType<TimeSlotItemsControl>();

    if (timeSlotItemsControl.GetBindingExpression(HeightProperty) == null)
    {
        timeSlotItemsControl.SetBinding(HeightProperty,
            new Binding
            {
                Source = _scheduler,
                Path = new PropertyPath("AppointmentsSource"),
                Converter = HeightConverter.Instance,
                ConverterParameter = scrollContentPresenter
            });
    }
}

XAML

XML
<UserControl.Resources>
   
    <DataTemplate x:Key="TimeLineSlotTemplate">
        <Grid Loaded="onTimeSlotLoaded"/>
    </DataTemplate>
 
    <local:TimeSlotTemplateSelector x:Key="TimeSlotTemplateSelector"
            TimeLineSlotTemplate="{StaticResource TimeLineSlotTemplate}"/>
 
    <theme:SchedulerTheme x:Key="CustomTheme" />
   
</UserControl.Resources>
 
<Controls:RadScheduler
    x:Name="_scheduler"
    ViewMode="Timeline"
    AvailableViewModes="Timeline"
    TimeSlotTemplateSelector="{StaticResource TimeSlotTemplateSelector}"
    telerik:StyleManager.Theme="{StaticResource CustomTheme}"/> 

On this stage, I thought that work is done because I saw the scrollbar when it should be and didn’t see when it was unnecessary when I changed the current week. But the reality was too difficult to stop work, because view did not want to draw appointments under the viewport.

6.png

I tried to beat this behavior several times, I used Reflector, Silverlight Spy, tried to debug the library’s code, but couldn’t fix this behavior in an appropriate way. But I found the hack which affects performance but solves the problem – collection recreation on ScrollContentPresenter resize. This approach caused the separation between original data source and view via some intermediate collection. We should listen to the original collection changes now and reflect them to intermediate collection.

Also intermediate collection should be recreated when you change visible range in scheduler to force height recalculation because new time range can have different maximum stack height.

I used collection that is derived from the BaseAppointmentCollection from Telerik’s suite to solve another problem with scheduler behavior, but you could use your own.

C#
private void onScrollContentPresenterSizeChanged(object sender, SizeChangedEventArgs e)
{
    updateAppointmentsCollection();
} 

private void updateAppointmentsCollection()
{
    var collection = new AppointmentCollection();
    collection.AddRange(_dataSource);
    _scheduler.AppointmentsSource = collection;
}

The current solution works even for changing set of appointments, i.e., when you add new appointments, delete existing or just enlarge you data source with existing appointments arrived from web service during application work. This is possible because of intermediate collection recreation on each data source collection change.

But what about appointment’s data change? We can change Start property via dialog, Drag-n-Drop and programmatically. In this case, data source collection would not change and scheduler wouldn’t be noticed about necessity of desired height change. But it should be because some appointment’s stack can be increased or decreased during this operation.

To solve this issue, we should subscribe to appointments’ changes and force Height update. I did it inside that intermediate collection that I mentioned above. I do subscription in special method (which is called every time when you add appointment to collection) and notify UI about changes (that can affect desired height) via special property which returns collection itself and is used during binding.

C#
protected override void InsertItem(int index, Appointment item)
{
    if (!Contains(item))
    {
        item.PropertyChanged += onItemPropertyChanged;
        base.InsertItem(index, item);
    }
}

public void ForceItemPropertyChangedSupportCollectionUpdate()
{
    OnPropertyChanged(
        new PropertyChangedEventArgs("ItemPropertyChangedSupportCollection"));
}

private void onItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "Start")
    {
        ForceItemPropertyChangedSupportCollectionUpdate();
    }
}

Conclusion

This solution successfully works in my project in spite of its hack nature. My future plans are to combine all sources which affect desired height (appointments collection’s change, appointment’s data change, control’s size change, and visible range’s change) via multi binding and try to find a way how to access TimeSlotItemsControl directly instead of DataTemplate usage.

Useful Links

History

  • 10th November, 2010: Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)