WPF:只讀集合在 XAML 中的繫結(WPF:Binding for readonly collection in xaml)
阿新 • • 發佈:2018-11-26
問題背景:
某一天,我想做一個簽到打卡的日曆。基於 Calendar,想實現這個目標,於是找到了它的 SelectedDates 屬性,用於標記簽到過的日期。
那麼 問題 來了:
基於MVVM模式,想將其在xaml中繫結到ViewModel中的一個List<DateTime>屬性。但 SelectedDates 是只讀的 CLR 屬性。
解決思路:
給它搭個橋:建立一個相同Type的附加屬性,用附加屬性繫結到ViewModel中的List<DateTime>屬性。並在附加屬性的變更通知和集合變更通知中新增處理:進行操作的橋接,將集合物件、集合的變動傳遞到SelectedDates中即可
主要程式碼如下:(PS:初步使用,如果有其他問題請指教:e-mal:[email protected])
// 用於繫結(傳遞工具)的依賴屬性 public static List<DateTime> GetSelectedDatesSource(DependencyObject obj) { return (List<DateTime>)obj.GetValue(SelectedDatesSourceProperty); } public static void SetSelectedDatesSource(DependencyObject obj, List<DateTime> value) { obj.SetValue(SelectedDatesSourceProperty, value); } public static readonly DependencyProperty SelectedDatesSourceProperty = DependencyProperty.RegisterAttached("SelectedDatesSource", typeof(List<DateTime>), typeof(CalendarEx), new PropertyMetadata(null, SetSelectedDatesSourcePropertyChangedCallback)); // 依賴屬性變更通知(集合物件的變更) private static void SetSelectedDatesSourcePropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs args) { // 附加屬性應用於 Calendar Calendar calendar = d as Calendar; if (calendar == null) return; // 變更 SelectedDatesSource 物件,先獲取新集合物件的集合內容 List<DateTime> newValue = args.NewValue as List<DateTime>; if (newValue == null) return; // 應用新內容 calendar.SelectedDates.Clear(); newValue.ForEach(v => calendar.SelectedDates.Add(v)); // 獲取 SelectedDatesSource 物件,新增集合變更通知處理,在其中處理目標集合物件 List<DateTime> sourceCollection = GetSelectedDatesSource(d); if (sourceCollection == null) return; calendar.SelectedDates.CollectionChanged -= SelectedDatesOnCollectionChanged; calendar.SelectedDates.CollectionChanged += SelectedDatesOnCollectionChanged; } // 集合變更通知(集合內容的變更) private static void SelectedDatesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args) { SelectedDatesCollection collection = sender as SelectedDatesCollection; if (collection == null) return; switch (args.Action) { case NotifyCollectionChangedAction.Add: foreach (DateTime item in args.NewItems) { collection.Add(item); } break; case NotifyCollectionChangedAction.Remove: foreach (DateTime item in args.NewItems) { collection.Remove(item); } break; case NotifyCollectionChangedAction.Replace: foreach (DateTime item in args.OldItems) { collection.Remove(item); } foreach (DateTime item in args.NewItems) { collection.Add(item); } break; case NotifyCollectionChangedAction.Move: // ignored break; case NotifyCollectionChangedAction.Reset: // ignored break; default: break; } }
使用起來就簡單了: