Search code examples
c#wpfmvvm

Understanding events and delegates in the context of MVVM and WPF apps


I have a code base that I inherited from someone for a WFP app. The app builds and works, but I'm trying to understand the underlying mechanism of the events and delegates at work. Bear with me here because I am fairly new to C#/.NET.

To simplify things, I will focus on one field that is present in the main window of the app. This is for the 'operator ID' field (string to enter operator name). Here is the code for that element in my XAML file.

<TextBox Text="{Binding OperatorID}" Grid.Column="1" 
                         TextChanged="OperatorIDBox_TextChanged"
                         materialDesign:TextFieldAssist.HasClearButton="True" 
                         TextWrapping="Wrap" FontSize="18"
                         Width="200" Height="45" HorizontalAlignment="Center" VerticalAlignment="Center" />

Now, in my "code-behind" xaml.cs file, I have the following (event handler?) method:

 private void OperatorIDBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        (DataContext as MyModel).OperatorID = (sender as TextBox).Text;
    }

Finally, in the "model" we have this:

public event PropertyChangedEventHandler PropertyChanged;

  

and this

private string operatorID;
        public string OperatorID
        {
            get { return operatorID; }
            set
            {
                operatorID = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }

I can't decide which way to think about this. One way is that when the text value is changed in the operatorID text box, the TextChanged property is flagged. That property in turn points to the function OperatorIDBox_TextChanged , which then goes and updates the actual property in the model with this code

            (DataContext as MyModel).OperatorID = (sender as TextBox).Text;

In the model the setter for the OperatorID property looks like this:

         set
            {
                operatorID = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }

Which I believe 'raises' the event after setting the property. But didn't we already 'raise' the event when "TextChanged" was fired in the XAML element and we executed the OperatorIDBox_TextChanged method and thus update the property in the model? In my head its a circular logic and I can't understand the actual sequence of events. Furthermore, what is the data-binding doing here? In the XAML when we say Text="{Binding OperatorID} I initially thought that this would take care of all the event handling under the hood.

Also, what is meant by (DataContext as MyModel) and (sender as TextBox)?? Why not just say MyModel.OperatorID = sender.TextBox?

In summary, it's not clear to me in the above architecture what the sequence of events are when someone changes the value of the OperatorID text box. Which peiece of the code is the event, delegate, event handler etc.. and how do they all fit together? And how does Data Context and binding fit into this? That is not clear to me at all. In previous programming environments (CVI for test automation using C-code), a lot of this was abstracted away and we simply had a "callback" function that you define which would fire if you did a certain event like clicked a button. I can conceptualize that in my head easily because its simpler e.g.

  1. Click the start button
  2. The CLICK_BUTTON event mask is passed to your callback function
  3. callback function has some logic to handle CLICK_BUTTON event.

But this whole event/delegate stuff in C# has got me confused.

I would appreciate any insights! Thank you!


Solution

  • JonasH's answer is totally correct, but I would like to offer an alternate interpretation.

    DLDR: Remove your OperatorIDBox_TextChanged method in the code behind and handle all changes through binding on your View Model

    MVVM abstracts the View layer away from any other layer such that the View knows very little about any other layer of your application. This allows Views to be much more maintainable and duplicatable as they are no dependencies between your View and any other layer of your application. I say that the View knows "very little" rather than "nothing" about other layers because at an absolute minimum, the View will need to know that there is a property named OperatorID somewhere on the View Model. The View you shared with the XAML and associated code behind violates MVVM principles because the View has a direct dependency on the View Model. The dependency is right here:

    (DataContext as MyModel).OperatorID = (sender as TextBox).Text;
    

    In this scenario, your code behind knows (has reference to) the View Model.

    Following MVVM principles, we therefore need to remove this line of code to get rid of the dependency. The question is therefore: how will we update the View if we are removing the TextChanged event?

    This is done through data binding and by raising the PropertyChanged event on your View Model, which you are already doing.

    Before we quickly review that, I want to correct your terminology as it is important when first considering MVVM. You mention the following:

    Finally, in the "model" we have this:

    public event PropertyChangedEventHandler PropertyChanged;

    Based on this implementation, this is in fact your View Model, not your Model. The model is the layer of your application that deals with all your business logic, data storage, and data IO (api's, db's, etc...). The View Model is what glues together your View and your Model by exposing properties that your View can bind to. You can think of it as "a model for your view".

    Getting into the property on your View Model, following the above logic, whenever the value of the dependency property you are bound to on the view changes, it will update the property on your view model. You can control the data flow of the binding by setting the Binding Mode. See this documentation. By default, the data flow is two way, but you can be verbose and specify that yourself in your view, if you would like - doesn't really change anything. See this for details on the default binding data flow. From the opposite side, if you want to update the value in the View, you can assign a new value to the bound property in your View Model, so long as the view model implements INotifyPropertyChanged and invokes the PropertyChanged event in the setter.

    From that, you might ask: if my property invokes PropertyChanged in its setter, won't that create a never ending loop of setting the new property?

    No, it will not. When the PropertyChanged event is raised, WPF will check if the new property is different than the value of the dependency property. If it is different, the dependency property's value will update, if it is the same, the process will end.

    For buttons, you bind the command to an ICommand property on your View Model. This is a bit outside the scope of this question though, but follows similar principles.