RaisePropertyChanged doesn't update binding - wpf

I've come across some strange behavior. I have a control that binds to a property like so:
<HyperlinkButton x:Name="ProjectBeheerLink"
Visibility="{Binding IsBeheerder, Converter={StaticResource VisibilityConverter}}"/>
In my viewmodel I have the property implemented like this:
Public ReadOnly Property IsBeheerder As Boolean
Get
Return iwtApp.AllMyFunctieRollen.Any(Function(x) x.Rol.Equals(Constants.RoleBeheerder))
End Get
End Property
Then when I raise my PropertyChanged event in my callback method
Private Sub GetMyPersonCompleted(ByVal lo As LoadOperation(Of FunctieRol))
'Init FunctieRollen ect. ...
RaisePropertyChanged(Function() Me.IsBeheerder)
End Sub
my binding does not update.
However! If I implement a backend field like this:
_isBeheerder = iwtApp.AllMyFunctieRollen.Any(Function(x) x.Rol.Equals(Constants.RoleBeheerder))
RaisePropertyChanged(Function() Me.IsBeheerder)
And change my property like this:
Public ReadOnly Property IsBeheerder As Boolean
Get
Return _isBeheerder
End Get
End Property
Everything works fine... Can someone explain me this behavior?
Just curious as to why..

The best way to implement a bound property is as follows:
Dim _isBeheerder As Boolean
Public ReadOnly Property IsBeheerder As Boolean
Get
Return _isBeheerder
End Get
Set
If value <> _isBeheerder Then
_isBeheerder = value
RaisePropertyChanged(Function() Me.IsBeheerder)
End If
End Get
End Property
then in your service callback you just set this property:
Private Sub GetMyPersonCompleted(ByVal lo As LoadOperation(Of FunctieRol))
IsBeheerder = myNewValue
End Sub

Related

Detect, whether an exception occured when using "ValidatesOnExceptions"

Imagine a WPF project with an MVVM approach. So, there is a view and a view model.
In the view model I have a property, that might throw an exception in the setter.
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
In the view there is a control bound to this property. And since I like to see my exceptions I switched on ValidatesOnExceptions in the binding and add an ErrorTemplate.
...
<DatePicker SelectedDate="{Binding DateValue, ValidatesOnExceptions=True}"
SelectedDateFormat="Short"
Validation.ErrorTemplate="{StaticResource ErrorTemplate}" />
...
Since you can't reset the value of a DatePicker once you picked one (at least I don't know how to do that) I added a small reset button right next to the DatePicker which is bound to a command which sets the property DateValue of the view model to Nothing. And since I don't want to see this button all the time I bound its Visibility to DateValue.HasValue, so this button only shows, when there's a value to reset.
So far, so good.
But now I have a problem when I pick an invalid date in the DatePicker (one that throws an exception in the properties' setter).
My reset button doesn't show up, since there's no value in the bound property, and I can't reset the DatePicker any other way (at least not that I know of). I'd first have to pick a proper date before I can reset the whole thing.
So, is there any way to determine, whether my property setter threw an exception. There must be a way, since this very error is shown to the user.
Or do I have to manually remember, that I threw an exception in another variable, to be able to access this information when needed?
And how do I "clear" the DatePicker. Since there's no value in the property, setting the property to Nothing will not change anything in the view. How would I get rid of the error?
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>
Remarks
It's best practice in UI design that you always prevent wrong input. If possible you shouldn't allow invalid input e.g. by disabling buttons, hiding invalid options, limiting ranges etc.
Don't allow the user to select invalid options. This leads to a frustrating user experience. One way to prevent wrong input is to use specialized controls. Instead of forcing the user to type a date you offer a DatePicker. This eliminates typos. But it can also eliminate wrong selections by providing only valid dates.
If you want to disallow selecting dates of the past you can narrow down the selectable range:
<!-- Only show dates from today -->
<DatePicker DisplayDateStart="{x:Static system:DateTime.Today}" />
If you have additional illegal dates e.g. holidays you can define a collection of those dates by setting the DatePicker.BlackoutDates property:
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}" />

Converting normal property to dependency property

I have a control that I am using for my new application. This control has a regular property as such.
Public Property Value() As String
Get
If AutoCompleteTextBox.SearchText Is Nothing Then
Return String.Empty
Else
Return AutoCompleteTextBox.SearchText.ToString.Trim
End If
End Get
Set(value As String)
AutoCompleteTextBox.SearchText = value
End Set
End Property
Edit:
So, after multiple tries, I am finally at this stage.
Public Shared ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(AutoCompleteBox))
Public Property Value() As String
Get
Return Me.GetValue(ValueProperty).ToString
End Get
Set(value As String)
Me.SetValue(ValueProperty, value)
End Set
End Property
Public Event PropertyChanged As PropertyChangedEventHandler _
Implements INotifyPropertyChanged.PropertyChanged
This is the dependency property. This property is still not binding. No errors are shown in output window for binding.
Text="{Binding RelativeSource={RelativeSource Self}, Path=Value, Mode=TwoWay}"
This is my binding method. I have no idea what else I can do. At least if there was an error, I could have figured out something. Without any error, I am just a headless chicken here.
Please refer to the following url for all the dependency fundamentals
http://www.wpftutorial.net/dependencyproperties.html
Basically, you can get a property changed event of dependency property by providing a FrameworkPropertyMetadata.
new FrameworkPropertyMetadata( [Default Value],
OnCurrentTimePropertyChanged);
And you can get back the target control (DependencyObject) at the event handler and implement your logic over there
private static void OnCurrentTimePropertyChanged(DependencyObject source,
DependencyPropertyChangedEventArgs e)
{
AutoCompleteTextBox control = source as AutoCompleteTextBox;
string time = (string)e.NewValue;
// Put some update logic here...
}
Declaring a dependency property in a control is a good thing.
You could make some binding in the xaml (sorry I don't have your XAML - I imagine).
Something like :
<TextBox x:Name="AutoCompleteTextBox"
Text="{Binding RelativeSource={RelativeSource=Self},Path=Value}"/>
Regards
TextBox has a property called Text. When you access Text property it will give you text entered in TextBox. Same is your case. Now why you want to convert it into a DP ? A DP would be useful if you want o bind this DP to some other control.
Extend this control itself. Make a new control and introduce this new DP.
While a DP is used where you want to bind this property to some control. This property then gets updated from control or control gets updated from this DP depending upon binding mode set.
How to do binding :
<TextBox x:Name="UserInput" />
<uc:MyAutoCompleteTextBox ValueDP="{Binding Text, ElementName=UserInput, Mode=OneWay}" />
MyAutoCompleteTextBox is new control which extends(inherits) from your old AutoComplete control.
If you want to apply some filtering logic or anything else, you can apply it in your DP itself like this :
Get
someVariable = TryCast(Me.GetValue(ValueProperty), String)
' apply somg logic to someVariable
' use your old Value property from here
Return someVariable
End Get
There are many WPF Binding tutorials on net.
I recommend :
http://blog.scottlogic.com/2012/04/05/everything-you-wanted-to-know-about-databinding-in-wpf-silverlight-and-wp7-part-one.html
Just change your code with following code and you should be good
your code
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(AutoCompleteBox))
Public Property Value() As String
Get
Return TryCast(Me.GetValue(ValueProperty), String)
End Get
Set(value As String)
Me.SetValue(ValueProperty, value)
End Set
End Property
New code
Public Shared ReadOnly ValueProperty As DependencyProperty = DependencyProperty.Register("Value", GetType(String), GetType(AutoCompleteBox))
Public Property Value() As String
Get
If AutoCompleteTextBox.SearchText Is Nothing Then
Return String.Empty
Else
Return AutoCompleteTextBox.SearchText.ToString.Trim
End If
End Get
Set(value As String)
AutoCompleteTextBox.SearchText = value
End Set
End Property
This DP will do what your Older property was doing. But just think about your requirement there can be a better way of writing the things.
Thanks

How to update the View using an init method in the ViewModel?

I'm new in WPF and MVVM (I'm using MvvmLight Toolkit) so the way I'm coding is maybe wrong.
The scenario is the following:
I would like to perform initialisation stuff every time i switch views.
So I have created a new InitViewModelBase which inherits ViewModel
Public MustInherit Class InitViewModelBase
Inherits ViewModelBase
...
Public MustOverride Sub Init()
End Class
I call this Init method when switching view
Private _currentViewModel As InitViewModel
Public Property CurrentViewModel() As InitViewModel
Get
Return _currentViewModel
End Get
Set(ByVal value As InitViewModel)
If _currentViewModel IsNot value Then
_currentViewModel = value
_currentViewModel.Init
End If
End Set
End Property
Now I would like to update for example the content of a Label in the Init method
Private _test As String
Public Property Test() As String = "TEST"
Get
Return _test
End Get
Set(ByVal value As String)
_test = value
RaisePropertyChanged("Test")
End Set
End Property
Public Overrides Sub Init()
Test = "UPDATE"
End Sub
<Label Content="{Binding Test}"/>
Why is my label content never updated when i set a new value in my Init method ?
If I set a new value in the constructor it works great.

Update Binding on Composite Property when Child Property Changes

Let's say I have a property called Greeting that is principally composed of two bound properties: LastName and FirstName. Can I subscribe to updates on first and last name so I can force a refresh with OnPropertyChanged() for my Greeting property. Here's a simple example:
View
<TextBox Text="{Binding FirstName}" />
<TextBox Text="{Binding LastName}" />
<TextBlock Text="{Binding Greeting}" />
ViewModel
Public Property FirstName() As String
Get
Return _firstName
End Get
Set(ByVal value As String)
_firstName = value
OnPropertyChanged("FirstName")
End Set
End Property
'... Omitting LastName for brevity ...
Public ReadOnly Property Greeting() As String
Get
Return String.Format("Hello {0} {1}", Firstname, LastName)
End Get
End Property
The way this is currently set up, nothing will ever update the Greeting binding. I could put OnPropertyChanged("Greeting") in the setter for FirstName and LastName, but this feels wrong. In a more complex example, I'd rather each object just take care of refreshing itself when something changes.
Q:) Can I force an update for a ReadOnly Property when one of the Properties it's composed of changes?
You can call PropertyChange of Greetings from setter of FirstName and LastName
Public Property FirstName() As String
Get
Return _firstName
End Get
Set(ByVal value As String)
_firstName = value
OnPropertyChanged("FirstName")
OnPropertyChanged("Greeting")
End Set
End Property
OR
You can subscribe to PropertyChanged of your ViewModel in itself
AddHandler this.PropertyChanged, AddressOf PropertyChanged
and in PropertyChanged you can check which property is changed depending on that you can RaisePropertyChanged for Greeting
Borrowing from nit's answer, just to round it out a little. Here's what I did to fire an update on the Greeting property when FirstName or LastName changes:
Private Sub UpdateGreeting(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) _
Handles Me.PropertyChanged
If e.PropertyName = "FirstName" OrElse e.PropertyName="LastName" Then
OnPropertyChanged("Greeting")
End If
End Sub
It handles the PropertyChanged event that is already implemented as part of the INotifyPropertyChanged interface on the ViewModel.
Then it checks if the PropertyName value of the event argument is equal to "FirstName" or "LastName".
If so, it manually raises the OnPropertyChanged() method for the Greeting property.

Bind to my class with INotifyPropertyChanged

I am sorry if this question is double somewhere, I've searched but did not find.
I have created my own class.
Public Class MyListService
Implements INotifyPropertyChanged
Public Event PropertyChanged(sender As Object, e As System.ComponentModel.PropertyChangedEventArgs) Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged
Private Sub OnPropertyChanged(ByVal Title As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(Title))
End Sub
Private _IsLoggedIn As Boolean = False
Public Property IsLoggedIn As Boolean
Get
Return _IsLoggedIn
End Get
Set(value As Boolean)
If _IsLoggedIn <> value Then
_IsLoggedIn = value
Call OnPropertyChanged("IsLoggedIn")
End If
End Set
End Property
End Class
In WPF project, I have in codebehind
Private WithEvents cWebService As new MyListService
In XAML:
<CheckBox IsChecked="{Binding IsLoggedIn}" x:Name="chkIsLoggedIn" />
Please can you tell me how to bind that "IsLoggedIn" property now to the Checkbox?
Regards
I have absolutely no VB experience but I've used WPF with C#.
Here is my guess:
you need to set DataContext of your CheckBox to point to the MyListService instance for the binding to work since the binding systems needs to know which object the IsLoggedIn property belongs to.

Resources