Как установить случайное ограничение на выбор дня в datagrid с отображением количества лимита в wpf?

Подскажите пожалуйста, как выставить рандомное число лимита в разные дни. Необходимо, чтобы при выборе даты проверялись другие даты и, при условии, что лимит не превышен, эта дата может быть установлена.


Ответы (1 шт):

Автор решения: EvgeniyZ

Напишу пример того, о чем я говорил в комментариях.

Подготовительные работы

Возиться с вашим кодом такое себе занятие, пожалуй, создам все с чистого листа, а там уже сами смотрите, надо вам такое или нет.

  1. Создаем отдельный класс, в котором будут храниться все свойства для нашего окна, назовем его MainViewModel (про MVVM тут речи нет, но советую почитать).

  2. Зададим нашему окну DataContext, который будет ссылаться на класс, который создали ранее:

    public MainWindow()
    {
        InitializeComponent();
        DataContext = new MainViewModel();
    }
    

    По правилам MVVM такой подход плохо, советую почитать это.

  3. Далее создаем класс, который будет описывать наш выводимый объект, пусть наше приложение выводит список задач, где каждая задача будет иметь название и дату, когда эту задачу надо выполнить. Значит нам нужен класс одной задачи:

    public class UserTask
    {
        public UserTask(string name)
            => Name = name;
    
        public string Name { get; set; } = string. Empty;
        public DateTime Finish { get; set; }
    }
    
  4. Теперь мы можем сделать сам список задач. Идем в класс MainViewModel и создаем там простую коллекцию нашего класса, заодно и заполним ее:

    public List<UserTask> Tasks { get; } = new()
    {
        new("Помыть посуду"),
        new("Покормить кота"),
        new("Сходить в душ"),
        new("Заняться спортом"),
        new("Поесть")
    };
    

    Обратите внимание, это публичное свойство, только к ним возможна привязка, помните это!

  5. Имея это, идем в XAML и пишем нужный нам вид, допустим, такой:

    <ItemsControl ItemsSource="{Binding Tasks}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Border
                    Margin="5"
                    Padding="5"
                    BorderBrush="Gray"
                    BorderThickness="1"
                    CornerRadius="5">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
    
                        <TextBlock
                            Grid.Column="0"
                            FontWeight="Medium"
                            Text="{Binding Name}" />
                        <DatePicker
                            Grid.Column="1"
                            Padding="0"
                            Background="Transparent"
                            BorderThickness="0"
                            SelectedDate="{Binding Finish}" />
                    </Grid>
                </Border>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
    

    С привязками вы знакомы, а значит и смысла объяснять, что и как нет. В двух словах мы просто через {Binding ...} указываем публичное свойство из класса, который установлен как DataContext.

Все, мы подготовили проект под наши нужды, выглядеть будет так:

Temp Project

Реализуем "плохие даты"

