Как связать BlackoutDates в WPF Toolkit Calendar control?

Я хотел бы привязать список дат к свойству BlackoutDates, но это действительно не представляется возможным. Особенно в сценарии MVVM. Кто-нибудь сделал что-то подобное? Есть ли хорошие календарные элементы управления, которые хорошо сочетаются с MVVM?

Ответ 1

Для вашей дилеммы DatePicker я нашел аккуратный взломать с помощью подключенных свойств (измененный из моего использования CommandBindings):

class AttachedProperties : DependencyObject
{

    #region RegisterBlackoutDates

    // Adds a collection of command bindings to a date picker existing BlackoutDates collection, since the collections are immutable and can't be bound to otherwise.
    //
    // Usage: <DatePicker hacks:AttachedProperties.RegisterBlackoutDates="{Binding BlackoutDates}" >

    public static DependencyProperty RegisterBlackoutDatesProperty = DependencyProperty.RegisterAttached("RegisterBlackoutDates", typeof(System.Windows.Controls.CalendarBlackoutDatesCollection), typeof(AttachedProperties), new PropertyMetadata(null, OnRegisterCommandBindingChanged));

    public static void SetRegisterBlackoutDates(UIElement element, System.Windows.Controls.CalendarBlackoutDatesCollection value)
    {
        if (element != null)
            element.SetValue(RegisterBlackoutDatesProperty, value);
    }
    public static System.Windows.Controls.CalendarBlackoutDatesCollection GetRegisterBlackoutDates(UIElement element)
    {
        return (element != null ? (System.Windows.Controls.CalendarBlackoutDatesCollection)element.GetValue(RegisterBlackoutDatesProperty) : null);
    }
    private static void OnRegisterCommandBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        System.Windows.Controls.DatePicker element = sender as System.Windows.Controls.DatePicker;
        if (element != null)
        {
            System.Windows.Controls.CalendarBlackoutDatesCollection bindings = e.NewValue as System.Windows.Controls.CalendarBlackoutDatesCollection;
            if (bindings != null)
            {
                element.BlackoutDates.Clear();
                foreach (var dateRange in bindings)
                {
                    element.BlackoutDates.Add(dateRange);
                }
            }
        }
    }

    #endregion
}

Я уверен, что я слишком поздно, чтобы помочь вам, но, надеюсь, кто-то еще найдет это полезным.

Ответ 2

Вот улучшенная версия ответа Matt, которая позволяет нам работать с BlackoutDates как с любой обычной коллекцией Observable (вам не нужно создавать новые коллекции каждый раз, когда вы хотите изменить BlackoutDates). Мы сохраняем список всех календарей и привязанных датпиксеров, и внутри их тега мы храним коллекцию, используемую в MVVM. Легкая модификация класса позволит работать с ObservableCollection <DateTime> при необходимости:

// Adds a collection of command bindings to a date picker existing BlackoutDates collection, since the collections are immutable and can't be bound to otherwise.
// Usage: <DatePicker CalendarAttachedProperties.RegisterBlackoutDates="{Binding BlackoutDates}" >
public class CalendarAttachedProperties : DependencyObject
{
    #region Attributes

    private static readonly List<Calendar> _calendars = new List<Calendar>();
    private static readonly List<DatePicker> _datePickers = new List<DatePicker>();

    #endregion

    #region Dependency Properties

    public static DependencyProperty RegisterBlackoutDatesProperty = DependencyProperty.RegisterAttached("RegisterBlackoutDates", typeof(CalendarBlackoutDatesCollection), typeof(CalendarAttachedProperties), new PropertyMetadata(null, OnRegisterCommandBindingChanged));

    public static void SetRegisterBlackoutDates(DependencyObject d, CalendarBlackoutDatesCollection value)
    {
        d.SetValue(RegisterBlackoutDatesProperty, value);
    }

    public static CalendarBlackoutDatesCollection GetRegisterBlackoutDates(DependencyObject d)
    {
        return (CalendarBlackoutDatesCollection)d.GetValue(RegisterBlackoutDatesProperty);
    }

    #endregion

    #region Event Handlers

