65.9K
CodeProject 正在变化。 阅读更多。
Home

Telerik 的 RadScheduler 时间线视图中的预约滚动条

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.25/5 (3投票s)

2010 年 11 月 10 日

CPOL

5分钟阅读

viewsIcon

43399

downloadIcon

856

如何启用和调整 Telerik 的 RadScheduler 时间线视图中的滚动条。

引言

在本文中,我将尝试展示如何克服原始时间线视图的行为(在视口中显示约会并带有“显示更多”按钮),并使其能够滚动这些约会。

背景

在我目前的 Silverlight 3 项目中,我使用了 Telerik 的控件库,它非常强大和精美。它提供了大量的不同控件,支持主题、模板等。但正如经常发生的那样,您需要一些与库架构师的初衷相悖的功能。在我的例子中,就是能够显示时间线视图中的所有约会并滚动它们,而不是默认行为,即仅在视口中显示约会并提供“显示更多”按钮。

我搜了很多解决方案,但即使是 Telerik 的支持人员也说在当前版本中是不可能的,并且可能会在未来实现。

最终的解决方案看起来像是一些技巧的组合,这些技巧会影响代码的和谐性和应用程序的性能,但它确实有效,并且可能会帮助其他人解决相同的问题。

解决方案步骤

最初,当时间线视图中有大量约会无法适应视口时,看起来是这样的

1.png

我的目标是实现以下视图

2.png

为了实现这一点,我们需要执行以下步骤

  1. 移除“显示更多”按钮
  2. 增加约会的高度
  3. 启用垂直滚动
  4. 根据约会数量更改滚动控件的高度

为了在不使用代码技巧的情况下解决前三个问题,我决定使用自定义主题,因为在项目中主题是固定的(Windows 7),而且这可能是唯一的方法。这个解决方案的代价是程序集大小增加(因为巨大的样式)并且无法为此控件动态更改主题。

我修改了“RadControls for Silverlight”工具包随附的原始 Windows 7 主题。除了上面列出的步骤外,我还为 ScrollViewer 添加了鼠标滚轮滚动支持,并移除了用于向前或向后切换一周的默认行为。所有主题更改都用注释中的更改标记。

这是提到的更改的结果

3.png

但是问题仍然存在。如果您没有足够的空间来展示您的约会,它们将被视口裁剪。原因是它们位于 AppointmentItemsControl 中,该控件使用了 VirtualizedAppointmentPanel(派生自 VirtualizingPanel),它始终是虚拟化的。它从 TimeSlotItemsControl 获取自己的高度,而 TimeSlotItemsControl 只是在可用空间内拉伸。

4.png

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

5.png

可以通过绑定具有特殊转换器的特殊转换器来实现此行为,该转换器将从约会源转换为所需的高度。

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

为了访问 TimeSlotItemsControlScrollContentPresenter,我设法使用时间槽的 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}"/> 

在这个阶段,我认为工作已经完成,因为我看到了应该出现的滚动条,并且在更改当前周时看不到不必要的滚动条。但现实比想象的复杂,工作无法停止,因为视图不想在视口下方绘制约会。

6.png

我曾多次尝试克服这种行为,我使用了 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 日:初始帖子
© . All rights reserved.