Определить, возникло ли исключение при использовании «ValidatesOnExceptions» - PullRequest
0 голосов
/ 10 июля 2020

Представьте себе проект WPF с подходом MVVM. Итак, есть представление и модель представления.

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

Public Property DateValue As Nullable(Of Date)
    Get
        Return _dateValue
    End Get
    Set(value As Nullable(Of Date))
        If value.HasValue Then
            If value.Value < Date.Today Then
                Throw New Exception("Error Message")
            End If
        End If

        _dateValue = value
        'skipped NotifyPropertyChanged in this example for the sake of simplicity
    End Set
End Property

В представлении есть элемент управления, связанный с этим свойством. И поскольку мне нравится видеть свои исключения, я включил ValidatesOnExceptions в привязке и добавил ErrorTemplate.

...
<DatePicker SelectedDate="{Binding DateValue, ValidatesOnExceptions=True}"
            SelectedDateFormat="Short"
            Validation.ErrorTemplate="{StaticResource ErrorTemplate}" />
...

Поскольку вы не можете сбросить значение DatePicker после того, как выбрали его (по крайней мере, я не знаю, как это сделать) Я добавил небольшую кнопку сброса рядом с DatePicker, которая привязана к команде, которая устанавливает свойство DateValue модели представления на Nothing. И поскольку я не хочу видеть эту кнопку все время, я привязал ее Visibility к DateValue.HasValue, поэтому эта кнопка отображается только тогда, когда есть значение для сброса.

Пока все хорошо.

Но теперь у меня проблема, когда я выбираю недопустимую дату в DatePicker (та, которая вызывает исключение в установщике свойств).

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

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

Или мне нужно вручную помнить, что я создал исключение в другой переменной, чтобы иметь возможность получить доступ к этой информации при необходимости?

А как "очистить" DatePicker. Поскольку в свойстве нет значения, установка свойства на Nothing ничего не изменит в представлении. Как мне избавиться от ошибки?

1 Ответ

0 голосов
/ 11 июля 2020
Class ViewModel
    Inherits INotifyPropertyChanged

    Private selectedDate As DateTime

    Public Property SelectedDate As DateTime
        Get
            Return Me.selectedDate
        End Get
        Set(ByVal value As DateTime)

            If value < DateTime.Today Then
                Throw New ArgumentException("Date can't be in the past.")
            End If

            Me.selectedDate = value
            OnPropertyChanged()
        End Set
    End Property

    Public ReadOnly Property ResetDateCommand As ICommand
        Get
            Return New RelayCommand(AddressOf ExecuteResetDate)
        End Get
    End Property

    Public Sub New()
        Me.SelectedDate = DateTime.Today
    End Sub

    Public Sub ExecuteResetDate(ByVal commandParameter As Object)
        Return CSharpImpl.__Assign(Me.SelectedDate, DateTime.Today)
    End Sub
End Class

MainWindow.xaml

<Window>
  <Window.Resources>
    <ViewModel />
  </Window.Resources>
  
  <StackPanel x:Name="RootPanel" viewModels:Item.IsMarkedAsRead="True">
    <Button Content="Reset Date"
            Visibility="{Binding ElementName=DatePicker, Path=(Validation.HasError), Converter={StaticResource BooleanToVisibilityConverter}}"
            Command="{Binding ResetDateCommand}" />
    <DatePicker x:Name="DatePicker" 
                SelectedDate="{Binding SelectedDate, ValidatesOnExceptions=True}"
                SelectedDateFormat="Short" />
  </StackPanel>
</Window>

Замечания

Лучшая практика в дизайне пользовательского интерфейса - всегда предотвращать неправильный ввод. Если возможно, вы не должны разрешать недопустимый ввод, например, отключая кнопки, скрывая недопустимые параметры, ограничивая диапазоны и т. Д. c. Не позволяйте пользователю выбирать недопустимые параметры. Это приводит к разочарованию пользователей. Один из способов предотвратить неправильный ввод - использовать специальные элементы управления. Вместо того, чтобы заставлять пользователя вводить дату, вы предлагаете DatePicker. Это исключает опечатки. Но он также может устранить неправильный выбор, предоставив только действительные даты.

Если вы хотите запретить выбор дат в прошлом, вы можете сузить диапазон выбора:

<!-- Only show dates from today -->
<DatePicker DisplayDateStart="{x:Static system:DateTime.Today}" />

Если у вас есть дополнительные недопустимые даты, например праздники, вы можете определить набор этих дат, установив свойство DatePicker.BlackoutDates:

DatePickerHelper.cs

Class DatePickerHelper
    Inherits DependencyObject

    Public Shared ReadOnly BlackedDaysProperty As DependencyProperty = DependencyProperty.RegisterAttached("BlackedDays", GetType(IEnumerable(Of CalendarDateRange)), GetType(DatePickerHelper), New PropertyMetadata(Nothing, AddressOf DatePickerHelper.OnBlackDatesChanged))

    Public Shared Sub SetBlackedDays(ByVal attachingElement As DependencyObject, ByVal value As IEnumerable(Of CalendarDateRange))
        Return attachingElement.SetValue(DatePickerHelper.BlackedDaysProperty, value)
    End Sub

    Public Shared Function GetBlackedDays(ByVal attachingElement As DependencyObject) As IEnumerable(Of CalendarDateRange)
        Return CType(attachingElement.GetValue(DatePickerHelper.BlackedDaysProperty), IEnumerable(Of CalendarDateRange))
    End Function

    Private Shared ReadOnly Property DatePickerTable As Dictionary(Of INotifyCollectionChanged, DatePicker)

    Private Shared Sub New()
        DatePickerHelper.DatePickerTable = New Dictionary(Of INotifyCollectionChanged, DatePicker)()
    End Sub

    Private Shared Sub OnBlackDatesChanged(ByVal d As DependencyObject, ByVal e As DependencyPropertyChangedEventArgs)
        Dim attachedDatePicker = TryCast(d, DatePicker)
        Dim oldObservableCollection As INotifyCollectionChanged = Nothing

        If CSharpImpl.__Assign(oldObservableCollection, TryCast(e.OldValue, INotifyCollectionChanged)) IsNot Nothing Then
            oldObservableCollection.CollectionChanged -= AddressOf UpdateDatePickerBlockedDates
            DatePickerHelper.DatePickerTable.Remove(oldObservableCollection)
        End If

        Dim newObservableCollection As INotifyCollectionChanged = Nothing

        If CSharpImpl.__Assign(newObservableCollection, TryCast(e.NewValue, INotifyCollectionChanged)) IsNot Nothing Then
            newObservableCollection.CollectionChanged += AddressOf UpdateDatePickerBlockedDates
            DatePickerHelper.DatePickerTable.Add(newObservableCollection, attachedDatePicker)
        End If

        attachedDatePicker.BlackoutDates.AddRange(TryCast(e.NewValue, IEnumerable(Of CalendarDateRange)))
    End Sub

    Private Shared Sub UpdateDatePickerBlockedDates(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
        Dim attachedDatePicker As DatePicker = Nothing

        If Not DatePickerHelper.DatePickerTable.TryGetValue(TryCast(sender, INotifyCollectionChanged), attachedDatePicker) Then
            Return
        End If

        Select Case e.Action
            Case NotifyCollectionChangedAction.Add
                attachedDatePicker.BlackoutDates.AddRange(e.NewItems.OfType(Of CalendarDateRange)())
            Case NotifyCollectionChangedAction.Remove, NotifyCollectionChangedAction.Replace
                e.OldItems.OfType(Of CalendarDateRange)().ToList().ForEach(Function(removedItem) attachedDatePicker.BlackoutDates.Remove(removedItem))
            Case NotifyCollectionChangedAction.Move
            Case NotifyCollectionChangedAction.Reset
                attachedDatePicker.BlackoutDates.Clear()
            Case Else
        End Select
    End Sub
End Class

ViewModel.cs

Class ViewModel
    Public Property BlockedDates As ObservableCollection(Of CalendarDateRange)

    // Block Christmas holidays and all days in the past
    Public Sub New()
        Return CSharpImpl.__Assign(Me.BlockedDates, New ObservableCollection(Of CalendarDateRange) From {
            New CalendarDateRange(New DateTime(2020, 12, 24), New DateTime(2020, 12, 26)),
            New CalendarDateRange(DateTime.MinValue, DateTime.Today.Subtract(TimeSpan.FromDays(1)))
        })
    End Sub
End Class

MainWindow.xaml

<!-- Only show dates from today -->
<DatePicker DatePickerHelper.BlackedDays="{Binding BlockedDates}" />
...