    private static void CalendarBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        CalendarBlackoutDatesCollection blackoutDates = sender as CalendarBlackoutDatesCollection;

        Calendar calendar = _calendars.First(c => c.Tag == blackoutDates);

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (CalendarDateRange dateRange in e.NewItems)
            {
                calendar.BlackoutDates.Add(dateRange);
            }
        }
    }

    private static void DatePickerBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        CalendarBlackoutDatesCollection blackoutDates = sender as CalendarBlackoutDatesCollection;

        DatePicker datePicker = _datePickers.First(c => c.Tag == blackoutDates);

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (CalendarDateRange dateRange in e.NewItems)
            {
                datePicker.BlackoutDates.Add(dateRange);
            }
        }
    }

    #endregion

    #region Private Methods

    private static void OnRegisterCommandBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        Calendar calendar = sender as Calendar;
        if (calendar != null)
        {
            CalendarBlackoutDatesCollection bindings = e.NewValue as CalendarBlackoutDatesCollection;
            if (bindings != null)
            {
                if (!_calendars.Contains(calendar))
                {
                    calendar.Tag = bindings;
                    _calendars.Add(calendar);
                }

                calendar.BlackoutDates.Clear();
                foreach (var dateRange in bindings)
                {
                    calendar.BlackoutDates.Add(dateRange);
                }
                bindings.CollectionChanged += CalendarBindings_CollectionChanged;
            }
        }
        else
        {
            DatePicker datePicker = sender as DatePicker;
            if (datePicker != null)
            {
                CalendarBlackoutDatesCollection bindings = e.NewValue as CalendarBlackoutDatesCollection;
                if (bindings != null)
                {
                    if (!_datePickers.Contains(datePicker))
                    {
                        datePicker.Tag = bindings;
                        _datePickers.Add(datePicker);
                    }

                    datePicker.BlackoutDates.Clear();
                    foreach (var dateRange in bindings)
                    {
                        datePicker.BlackoutDates.Add(dateRange);
                    }
                    bindings.CollectionChanged += DatePickerBindings_CollectionChanged;
                }
            }
        }
    }

    #endregion
}

Вот пример ObservableCollection <DateTime> версия:

// Adds a collection of command bindings to a date picker existing BlackoutDates collection, since the collections are immutable and can't be bound to otherwise.
// Usage: <DatePicker hacks:AttachedProperties.RegisterBlackoutDates="{Binding BlackoutDates}" >
public class CalendarAttachedProperties : DependencyObject
{
    #region Attributes

    private static readonly List<Calendar> _calendars = new List<Calendar>();
    private static readonly List<DatePicker> _datePickers = new List<DatePicker>();

    #endregion

    #region Dependency Properties

    public static DependencyProperty RegisterBlackoutDatesProperty = DependencyProperty.RegisterAttached("RegisterBlackoutDates", typeof(ObservableCollection<DateTime>), typeof(CalendarAttachedProperties), new PropertyMetadata(null, OnRegisterCommandBindingChanged));

    public static void SetRegisterBlackoutDates(DependencyObject d, ObservableCollection<DateTime> value)
    {
        d.SetValue(RegisterBlackoutDatesProperty, value);
    }

    public static ObservableCollection<DateTime> GetRegisterBlackoutDates(DependencyObject d)
    {
        return (ObservableCollection<DateTime>)d.GetValue(RegisterBlackoutDatesProperty);
    }

    #endregion

    #region Event Handlers

    private static void CalendarBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        ObservableCollection<DateTime> blackoutDates = sender as ObservableCollection<DateTime>;

