WPF: Where does MVVM stop and code-behind begin?
Asked Answered
P

5

8

I have created a Window which has a ListView to display a collection of persons. There are also 3 TextBoxes that are supposed to display the person's first and last name, and an age. Finally, there's a Button to save the new person data entered in those TextBoxes.

Loading persons into the ListView is done by implementing MVVM. Works like a charm! Adding new people to the collection by clicking the Button is also done through MVVM.

But there are two use cases that I am not sure whether it is wiser to use commands, i.e. MVVM, or just plain code-behind. The use cases are:

  1. When user selects a person from the ListView, the TextBoxes should show the person details.
  2. When user types types characters instead of digits in the TextBox that displays the person's age, she or he should be warned that the entered data is incorrect.

The reason why I am in doubt whether I should use MVVM or code-behind is because both use cases are related to view only (GUI), i.e. there is no interactivity with the model or application business logic. The ListView item source is bound to a collection of persons ObservableColleciton<Person> and all data related to the selected person is already passed to the view when the ListView is populated with items. In the second use case, again, there is no need to go to ViewModel in order to let it fire the message box about the wrong user input. How about creating a validation callback in the age dependency property of the ViewModel class instead?

Province answered 6/4, 2011 at 20:30 Comment(0)
D
3

The main motivation behind MVVM is separation of concerns, i.e. separate logic from the presentation. What you are describing (search and validation) looks more "logic" to me, so I would put it in the ViewModel (assuming it cannot be performed with databinding of course).

  • Keep in mind that the view is difficult to test, so if there's a chance that the logic you are implementing has significant errors that would be a reason to put it in the viewModel.

  • An alternative (semi-serious but usually effective) method to decide if something belongs to the model or to the viewModel is asking yourself what would happen if you give the view (the Window, UserControl or whatever) to your graphic designer (even if you don't have one, pretend you do). If you are ok with the idea that he could put his c#-incompetent[*] hands on the code behind (and make a mess out of it) it's generally a sign that the code is strictly presentation-related and can safely live in the view. Most times you'll end up moving it to the ViewModel.

[*] just saying for educational purposes, many designers are more c# competent than me :-)

Deandeana answered 6/4, 2011 at 20:45 Comment(0)
D
7

The only time I start dropping code into the code-behind files is when I cannot put it in the ViewModel or deeper in the object graph.

For example, you're first situation is, as mentioned in the answer by Chris Wenham, completely solvable in XAML code. There is no need to resort to the code-behind to achieve this effect. And this example can be expanded to interactions with a collection that isn't necessarily presented in a control like a listbox. You can write XAML code the responds to changes in the current item in a collection in the ViewModel via binding.

Your second situation can also achieved through dvalidation facilities within the ViewModel and databinding to those facilities with your XAML. IDataErrorInfo is a great mechanism that is built in for just this purpose. Here is a nice little article demonstrating a simple use of IDataErrorInfo.

Examples of when you have to drop into code-behind are hopefully few and far between. One example I've encountered is when a control does not support ICommand and you can not bind functionality to a behavioral element, an example control being a ListBox. But there are some techniques to get around this limitation, as demonstrated in this great SO question. Also, in the event that you need to override rendering in custom controls, you'll need to do this in code-behind or in an inherited class.

Disputation answered 6/4, 2011 at 21:45 Comment(2)
Nice answer,just one more question from my side. When the view has to have other modal windows say by clicking a button I will get a Messagebox. Do I need to create such windows in code-behind?Doordie
That's a tricky one. A "pure" messagebox (Yes/No/Cancel) for example would still be done in code-behind but I find there are much better ways to create a user experience. Now, a modal window that is doing more than just yes/no/cancel functionality should generally have a viewmodel and accompanying view for presentation. Here is a great SO question/answer on this very topic. #455368Disputation
A
6

The textboxes can and should definitely be populated through bindings in XAML when the ListView selection changes, eg:

<ListView Name="people" .../>

<TextBox Text="{Binding ElementName=people, Path=SelectedItem.FirstName}"/>

Or to reduce coding, put the text boxes in their own panel with a DataContext set, eg:

<Grid DataContext="{Binding ElementName=people, Path=SelectedItem}">
    <TextBox Text="{Binding Path=FirstName}"/>
    <TextBox Text="{Binding Path=LastName}"/>
</Grid>

Validation can be hooked-up in XAML, but the code that does the validating is typically implemented in a class. There's a convenient abstract class in System.Windows.Controls called ValidationRule that can be used to quickly create a validator. See this blog post for an example.

Acatalectic answered 6/4, 2011 at 20:42 Comment(0)
D
3

The main motivation behind MVVM is separation of concerns, i.e. separate logic from the presentation. What you are describing (search and validation) looks more "logic" to me, so I would put it in the ViewModel (assuming it cannot be performed with databinding of course).

  • Keep in mind that the view is difficult to test, so if there's a chance that the logic you are implementing has significant errors that would be a reason to put it in the viewModel.

  • An alternative (semi-serious but usually effective) method to decide if something belongs to the model or to the viewModel is asking yourself what would happen if you give the view (the Window, UserControl or whatever) to your graphic designer (even if you don't have one, pretend you do). If you are ok with the idea that he could put his c#-incompetent[*] hands on the code behind (and make a mess out of it) it's generally a sign that the code is strictly presentation-related and can safely live in the view. Most times you'll end up moving it to the ViewModel.

[*] just saying for educational purposes, many designers are more c# competent than me :-)

Deandeana answered 6/4, 2011 at 20:45 Comment(0)
F
2

You can use a validation rule for your binding. It won't show a message box (although it would probably be possible), but you can define an ErrorTemplate that shows the error.

Floeter answered 6/4, 2011 at 20:43 Comment(0)
G
-2

I love the way the question is posed because WPF architectural style really is a spectrum with cleanly separated and purely declarative binding-based UI on one end and totally comingled code-behind on the other. While the question is old - and the other answers are by and large excellent - it's worth a "modern" take because in the last few years there's been a glut of poor advice on this particular topic circulating on this site.

To answer this question one has to first understand the reason and motivation for MVVM. It's not just a bunch of arbitrary design rules, nor is it a religion. There's no grand council that arbitrates right from wrong. There's no reason to follow any rule unless it benefits you and/or your project.

By and large the goals and motivations of those who invented and practice MVVM are:

  • Clean separation of UI implementation from UI logic, which benefits large teams especially but even helps solo coders tackle complex projects more effectively
  • Limiting connection points between the UI logic (view model) and implementation (view) layers to declarative bindings, which improves maintainability, readability, and reusability
  • Maximizing code reusability, thereby future proofing your application (i.e., portability to other platforms, either simultaneously or in the future, or reuse of UI components in other applications or even in the same application but applied to different functionality)

That these were the original goals is not an opinion, but a fact beyond dispute. (See the articles I cite in In MVVM is it acceptable to access the ViewModel in the view's code behind?). Of course, if someone doesn't share those goals, and cares instead solely about aesthetics or design philosophy, then they should just code their application in whatever manner makes them happy. But assuming someone does share the aforementioned practical goals, understands the basics of the pattern, and finds it a good fit for their application, then the answers to architectural questions become self-evident:

  1. UI logic that has no meaning or applicability outside the context of the specific application on the specific platform, and is impossible to express in an abstract way without reliance on platform specifics, should go in the View layer. Whether to use UserControl code-behind or custom Controls is really a matter of taste, but reusability - be it in other applications or even the same application in different views - tends to favor the latter approach.

  2. For UI logic that is abstract and could run on any conceivable platform, but which is difficult or cumbersome to implement in an MVVM-friendly way out of the box (which is frankly where the hard questions usually arise), there are various options. You have things like the MVVM Community Toolkit which add behaviors and attached commands. Or you can often subclass Controls to add ICommand properties that get invoked by private event handlers or overrides.

    What one should not do, however, is simply use UserControl code-behind in these cases, if it can be avoided, becuase while adding extra scaffolding to bind the UI logic to the UI implementation in a declarative way may be a hassle, the UI logic is nonetheless abstract and thus belongs in the view model, while the scaffolding, if done right, should have applicability to other views and even other WPF projects. Code-behind, by contrast, has no reusability outside the specific platform, application, or view. So the little bit of extra work you might do on the view side to extend a control or behavior to make it more MVVM-friendly will pay additional dividends in the long term besides just philosophical purity.

  3. Finally, if the UI logic is abstract and there is a clear and obvious MVVM-friendly binding-only solution, then the answer is simple. It belongs in the view model for all the reasons discussed.

It is never - in my view - a good argument to say "this code doesn't belong in the view model because it relates to UI". It's called view model for a reason - it's a model of the view. Of course it relates to the UI. But granted, words alone are imprecise, and fewer words are more imprecise than "relates", so here's a more objective standard that encapsulates all of the points above:

  • Can the logic be expressed in pure .NET C#, and coded and understood by someone with no specific knowledge of the GUI platform, without any platform-specific dependencies? and
  • Does the GUI platform provide a simple means of binding to or otherwise consuming such logic, or can it reasonably be extended to do so?

If the answer to both questions is yes, then the logic belongs in the view model. If you follow that guide, while some folks here may still try to excommunicate you from the club (as they have tried to do to me), you'll nonetheless be doing exactly what MVVM's conceivers meant for you to be doing.

To give a concrete example of what I mean, consider a dynamic label for a button and the question of where this logic should go. This easily passes the above test for what belongs in the view model, but it also might "feel" like an exclusively GUI-concern. But so what? By choosing view model, you have: (1) a single source of truth for the label content, on any platform you may target now or in the future; and (2) a clean XAML-only view. If you choose code-behind, you'll get a lot of ugly code-behind and thus a bigger development bill, and maybe a pat on the back from the purists. Which would you prefer?

With all that said, let's turn to the specifics of the question.

You've got what's called a Master-Detail interface. The master is the list of people; the detail view shows, well, the details; and you have two use cases for which you're unsure whether a pure MVVM approach is appropriate:

When user selects a person from the ListView, the TextBoxes should show the person details.

Ok - to go back to the standard above: (1) can this logic be expressed in pure .NET C#, and coded and understood by someone with no specific knowledge of the GUI platform? The answer is clearly yes:

public class PersonViewModel : ViewModelBase
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    // etc.
}

public class MasterViewModel : ViewModelBase
{
    public ObservableCollection<PersonViewModel> People { get; set; }

    private PersonViewModel _SelectedPerson;
    public PersonViewModel SelectedPerson
    {
        get
        {
            return _SelectedPerson;
        }
        set
        {
            if (_SelectedPerson == value)
                return;
            _SelectedPerson = value;
            OnPropertyChanged();
        }
    }
}

That's the entirety of the logic needed for this problem. In fact there's no non-trivial imperative logic here at all. There's simply the concept of a person; there's a list of all available people; and there's a property representing the currently selected person. You need know nothing about WPF or GUIs to write and maintain this code. For all practical purposes it's a schematic.

Next, (2) can the GUI platform easily bind to the view model? This is also an obvious yes:

    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*" />
            <ColumnDefinition Width="*" />
        </Grid.ColumnDefinitions>
        <ListView Grid.Column="0"
                  ItemsSource="{Binding People}" 
                  SelectedItem="{Binding SelectedPerson, Mode=TwoWay}"/>
        <local:PersonView Grid.Column="1" 
                          DataContext="{Binding SelectedPerson}"/>
    </Grid>

Can you get any more elegant? I don't think so. You've got a totally platform-independent, abstract view model. You've got a simple, clean XAML file that declares the implementation and connection points. You're basically done. Go have a beer.

And what's the alternative? I'm not going to show it, but if you think about it just for a minute you'll realize that it's ugly to say the least. Event handlers. x:Named members. Casting DataContext to get a view model. It's a bunch of ugly, comingled, single-purpose code. And there's no upside. Even if you think the concept of a "selected person" is too "GUI-ish", so what? What possible benefit do you derive by abstaining from the manifestly more elegant solution?

When user types characters instead of digits in the TextBox that displays person's age, she or he should be warned that the entered data is incorrect.

Data validation brings the complexity of the problem up a half a notch, but it doesn't change the fundamentals. Go back to the objective standard - (1) can this logic be expressed in pure .NET C#, and coded and understood by someone with no specific knowledge of the GUI platform? Clearly yes. The logic is, "if the age entered is not a valid number, display a warning." There is nothing about this logic that's unique to WPF, and it's easy for any competent programmer to understand without knowledge of GUIs.

What about the second part - ease of binding the GUI to the abstract logic? It turns out WPF has rudimentary built-in binding validation with visual feedback that you can easily turn on if you use ValidatesOnDataErrors=True in the binding. If the built-in validation that you get for free is good enough for you, there's no reason to reinvent the wheel. Remember - practical benefits.

If you want something more elaborate, INotifyDataErrorInfo - which is not a WPF-dependent type - can be used, which WPF also has built-in support for if your view model implements this interface. Thus part (2) of the standard is also easily satisfied. (I have a detailed answer showing how to use that interface in your view model in an MVVM-friendly manner and without WPF dependencies, so I won't repeat it all here). However, here's a more quick and dirty approach, purely for demonstration purposes, that also easily binds to the GUI:

In PersonViewModel:

    #region string Age property
    private int? _Age;
    public string Age
    {
        get
        {
            return _Age?.ToString() ?? string.Empty;
        }
        set
        {
            if (int.TryParse(value, out var age))
            {
                _Age = age;
                this.AgeError = null;
            }
            else
            {
                _Age = null;
                this.AgeError = $"Please enter a valid value for Age.";
            }
            OnPropertyChanged();
        }
    }
    #endregion

    #region string AgeError property
    private string _AgeError;
    public string AgeError
    {
        get
        {
            return _AgeError;
        }
        set
        {
            if (_AgeError == value)
                return;
            _AgeError = value;
            OnPropertyChanged();
        }
    }
    #endregion

and in the view:

    <TextBox Text="{Binding Age}" />
    <TextBlock Text="{Binding AgeError}"
               Visibility="{StaticResource HideEmptyStringsConverter}" />

Again a real-life application would use INotifyDataErrorInfo, but the principle is the same. The single source of truth for the validation logic, and in particular the message(s) you want to display, should always be the view model (and if you really want to be professional about it, the strings should live in a platform-independent JSON file localiz(s)ed for each region). There's no upside - zero - to stuffing all this in the platform dependent layer of your app. This logic is universal, and will be timeless. In 100 years when we are using Minority Report-style holographic UIs, the logic in the view model will still hold. As for WPF, I wouldn't hold my breath...

Conclusion

When it comes to architecting an MVVM app, asking if code is "related" to the GUI is the wrong question; it's too subjective, and it doesn't help you maximize MVVM's benefits. Focus instead on the practical. What's the cost/benefit to putting the code in one place over the other? How likely would it ever be used on other platforms? Does the logic depend on any UI platform specifics? How much tedious scaffolding would be needed to rig up an "MVVM-friendly" implementation, and does such scaffolding have uses outside this particular application or even this particular view?

Practical - not philosophical - concerns are how you determine where MVVM stops and code-behind begins.

Gamesmanship answered 26/7 at 17:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.