Telerik 的 RadScheduler 时间线视图中的预约滚动条
如何启用和调整 Telerik 的 RadScheduler 时间线视图中的滚动条。
引言
在本文中,我将尝试展示如何克服原始时间线视图的行为(在视口中显示约会并带有“显示更多”按钮),并使其能够滚动这些约会。
背景
在我目前的 Silverlight 3 项目中,我使用了 Telerik 的控件库,它非常强大和精美。它提供了大量的不同控件,支持主题、模板等。但正如经常发生的那样,您需要一些与库架构师的初衷相悖的功能。在我的例子中,就是能够显示时间线视图中的所有约会并滚动它们,而不是默认行为,即仅在视口中显示约会并提供“显示更多”按钮。
我搜了很多解决方案,但即使是 Telerik 的支持人员也说在当前版本中是不可能的,并且可能会在未来实现。
最终的解决方案看起来像是一些技巧的组合,这些技巧会影响代码的和谐性和应用程序的性能,但它确实有效,并且可能会帮助其他人解决相同的问题。
解决方案步骤
最初,当时间线视图中有大量约会无法适应视口时,看起来是这样的

我的目标是实现以下视图

为了实现这一点,我们需要执行以下步骤
- 移除“显示更多”按钮
- 增加约会的高度
- 启用垂直滚动
- 根据约会数量更改滚动控件的高度
为了在不使用代码技巧的情况下解决前三个问题,我决定使用自定义主题,因为在项目中主题是固定的(Windows 7
),而且这可能是唯一的方法。这个解决方案的代价是程序集大小增加(因为巨大的样式)并且无法为此控件动态更改主题。
我修改了“RadControls for Silverlight
”工具包随附的原始 Windows 7 主题。除了上面列出的步骤外,我还为 ScrollViewer
添加了鼠标滚轮滚动支持,并移除了用于向前或向后切换一周的默认行为。所有主题更改都用注释中的更改标记。
但是问题仍然存在。如果您没有足够的空间来展示您的约会,它们将被视口裁剪。原因是它们位于 AppointmentItemsControl
中,该控件使用了 VirtualizedAppointmentPanel
(派生自 VirtualizingPanel
),它始终是虚拟化的。它从 TimeSlotItemsControl
获取自己的高度,而 TimeSlotItemsControl
只是在可用空间内拉伸。

因此,我们应该更改 TimeSlotItemsControl
的高度来实现所需行为。此高度应取决于可见时间范围内每天约会的最大数量。

可以通过绑定具有特殊转换器的特殊转换器来实现此行为,该转换器将从约会源转换为所需的高度。
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;
}
为了访问 TimeSlotItemsControl
和 ScrollContentPresenter
,我设法使用时间槽的 DataTemplate
和一个简单的 TemplateSelector
来将其连接到日程表。
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
<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}"/>
在这个阶段,我认为工作已经完成,因为我看到了应该出现的滚动条,并且在更改当前周时看不到不必要的滚动条。但现实比想象的复杂,工作无法停止,因为视图不想在视口下方绘制约会。

我曾多次尝试克服这种行为,我使用了 Reflector、Silverlight Spy,并尝试调试库的代码,但都无法以适当的方式修复这种行为。但我发现了一个影响性能但能解决问题的技巧——在 ScrollContentPresenter
调整大小时重新创建集合。这种方法通过一些中间集合在原始数据源和视图之间产生了隔离。我们现在应该监听原始集合的变化,并将它们反映到中间集合中。
当您在日程表中更改可见范围时,中间集合也应该被重新创建,以强制进行高度重新计算,因为新的时间范围可能有不同的最大堆叠高度。
我使用了派生自 Telerik 套件的 BaseAppointmentCollection
的集合来解决与日程表行为相关的另一个问题,但您也可以使用自己的集合。
private void onScrollContentPresenterSizeChanged(object sender, SizeChangedEventArgs e)
{
updateAppointmentsCollection();
}
private void updateAppointmentsCollection()
{
var collection = new AppointmentCollection();
collection.AddRange(_dataSource);
_scheduler.AppointmentsSource = collection;
}
当前的解决方案甚至适用于不断变化的约会集,即当您添加新约会、删除现有约会或在应用程序工作期间从 Web 服务获取现有约会来扩大您的数据源时。这是可能的,因为在每次数据源集合更改时都会重新创建中间集合。
但是约会的数据变化呢?我们可以通过对话框、拖放和编程方式更改 Start
属性。在这种情况下,数据源集合不会改变,日程表也不会注意到需要更改所需的高度。但它应该改变,因为在操作过程中,某些约会的堆叠可能会增加或减少。
为了解决这个问题,我们应该订阅约会的变化并强制更新高度。我在前面提到的中间集合中完成了这个操作。我在特殊方法(每次将约会添加到集合时都会调用该方法)中进行订阅,并通过一个特殊属性(它返回集合本身并在绑定时使用)向 UI 通知(可能影响所需高度)更改。
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();
}
}
结论
尽管此解决方案本质上是技巧性的,但它在我的项目中成功运行。我未来的计划是将所有影响所需高度的源(约会集合的变化、约会数据变化、控件大小变化和可见范围变化)通过多重绑定结合起来,并尝试找到一种直接访问 TimeSlotItemsControl
而不是使用 DataTemplate
的方法。
有用链接
历史
- 2010 年 11 月 10 日:初始帖子