Loading

WPF 只读集合在 XAML 中的绑定(WPF:Binding for readonly collection in xaml)

问题背景

某一天,我想做一个签到打卡的日历。基于 Calendar,想实现这个目标,于是找到了它的 SelectedDates 属性,用于标记签到过的日期。

问题来了。

基于MVVM模式,想将其在xaml中绑定到ViewModel中的一个ObservableCollection属性。但 SelectedDates只读CLR 属性。

解决思路:

给它搭个桥:创建一个相同Type的附加属性,用附加属性绑定到ViewModel中的ObservableCollection属性。并在附加属性的变更通知和集合变更通知中添加处理:进行操作的桥接,将集合对象、集合的变动传递到SelectedDates中即可。

主要代码如下:

public static class CalendarEx
{
    #region SelectedDates 只读,不可在 Xaml 中绑定的问题解决方案:建立附加属性通道
    public static ObservableCollection<DateTime> GetSelectedDatesSource(DependencyObject obj)
    {
        return (ObservableCollection<DateTime>)obj.GetValue(SelectedDatesSourceProperty);
    }
    public static void SetSelectedDatesSource(DependencyObject obj, ObservableCollection<DateTime> value)
    {
        obj.SetValue(SelectedDatesSourceProperty, value);
    }
    public static readonly DependencyProperty SelectedDatesSourceProperty =
        DependencyProperty.RegisterAttached("SelectedDatesSource", typeof(ObservableCollection<DateTime>), typeof(CalendarEx), new PropertyMetadata(null, SetSelectedDatesSourcePropertyChangedCallback));
    /// <summary>
    ///  REMARK 只读集合的 XAML 绑定(目前是单向绑定,双向绑定需定义 SelectedDates 的变更事件)
    /// </summary>
    /// <param name="d"></param>
    /// <param name="args"></param>
    private static void SetSelectedDatesSourcePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args)
    {
        // 附加属性应用于 Calendar
        Calendar calendar = d as Calendar;
        if (calendar == null) return;
        // 对象本身未变更,则不进行任何操作,避免相同对象多次添加变更事件
        if (args.NewValue == args.OldValue) return;
        // 变更 SelectedDatesSource 对象,先获取新集合对象的集合内容
        ObservableCollection<DateTime> newValue = args.NewValue as ObservableCollection<DateTime>;
        if (newValue == null) return;
        
        // 应用新内容
        calendar.SelectedDates.Clear();
        foreach (DateTime time in newValue)
        {
            calendar.SelectedDates.Add(time);
        }
        // 获取 SelectedDatesSource 对象,添加集合变更通知处理,在其中处理目标集合对象
        ObservableCollection<DateTime> sourceCollection = GetSelectedDatesSource(d);
        if (sourceCollection == null) return;
        // _NOTE 这里使用 local 函数的目的是,集合变更通知函数(静态)需要访问 calendar,使用 local/匿名 函数方便访问,否则需要定义一个根据集合访问 calendar 的服务
        // 另外,local 函数比匿名函数好的地方在于,可以取消事件订阅(经验证)
        void SelectedDatesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs cArgs)
        {
            ObservableCollection<DateTime> collection = sender as ObservableCollection<DateTime>;
            if (collection == null) return;
            switch (cArgs.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (DateTime item in cArgs.NewItems)
                    {
                        calendar.SelectedDates.Add(item);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach (DateTime item in cArgs.NewItems)
                    {
                        calendar.SelectedDates.Remove(item);
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    foreach (DateTime item in cArgs.OldItems)
                    {
                        calendar.SelectedDates.Remove(item);
                    }
                    foreach (DateTime item in cArgs.NewItems)
                    {
                        calendar.SelectedDates.Add(item);
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    // ignored
                    break;
                case NotifyCollectionChangedAction.Reset:
                    // ignored
                    break;
                default:
                    break;
            }
        }
        // 这里
        sourceCollection.CollectionChanged -= SelectedDatesOnCollectionChanged;
        sourceCollection.CollectionChanged += SelectedDatesOnCollectionChanged;
    }
    #endregion
}

使用起来就简单了:

<Calender Grid.Column="0" x:Name="Calendar" Width=260" Heigh="270"
    SelectionMode="MultipleRange" lui:CalendarEx.SelectedDatesSource="{Binding Checkins}"
    lui:BehaviorRepo.ReleaseStylusCaptureCalendar="True"
/>
posted @ 2018-11-26 11:30  NLNet  阅读(60)  评论(0编辑  收藏  举报