Сейчас наша цель, сделать простую коллекцию DateTime типов, при добавлении в которую, все привязанные к ней DatePicker должны заблокировать эту дату для выбора.
За основу взял ответ с En SO.

  1. За блокировку дат у DatePicker отвечает BlackoutDates, это простая коллекция объектов CalendarDateRange, только вот проблема, мы не можем привязать к ней чего либо, в следствии чего нам нужно сделать некую "обертку", некое "расширение", которое позволит указать нужную нам коллекцию в качестви привязки. Чтож, делаем простой класс и наследуем его от DependencyObject, пусть будет называться DatePickerHelper.

  2. Создаем "Присоединяемое свойство" (AttachedProperty), написав примерно следующее:

     public static readonly DependencyProperty BlackoutDatesProperty =
         DependencyProperty.RegisterAttached(
             name: "BlackoutDates",
             propertyType: typeof(ObservableCollection<DateTime>),
             ownerType: typeof(DatePickerHelper),
             defaultMetadata: new (null, OnBindingChanged));
    
     public static void SetBlackoutDates(DependencyObject d, ObservableCollection<DateTime> value) 
         => d.SetValue(BlackoutDatesProperty, value);
    
     public static ObservableCollection<DateTime> GetBlackoutDates(DependencyObject d)
         => (ObservableCollection<DateTime>)d.GetValue(BlackoutDatesProperty);
    
     private static void OnBindingChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
     {
         // TODO
     }
    

    Тут все довольно просто, мы делаем статичные Get и Set методы, которые нашему свойству будут задавать/забирать значение. Само свойство имеет название, тип (нам нужна коллекция дат, указываем значит ее), а также typeMetadata - это некий объект, который позволяет указать значение по умолчанию, а также подписаться на событие изменения привязки, оно нам и нужно. Сам обработчик события пока пустой.

  3. Теперь наша цель, это при новой привязке, задавать на основе полученных данных, нужный нам вид для BlackoutDates:

     private static void OnBindingChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs)
     {
         if (dependencyObject is DatePicker datePicker && eventArgs.NewValue is ObservableCollection<DateTime> blackoutDates)
         {
             datePicker.BlackoutDates.Clear();
             foreach (var date in blackoutDates)
             {
                 datePicker.BlackoutDates.Add(new(date));
             }
         }
     }
    

    Тут мы проверяем, нужный нам объект приходит или нет, тем самым получая и коллекцию дат, и DatePicker, если все хорошо, то очищаем ранее заданные заблокированные даты, и добавляем новые, из нашей коллекции.

  4. Давайте проверим, что получилось, для этого

    • В MainViewModel создадим новое свойство наших дат:

      public ObservableCollection<DateTime> BlackoutDates { get; } = new()
      {
          DateTime.Now.AddDays(1),
          DateTime.Now.AddDays(2)
      };
      
    • В XAML, у DatePicker пропишем local:DatePickerHelper.BlackoutDates="{Binding BlackoutDates}".

    • Если все сделали правильно, то должно заблокировать указанные даты в календаре

      Dates Test

  5. У нас сейчас есть один недостаток, при добавлении или удалении дат в коллекцию, наше приложение не будет это обновлять, давайте исправим:

    • В OnBindingChanged, после цикла foreach подписываемся на событие обновления коллекции, примерно следующим образом:

      blackoutDates.CollectionChanged += (_, e) => OnCollectionChanged(datePicker, e);
      
    • Сам обработчик делаем таким:

      private static void OnCollectionChanged(DatePicker datePicker, NotifyCollectionChangedEventArgs args)
      {
      
      }
      

      Думаю заметили, что мы передаем сюда дополнительно еще и DatePicker, из-за чего такой "странный" подход к подписыванию на событие.

    • В обработчике теперь прописываем все нужные действия:

      private static void OnCollectionChanged(DatePicker datePicker, NotifyCollectionChangedEventArgs args)
      {
          if (args.Action == NotifyCollectionChangedAction.Add && args.NewItems is not null) 
          {
              foreach (DateTime date in args.NewItems)
              {
                  datePicker.BlackoutDates.Add(new(date));
              }
          }
          else if (args.Action == NotifyCollectionChangedAction.Remove && args.OldItems is not null)
          {
              foreach (DateTime date in args.OldItems)
              {
                  var item = datePicker.BlackoutDates.First(x => x.Start == date);
                  datePicker.BlackoutDates.Remove(item);
              }
          }
      }
      

      Я тут прописал добавление новых и удаление старых, как и что я думаю уже догадались, просто приводим в нужный вид, а затем при помощи foreach делаем нужные манипуляции.

Собственно, вот и все, мы получили удобный проект, где без труда можем заблокировать любую нужную нам дату в любой момент. Остается лишь одно, блокировать их тогда, когда на указанную дату у нас уже есть несколько записей, только это я думаю оставлю уже для вас, а то что я ваше учебное задание делаю за вас) Могу лишь подсказать как сделал бы я:

  • Создал бы словарь, который ключом будет иметь дату, а значением число, после которого дата должна блокироваться.
  • В класс данных (у нас это UserTask) добавил событие, допустим просто public event Action<DateTime> OnDateUpdate;.
  • При добавлении в коллекцию Task, подписался бы на это событие, а чтоб это делалось автоматически, тут можно провернуть аналогичный трюк, как выше с добавлением/удалением новых данных в ObservableCollection<>, то есть, по его событию обновления, на новые объекты подписываемся, а от удаленных (старых) наоборот, отписываемся.
  • В обработчике события (а он будет один), делаем проверку на наличие ключа в словаре, если есть, то считаем, сколько у нас задач с выбранной датой (var items = Tasks.Count(x => x.Finish == ...)), ну и если число равно или больше из словаря, то добавляем дату в коллекцию BlackoutDates, если меньше, наоборот удаляем (если есть).
  • Ну а так, тут уже полет фантазии, можно сделать по-разному.
→ Ссылка