Как установить случайное ограничение на выбор дня в datagrid с отображением количества лимита в wpf?
Подскажите пожалуйста, как выставить рандомное число лимита в разные дни. Необходимо, чтобы при выборе даты проверялись другие даты и, при условии, что лимит не превышен, эта дата может быть установлена.
Ответы (1 шт):
Напишу пример того, о чем я говорил в комментариях.
Подготовительные работы
Возиться с вашим кодом такое себе занятие, пожалуй, создам все с чистого листа, а там уже сами смотрите, надо вам такое или нет.
Создаем отдельный класс, в котором будут храниться все свойства для нашего окна, назовем его
MainViewModel(про MVVM тут речи нет, но советую почитать).Зададим нашему окну
DataContext, который будет ссылаться на класс, который создали ранее:public MainWindow() { InitializeComponent(); DataContext = new MainViewModel(); }По правилам MVVM такой подход плохо, советую почитать это.
Далее создаем класс, который будет описывать наш выводимый объект, пусть наше приложение выводит список задач, где каждая задача будет иметь название и дату, когда эту задачу надо выполнить. Значит нам нужен класс одной задачи:
public class UserTask { public UserTask(string name) => Name = name; public string Name { get; set; } = string. Empty; public DateTime Finish { get; set; } }Теперь мы можем сделать сам список задач. Идем в класс
MainViewModelи создаем там простую коллекцию нашего класса, заодно и заполним ее:public List<UserTask> Tasks { get; } = new() { new("Помыть посуду"), new("Покормить кота"), new("Сходить в душ"), new("Заняться спортом"), new("Поесть") };Обратите внимание, это публичное свойство, только к ним возможна привязка, помните это!
Имея это, идем в 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.
Все, мы подготовили проект под наши нужды, выглядеть будет так:
Реализуем "плохие даты"
Сейчас наша цель, сделать простую коллекцию DateTime типов, при добавлении в которую, все привязанные к ней DatePicker должны заблокировать эту дату для выбора.
За основу взял ответ с En SO.
За блокировку дат у
DatePickerотвечаетBlackoutDates, это простая коллекция объектовCalendarDateRange, только вот проблема, мы не можем привязать к ней чего либо, в следствии чего нам нужно сделать некую "обертку", некое "расширение", которое позволит указать нужную нам коллекцию в качестви привязки. Чтож, делаем простой класс и наследуем его отDependencyObject, пусть будет называтьсяDatePickerHelper.Создаем "Присоединяемое свойство" (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- это некий объект, который позволяет указать значение по умолчанию, а также подписаться на событие изменения привязки, оно нам и нужно. Сам обработчик события пока пустой.Теперь наша цель, это при новой привязке, задавать на основе полученных данных, нужный нам вид для
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, если все хорошо, то очищаем ранее заданные заблокированные даты, и добавляем новые, из нашей коллекции.Давайте проверим, что получилось, для этого
В
MainViewModelсоздадим новое свойство наших дат:public ObservableCollection<DateTime> BlackoutDates { get; } = new() { DateTime.Now.AddDays(1), DateTime.Now.AddDays(2) };В XAML, у
DatePickerпропишемlocal:DatePickerHelper.BlackoutDates="{Binding BlackoutDates}".Если все сделали правильно, то должно заблокировать указанные даты в календаре
У нас сейчас есть один недостаток, при добавлении или удалении дат в коллекцию, наше приложение не будет это обновлять, давайте исправим:
В
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, если меньше, наоборот удаляем (если есть). - Ну а так, тут уже полет фантазии, можно сделать по-разному.