        Calendar calendar = _calendars.First(c => c.Tag == blackoutDates);

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (DateTime date in e.NewItems)
            {
                calendar.BlackoutDates.Add(new CalendarDateRange(date));
            }
        }
    }

    private static void DatePickerBindings_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        ObservableCollection<DateTime> blackoutDates = sender as ObservableCollection<DateTime>;

        DatePicker datePicker = _datePickers.First(c => c.Tag == blackoutDates);

        if (e.Action == NotifyCollectionChangedAction.Add)
        {
            foreach (DateTime date in e.NewItems)
            {
                datePicker.BlackoutDates.Add(new CalendarDateRange(date));
            }
        }
    }

    #endregion

    #region Private Methods

    private static void OnRegisterCommandBindingChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
    {
        Calendar calendar = sender as Calendar;
        if (calendar != null)
        {
            ObservableCollection<DateTime> bindings = e.NewValue as ObservableCollection<DateTime>;
            if (bindings != null)
            {
                if (!_calendars.Contains(calendar))
                {
                    calendar.Tag = bindings;
                    _calendars.Add(calendar);
                }

                calendar.BlackoutDates.Clear();
                foreach (DateTime date in bindings)
                {
                    calendar.BlackoutDates.Add(new CalendarDateRange(date));
                }
                bindings.CollectionChanged += CalendarBindings_CollectionChanged;
            }
        }
        else
        {
            DatePicker datePicker = sender as DatePicker;
            if (datePicker != null)
            {
                ObservableCollection<DateTime> bindings = e.NewValue as ObservableCollection<DateTime>;
                if (bindings != null)
                {
                    if (!_datePickers.Contains(datePicker))
                    {
                        datePicker.Tag = bindings;
                        _datePickers.Add(datePicker);
                    }

                    datePicker.BlackoutDates.Clear();
                    foreach (DateTime date in bindings)
                    {
                        datePicker.BlackoutDates.Add(new CalendarDateRange(date));
                    }
                    bindings.CollectionChanged += DatePickerBindings_CollectionChanged;
                }
            }
        }
    }

    #endregion
}

Ответ 3

Я реализовал приведенный выше пример (класс AttachedProperties). Я создал свойство в моей Viewmodel следующим образом:

    public CalendarBlackoutDatesCollection BlackoutDates
    {
        get
        {
            return _blackoutDates;
        }
        set
        {
            _blackoutDates = value;
            this.RaisePropertyChanged(p => p.BlackoutDates);
        }
    }

Этот объект ViewModel вводит из ObservableBase:

  using System;
  using System.Collections.Generic;
  using System.Linq;
  using System.Text;
  using System.ComponentModel;
  using System.Windows.Data;
  using System.Collections;

     namespace MySolution
     {
        public abstract class ObservableBase : INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;

            public void RaisePropertyChanged(string propertyName)
            {
                if (this.PropertyChanged != null)
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
         }
     }

Это Xaml в окне, которое использует это свойство:

  <Window x:Class="MySolution.MainWindow"

    xmlns:local="clr-namespace:MySolution">
    <Grid>
                    <DatePicker x:Name="datePicker" Grid.Row="0" Height="30" 
                                local:AttachedProperties.RegisterBlackoutDates="{Binding BlackoutDates}">
                    </DatePicker>
    </Grid>

Теперь, когда я хочу добавить BlackoutDates в календарь, я вызываю UpdateCalendarBlackoutDates в моей ViewModel:

    private void UpdateCalendarBlackoutDates()
    {
        CalendarDateRange r = new CalendarDateRange(new DateTime(2010, 12, 9), new DateTime(2010, 12, 9));
        CalendarDateRange r2 = new CalendarDateRange(new DateTime(2010, 12, 10), new DateTime(2010, 12, 10));
        // Because we can't reach the real calendar from the viewmodel, and we can't create a
        // new CalendarBlackoutDatesCollection without specifying a Calendar to
        // the constructor, we provide a "Dummy calendar", only to satisfy
        // the CalendarBlackoutDatesCollection...
        // because you can't do: BlackoutDates = new CalendarBlackoutDatesCollection().
        Calendar dummyCal = new Calendar();
        BlackoutDates = new CalendarBlackoutDatesCollection(dummyCal);
        // Add the dateranges to the BlackOutDates property
        BlackoutDates.Add(r);
        BlackoutDates.Add(r2);
    }

Это отлично работает для меня. Его можно было бы дополнительно усовершенствовать, изменив метод OnRegisterCommandBindingChanged, чтобы принять List of DateRanges вместо CalendarBlackoutDatesCollection и изменить свойство на List следующим образом:

public List<CalendarDateRange> BlackoutDates
{
  etc.

но пока это работает для меня..