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/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube