Delayed assignment to a WithEvents backing field - wpf

I've noticed that when a property's backing field has the WithEvents modifier, value assignment can "lag" for lack of better words. I've reproduced the behavior in a simple demo, so the purpose of WithEvents won't be evident here (and thus it won't be constructive to say "just get rid of it")
Public Class ItemViewModel
Public Property Id As Integer
End Class
Public Class ViewModel
Inherits ViewModelBase
Private WithEvents _item As ItemViewModel = New ItemViewModel() With {.Id = 0}
Public Property Item As ItemViewModel
Get
Return _item
End Get
Set(value As ItemViewModel)
SetProperty(_item, value)
End Set
End Property
...
SetProperty definition:
Protected Function SetProperty(Of T)(ByRef field As T, value As T, <CallerMemberName> Optional name As String = Nothing) As Boolean
If (EqualityComparer(Of T).Default.Equals(field, value)) Then
Return False
End If
field = value
NotifyPropertyChanged(name)
Return True
End Function
When I update the Item property to be a new item with an incremented id, the property getter is hit as soon as the event fires, as expected. However, the value of the backing field is still the old value! If I add another PropertyChanged event right after the SetProperty call, the backing field will have the correct value at that point. Of course, if I take out WithEvents, it works as expected with only one event.
This is the only time I've seen SetProperty fail in such a way. What is the problem that WithEvents is causing?
UPDATE: When ViewModel implements INotifyPropertyChanged directly, instead of inheriting from the base, and raises PropertyChanged after setting the value, it works.

What's going on here is that WithEvents is a feature that the .NET Framework itself does not natively support. VB.NET is implementing it on top of .NET. The feature is there because it was also provided by VB6. The way the feature was implemented in VB6, though, is very different because of a fundamental difference in the event models between COM and .NET.
I won't go into how VB6 implemented the feature; that isn't really relevant. What's important is how events work with .NET. Basically, with .NET, events have to be explicitly hooked and unhooked. When events are defined, there are a lot of parallels with how properties are defined. In particular, there is a method that adds a handler to an event and a method that removes a handler, similar to the symmetry between the "set" and "get" methods a property has.
The reason events use methods like this is to hide the list of attached handlers from outside callers. If code outside of a class had access to the full list of attached handlers, it would be possible for it to interfere with it, which would be a very poor programming practice potentially resulting in very confusing behaviour.
VB.NET exposes direct calls to these event "add" and "remove" methods through the AddHandler and RemoveHandler operators. In C#, exactly the same underlying operation is expressed using the += and -= operators, where the left-hand argument is an event member reference.
What WithEvents gives you is syntactic sugar that hides the AddHandler and RemoveHandler calls. What's important to recognize is that the calls are still there, they're just implicit.
So, when you write code like this:
Private WithEvents _obj As ClassWithEvents
Private Sub _obj_GronkulatedEvent() Handles _obj.GronkulatedEvent
...
End Sub
..you are asking VB.NET to ensure that whatever object is assigned to _obj (keeping in mind that you can change that object reference at any time), the event GronkulatedEvent should be handled by that Sub. If you change the reference, then the old object's GronkulatedEvent should be immediately detached, and the new object's GronkulatedEvent attached.
VB.NET implements this by turning your field into a property. Adding WithEvents means that the field _obj (or, in your case, _item) is not actually a field. A secret backing field is created, and then _item becomes a property whose implementation looks like this:
Private __item As ItemViewModel ' Notice this, the actual field, has two underscores
Private Property _item As ItemViewModel
<CompilerGenerated>
Get
Return __item
End Get
<CompilerGenerated, MethodImpl(Synchronized)>
Set(value As ItemViewModel)
Dim previousValue As ItemViewModel = __item
If previousValue IsNot Nothing Then
RemoveHandler previousValue.GronkulatedEvent, AddressOf _item_GronkulatedEvent
End If
__item = value
If value IsNot Nothing Then
AddHandler value.GronkulatedEvent, AddressOf _item_GronkulatedEvent
End If
End Set
End Property
So, why does this cause the "lag" you see? Well, you can't pass a property "ByRef". To pass something "ByRef", you need to know its memory address, but a property hides the memory address behind "get" and "set" methods. In a language like C#, you would simply get a compile-time error: A property is not an L-value, so you cannot pass a reference to it. However, VB.NET is more forgiving and writes extra code behind the scenes to make things work for you.
In your code, you are passing what looks like a field, the _item member, into SetProperty, which takes the parameter ByRef so it can write a new value. But, due to WithEvents, the _item member is really a property. So, what does VB.NET do? It creates a temporary local variable for the call to SetProperty, and then assigns it back to the property after the call:
Public Property Item As ItemViewModel
Get
Return _item ' This is actually a property returning another property -- two levels of properties wrapping the actual underlying field -- but VB.NET hides this from you
End Get
Set
' You wrote: SetProperty(_item, value)
' But the actual code emitted by the compiler is:
Dim temporaryLocal As ItemViewModel = _item ' Read from the property -- a call to its Get method
SetProperty(temporaryLocal, value) ' SetProperty gets the memory address of the local, so when it makes the assignment, it is actually writing to this local variable, not to the underlying property
_item = temporaryLocal ' Once SetProperty returns, this extra "glue" code passes the value back off to the property, calling its Set method
End Set
End Property
So, because WithEvents converted your field to a property, VB.NET had to defer the actual assignment to the property until after the call to SetProperty returns.
Hope that makes sense! :-)

Related

Property vs. Variable as ByRef parameter

I created a base class that implements the INotifyPropertyChanged interface. This class also contains a generic function SetProperty to set the value of any property and raise the PropertyChanged event, if necessary.
Public Class BaseClass
Implements INotifyPropertyChanged
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Protected Function SetProperty(Of T)(ByRef storage As T, value As T, <CallerMemberName> Optional ByVal propertyName As String = Nothing) As Boolean
If Object.Equals(storage, value) Then
Return False
End If
storage = value
Me.OnPropertyChanged(propertyName)
Return True
End Function
Protected Overridable Sub OnPropertyChanged(<CallerMemberName> Optional ByVal propertyName As String = Nothing)
If String.IsNullOrEmpty(propertyName) Then
Throw New ArgumentNullException(NameOf(propertyName))
End If
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
End Class
Then I have a class, that is supposed to hold some data. For the sake of simplicity it only contains one property (in this example).
Public Class Item
Public Property Text As String
End Class
Then I have a third class that inherits from the base class and uses the data holding class. This third class is supposed to be a ViewModel for a WPF window.
I don't list the code for the RelayCommand class, since you probably all have an implementation yourself. Just keep in mind, that this class executes the given function, when the command is executed.
Public Class ViewModel
Inherits BaseClass
Private _text1 As Item 'data holding class
Private _text2 As String 'simple variable
Private _testCommand As ICommand = New RelayCommand(AddressOf Me.Test)
Public Sub New()
_text1 = New Item
End Sub
Public Property Text1 As String
Get
Return _text1.Text
End Get
Set(ByVal value As String)
Me.SetProperty(Of String)(_text1.Text, value)
End Set
End Property
Public Property Text2 As String
Get
Return _text2
End Get
Set(ByVal value As String)
Me.SetProperty(Of String)(_text2, value)
End Set
End Property
Public ReadOnly Property TestCommand As ICommand
Get
Return _testCommand
End Get
End Property
Private Sub Test()
Me.Text1 = "Text1"
Me.Text2 = "Text2"
End Sub
End Class
And then I have my WPF window that uses the ViewModel class as its DataContext.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfTest"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding Text1}" Height="24" Width="100" />
<TextBox Text="{Binding Text2}" Height="24" Width="100" />
<Button Height="24" Content="Fill" Command="{Binding TestCommand}" />
</StackPanel>
</Window>
As you can see, this window contains only two TextBoxes and a button. The TextBoxes are bound to the properties Text1 and Text2 and the button is supposed to execute the command TestCommand.
When the command is executed both properties Text1 and Text2 is given a value. And since both properties raise the PropertyChanged event, these values should be shown in my window.
But only the value "Text2" is shown in my window.
The value of property Text1 is "Text1", but it seems that the PropertyChanged event for this property is raised before the property got its value.
Is there any way to change the SetProperty function in my base class to raise the PropertyChanged after the property got its value?
Thank you for your help.
What actually happens ?
This doesn't work because the properties don't behave as fields do.
When you do Me.SetProperty(Of String)(_text2, value), what happens is that the reference to the field _text2 is passed instead of its value, so the SetProperty function can modify what's inside the reference, and the field is modified.
However, when you do Me.SetProperty(Of String)(_text1.Text, value), the compiler sees a getter for a property, so it will first call the Get property of _text1, then pass the reference to the return value as parameter. So when your function SetProperty is receving the ByRef parameter, it is the return value from the getter, and not the actual field value.
From what I understood here, if you say that your property is ByRef, the compiler will automatically change the field ref when you exit the function call... So that would explain why it's changing after your event...
This other blog seems to confirm this strange behavior.
In C#, the equivalent code wouldn't compile. .NET isn't comfortable passing properties by reference, for reasons which folks like Eric Lippert have gone into elsewhere (I dimly recall Eric addressing the matter vis a vis C# somewhere on SO, but can't find it now -- loosely speaking, it would require one weird workaround or another, all of which have shortcomings that the C# team regards as unacceptable).
VB does it, but as a rather strange special case: The behavior I'm seeing is what I would expect if it were creating a temporary variable which is passed by reference, and then then assigning its value to the property after the method completes. This is a workaround (confirmed by Eric Lippert himself below in comments, see also #Martin Verjans' excellent answer) with side effects that are counterintuitive for anybody who doesn't know how byref/ref are implemented in .NET.
When you think about it, they can't make it work properly, because VB.NET and C# (and F#, and IronPython, etc. etc.) must be mutually compatible, so a VB ByRef parameter must be compatible with a C# ref argument passed in from C# code. Therefore, any workaround has to be entirely the caller's responsibility. Within the bounds of sanity, that limits it to what it can do before the call begins, and after it returns.
Here's what the ECMA 335 (Common Language Infrastructure) standard has to say (Ctrl+F search for "byref"):
§I.8.2.1.1 Managed pointers and related types
A managed pointer (§I.12.1.1.2), or byref (§I.8.6.1.3, §I.12.4.1.5.2), can point to a local variable, parameter, field of a compound type, or element of an array. ...
In other words, as far as the compiler is concerned, ByRef storage As T is actually the address of a storage location in memory where the code puts a value. It's very efficient at runtime, but offers no scope for syntactic sugar magic with getters and setters. A property is a pair of methods, a getter and a setter (or just one or the other, of course).
So as you describe, storage gets the new value inside SetProperty(), and after SetProperty() completes, _text1.Text has the new value. But the compiler has introduced some occult shenanigans which cause the actual sequence of events not to be what you expect.
As a result, SetProperty cannot be used in Text1 the way you wrote it. The simplest fix, which I have tested, is to call OnPropertyChanged() directly in the setter for Text1.
Public Property Text1 As String
Get
Return _text1.Text
End Get
Set(ByVal value As String)
_text1.Text = value
Me.OnPropertyChanged()
End Set
End Property
There's no way to handle this that isn't at least a little bit ugly. You could give Text1 a regular backing field like Text2 has, but then you'd need to keep that in sync with _text1.Text. That's uglier than the above IMO because you have to keep the two in sync, and you still have extra code in the Text1 setter.

Working with ObservableCollections in WPF and MVVM

I'm fairly new to WPF and still try to get the feeling on how to do something with built-in functions rather than inventing the wheel on my own again.
Today I stumbled upon a problem, that I couldn't solve with built-in functions and the possible ways I could think of I didn't like very much. So hopefully you can point me in the right direction or even can name a clever way with built-in functions.
So, for the sake of simplicity let's say I'd like to write a ViewModel for the MailMessage class that can be found in the System.Net.Mail namespace.
Imports System.Collections.ObjectModel
Imports System.Net.Mail
Public Class MailMessageViewModel
Private _message As MailMessage
...
End Class
A MailMessage object has (among others) a property To of type MailAddressCollection containing all the recipients for my e-mail as MailAddress objects.
In my ViewModel I wrap this collection of MailAddress objects into an ObservableCollection.
And here's my first question, how do I do that. Do I use:
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
Return New ObservableCollection(Of MailAddress)(_message.To)
End Get
End Property
or do I use:
Private _recipients As ObservableCollection(Of MailAddress)
Public ReadOnly Property Recipients As ObservableCollection(Of MailAddress)
Get
If _recipients Is Nothing Then
_recipients = New ObservableCollection(Of MailAddress)(_message.To)
End If
Return _recipients
End Get
End Property
My view model now has a bindable property Recipients.
Now I'd like to be able to delete an e-mail address from the To collection of my MailMessage.
But when I delete an address from the ObservableCollection, my UI gets updated properly, but the To collection stays untouched. If I delete directly from the To collection of my MailMessage, the ObservableCollection and therefore my UI don't reflect the changes.
Do I really have to wire the ObservableCollection and the corresponding source collection manually by using the CollectionChanged event or by doing all changes twice (in the ObservableCollection and in the real collection)? Or is there any clever WPF way I don't know of?
Don't "wrap" anything.
Simply create a View Model containing properties needed to send your mail message.
At some point in future, you'll actually be sending the message. For example, the user clicks a Send button that fires an ICommand somewhere. At this time, convert your ViewModel into a MailMessage.
You cannot "wrap" one collection within another without lots of code. It only takes a few minutes to copy property values from an instance of one type to an instance of another type.
If the changes always go from the ObservableCollection to the original List, i think that you could add a handler to 'CollectionChanged' event of the ObservableCollection. I think that doing it this way won't be so onerous.
AddHandler Recipients.CollectionChanged, AddressOf RecipientsCollChanged
....
Private Sub RecipientsCollChanged(sender As Object, e As NotifyCollectionChangedEventArgs)
If e.OldItems IsNot Nothing Then
For Each elem In e.OldItems
_message.To.Remove(elem)
Next
End If
End Sub
Obviously, if you want, you can also handle the modify and the adding of elements into the ObservableCollection using the informations into the NotifyCollectionChangedEventArgs parameter.

How do I resolve this apparent Catch22

I have a wpf custom control with (as is common) a shared constructor. This one looks like this;
Shared Sub New()
'This OverrideMetadata call tells the system that this element wants to provide a style that is different than its base class.
'This style is defined in Themes\Generic.xaml
DefaultStyleKeyProperty.OverrideMetadata(GetType(VtlDataNavigator), New FrameworkPropertyMetadata(GetType(VtlDataNavigator)))
ItemsSourceProperty.OverrideMetadata(GetType(VtlDataNavigator), New FrameworkPropertyMetadata(Nothing, AddressOf OnItemsSourceHasChanged))
End Sub
The control inherits ItemsControl and I want to have notification of when the itemsSource changes hence the second line in the constructor. OnItemsSourceHasChanged needs to be a shared sub in order for the line in the constructor to compile. Fine.
In the shared sub I have the following:
Private Shared Function OnItemsSourceHasChanged(d As DependencyObject, e As DependencyPropertyChangedEventArgs) As Object
RecordCount = Items.SourceCollection.Cast(Of Object)().Count()
End Function
which of course fails to compile because you cannot refer to an instance method of a class from within a shared method without creating a specific instance of the class. Even If a create a separate non shared handler for this I will still end up with the same error when trying to call it.
In essence all I want is the total RecordCount from the ItemsSource (hence my need to know when it's changed) so that I can then assign that value to RecordCount. However what I appear to have instead is the proverbial catch 22 of errors.
The answer is probably staring me in the face, but for now it escapes me. Any Ideas?

Updated title: Why ICommand.CanExecute is getting called all the time, instead of working like an event?

I am adopting MVVM pattern in WPF and have learned the use of Command. But in my implementation, the delegate I assigned to implement CanExecute is always called. I mean if I put a break point inside the delegate function, it shows that this function keeps getting called. To my understanding (and a natural way of thinking, but of course I can be wrong), this delegate only gets called when I somehow notifies the change of the state and that's when the CommandManager (re)checks the CanExecute property and modify the IsEnabled property of the UI element.
Here is my implementation of VB.NET, which I got originally from a C# version. I did notice that I needed to make some change to the ported code in order for it to compile. Could it be the underlying of C# and VB.NET is different? So can somebody provide me a original VB.NET implementation, or point me out what is wrong or do if I understand the Command behavior correctly?
Here is my VB.NET version:
Public Class CommandBase
Implements ICommand
Public Property ExecuteDelegate() As Action(Of Object)
Public Property CanExecuteDelegate() As Predicate(Of Object)
Public Sub New()
End Sub
Public Sub New(execute As Action(Of Object))
Me.New(execute, Nothing)
End Sub
Public Sub New(execute As Action(Of Object), canExecute As Predicate(Of Object))
If execute Is Nothing Then
Throw New ArgumentNullException("execute")
End If
ExecuteDelegate = execute
CanExecuteDelegate = canExecute
End Sub
Public Function CanExecute(parameter As Object) As Boolean Implements ICommand.CanExecute
Return If(CanExecuteDelegate Is Nothing, True, CanExecuteDelegate(parameter))
End Function
Public Custom Event CanExecuteChanged As EventHandler Implements ICommand.CanExecuteChanged
AddHandler(ByVal value As EventHandler)
If CanExecuteDelegate IsNot Nothing Then
AddHandler CommandManager.RequerySuggested, value
End If
End AddHandler
RemoveHandler(ByVal value As EventHandler)
If CanExecuteDelegate IsNot Nothing Then
RemoveHandler CommandManager.RequerySuggested, value
End If
End RemoveHandler
RaiseEvent(ByVal sender As Object, ByVal e As System.EventArgs)
CommandManager.InvalidateRequerySuggested()
End RaiseEvent
End Event
Public Sub Execute(parameter As Object) Implements ICommand.Execute
If ExecuteDelegate IsNot Nothing Then ExecuteDelegate.Invoke(parameter)
End Sub
Public Sub RaiseCanExecuteChanged()
CommandManager.InvalidateRequerySuggested()
End Sub
End Class
And how I instantiate an object is something like this:
MyCommand = New CommandBase(AddressOf CommandExec, AddressOf CanExecuteExec)
where the CanExecuteExec of course has the signature like this:
Private Function CanExecuteExec(obj As Object) As Boolean
Like I mentioned, the CanExecuteExec is getting called all the time. I guess it is inefficient, imagine that I have hundreds of Command objects and most of the CanExecute of them don't get changed most of the time.
UPDATE:
Somebody says the CanExecute indeed gets called all the time, while others say the opposite. I am no expert on this but I have to say the second opinion sounds more natural and makes more sense to me. Although I still need to figure out if that is true, why WPF detects the change all the time so that it keeps checking the CanExecute
In your CanExecuteDelegate you have hook to CommandManager.RequerySuggested.
So, whenever CommandManager.RequerySuggested is raised your CanExecuteDelegate will be called.
CommandManager.RequerySuggested event is raised whenever changes to the command source are detected by the command
manager which ranges from Keyboard.KeyUpEvent, Mouse.ClickEvent etc.
Also, CommandManager has a static method - InvalidateRequerySuggested which forces the CommandManager to raise the RequerySuggestedEvent. So, you can call that to validate your commands too manually.
If you want to take the control in hand for raising CanExecute, you can use the Delegate Command provided by PRISM. CanExecute delegate will get called only when you explicitly call RaiseCanExecuteChanged() method exposed by Delegate Command.
Incorporating comments to answer
Breakpoint is hitting every time on turning to VS since
CommandManager RequerySuggested event gets called on lost focus of
window and on activation property changed of window. That's why you
notice that breakpoint is hitting every now and then when you move to
VS since focus moves from WPF window to Visual Studio.
When you set up your command, there's no reliable way for the runtime to know what data your CanExecute will rely on in order to make its decision. So, when you have commands that are bound into your UI and are registered in the CommandManager, the behaviour is that the CanExecute for all commands is re-evaluated whenever the state of your application changes. The way WPF knows about this is when a bound property is updated, or when a UI event occurs.
Typically you'll see CanExecute called whenever bindings update or when certain control events occur (for example, when a textbox's text is highlighted, the CanExecute of the inbuilt Cut and Copy commands will change, and so the highlight event triggers a re-evaluation that I would imagine is bound to the MouseUp event).
May be for unknown reason the UI could be getting updated (Measure, Arrange, and then Render calls). And if you have a breakpoint set on can execute method it'll be re-occurring. In other words you can't get pass this break point, each time you would do F5, the break point will it again.
To investigate you should put the logging/output statements in your can execute method and how many times and when it is getting called.

Update property's value in datagrid based on another property's input

I have an observable collection binded to a datagrid. On a datagrid row, when I edit a property's value I need another property on that row to update it's value based on a calculation. What is the best way to go about doing this. I tried creating a collectionchanged method like below:
Public WithEvents Tickets As ObservableCollection(Of Ticket)
Public Sub Tickets_CollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs) Handles TicketCollection.CollectionChanged
CalculatedVariable = determineCalculation()
End Sub
However this it only called when a new item is added or deleted, not when a property within the row is edited. Another way I looked at is calling the calculation method within the setter of each property but that seems messy knowing that i would need to call that method and update the property on several different properties that I need it on. Is there a good easy efficient way that I can accomplish what I'm trying to do? Thannks for any feedback :)
public property MyVariable
Get
return _MyVariable
Set(value)
_MyVariable = value
CalculatedVariable = determineCalculation()
RaisePropertyChange("MyVariable") , ect..
EndProperty
The class of the elements listed by your ObservableCollection has to implement INotifyPropertyChanged. You have to raise the PropertyChanged event in your setters. Then you can subscribe to this event inside your class and make the calculation in the callback. I find it a little bit cleaner than doing the calculation in each setter, and anyways you need to implement INotifyPropertyChanged if you want the UI to be updated when you change any property of your object.

Resources