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:
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 Control
s 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.
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 Control
s 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.
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:Name
d 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.