1. 程式人生 > >WPF:只讀集合在 XAML 中的繫結(WPF:Binding for readonly collection in xaml)

WPF:只讀集合在 XAML 中的繫結(WPF:Binding for readonly collection in xaml)

問題背景:

某一天,我想做一個簽到打卡的日曆。基於 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;
    }
}

使用起來就簡單了: