Practical .NET
Moving Data to the Form in Windows Forms
When you implement the Model-View-ViewModel pattern you need to tell your View (in this case, a Windows Form) when the ViewModel has new data. Here's how to do that, along with a warning about how to avoid a potential bug.
In another column, I looked at the issue of how to structure a Windows Forms application using the Model-View-ViewModel (MVVM) pattern so that I could do Test-Driven Development (TDD). In that column, I showed how using the ObservableCollection class simplified moving list data out of the ViewModel and into the form's controls. What I didn't address (and will take care of in this column) is how to get data from a single-valued property (properties declared as string or integer or an object type) out of the ViewModel and into the form.
Using the ViewModel
In my MVVM design, events fired by controls in the form move data into properties in my ViewModel or call methods in my ViewModel. When the form calls methods in my ViewModel, those methods update properties in the ViewModel. But I then need to notify the form that the ViewModel has new data so the form can move that data out of the ViewModel and into the controls on the form. The simplest way to do that is to have the ViewModel fire an event when its properties change, and have the form catch and process those events.
The first step in implementing this plan is to tweak the code that instantiates my ViewModel. I declare the variable that holds the ViewModel using the WithEvents keyword, and add a method to handle an event raised by my ViewModel:
Private WithEvents VM As InvoicePremimumVM
Private Sub New()
MyBase.New
VM = New InvoicePremimumVM
AddHandler VM.PropertyChanged, AddressOf VMPropertyChanged
Now, I need to have my ViewModel raise this PropertyChanged event whenever the ViewModel has data about which the form should know. The .NET Framework has a specific event designed for just this purpose, and if you add the INotifyPropertyChanged interface to your class, Visual Studio will add the event to your class for you. That's what I did with my ViewModel class (I wrote the first two lines of code, and Visual Studio wrote the last one):
Public Class InvoicePremimumVM
Implements INotifyPropertyChanged
Public Event PropertyChanged(sender As Object,
e As System.ComponentModel.PropertyChangedEventArgs) Implements
System.ComponentModel.INotifyPropertyChanged.PropertyChanged
My next step is to raise the PropertyChanged event when something changes in my ViewModel. When raising the PropertyChanged event, I have to pass the event a reference to my ViewModel and a PropertyChangedEventArgs object holding the name of the property that was changed. Because I'll be raising this event from multiple properties in my ViewModel class, I add a method to my ViewModel that handles creating that object and raising the event:
Private Sub RaisePropertyChanged(PropertyName As String)
Dim ev As PropertyChangedEventArgs
ev = New PropertyChangedEventArgs(PropertyName)
RaiseEvent PropertyChanged(Me, ev)
End Sub
Now, in my ViewModel, whenever I update a property, I raise this event. A typical property in my ViewModel looks like this:
Private _AuthCode As String
Friend Property AuthCode As String
Get
Return _AuthCode
End Get
Set(value As String)
_AuthCode = value
RaisePropertyChanged("AuthCode")
End Set
End Property
In my form's VMPropertyChanged method, I check to see which property has been changed and do whatever I think best with the property. In this case, that's updating a textbox on the form:
Private Sub VMPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
Select Case e.PropertyName
Case "AuthCode"
Me.txtSInvoice.Text = VM.AuthCode
End Select
End Sub
Silly Processing
and a Trap
The logic for updating the textbox works like this: When code in my ViewModel updates the AuthCode property, the property raises the PropertyChanged event; the form catches that PropertyChanged event and moves data from the AuthCode property into the txtSInvoice textbox on the form. This leads to some silly processing.
Assume, for example, that I update the ViewModel AuthCode property in the LostFocus event of the textbox:
Private Sub txtSInvoice_LostFocus(
) Handles txtSInvoice.LostFocus
VM.AuthCode = txtSInvoice.Text
End Sub
Now, imagine what happens when the user changes the textbox contents and tabs out of the textbox. The code in the LostFocus event updates the ViewModel AuthCode property; the ViewModel then raises the PropertyChanged event; the PropertyChanged event causes the form to read the AuthCode property from the ViewModel; the form then updates the textbox
with the value the textbox already has. This is silly: I've made a roundtrip from my textbox to my ViewModel and back to the form to update the textbox with whatever the user just put in the textbox.
However, I'm not worried enough about this to change anything. This silly processing is just moving stuff around in memory, and as a result, the form's response time isn't appreciably affected. If I wanted to avoid this problem, I'd have to add more code to ensure the PropertyChanged event is only raised when the property is updated from within the ViewModel. I'm not sure how I'd do that. It would make either my form or my ViewModel (or both!) more complicated, and I'd have to do it (and do it correctly) for every property in my ViewModel. I don't like doing any of those things. The current processing is silly, but I can live with it because I like it better than the alternative.
I do have to be careful, though: I can't use an event on the textbox that will fire when the textbox is updated because I could end up with an endless loop. I could, for example, use the textbox TextChanged property to update my ViewModel property. Then, when the user typed in the textbox, the textbox would raise the TextChanged property; that would update the ViewModel property, which would raise the PropertyChanged event; that event would cause the form to update the textbox, which would cause the textbox to raise its TextChanged event; raising the TextChanged event would start the whole cycle over again
and again
and again.
I won't explain how I know this. Just live with the silly processing and pick the event on the control for updating your ViewModel carefully.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.