I have used ValueConverters
in some cases and put the logic in the ViewModel
in others. My feeling is that a ValueConverter
becomes part of the View
layer, so if the logic is really part of the View
then put it there, otherwise put it in the ViewModel
.
Personally I don't see a problem with a ViewModel
dealing with View
-specific concepts like Brush
es because in my applications a ViewModel
only exists as a testable and bindable surface for the View
. However, some people put a lot of business logic in the ViewModel
(I do not) and in that case the ViewModel
is more like a part of their business layer, so in that case I wouldn't want WPF-specific stuff in there.
I prefer a different separation:
View
- WPF stuff, sometimes untestable (like XAML and code-behind) but also ValueConverter
s
ViewModel
- testable and bindable class that is also WPF-specific
EditModel
- part of the business layer that represents my model during manipulation
EntityModel
- part of the business layer that represents my model as persisted
Repository
- responsible for persistence of the EntityModel
to the database
So, the way I do it, I have little use for ValueConverter
s
The way I got away from some of your "Con's" is to make my ViewModel
's very generic. For instance, one ViewModel
I have, called ChangeValueViewModel
implements a Label property and a Value property. On the View
there's a Label
that binds to the Label property and a TextBox
that binds to the Value property.
I then have a ChangeValueView
which is a DataTemplate
keyed off of the ChangeValueViewModel
type. Whenever WPF sees that ViewModel
it applies that View
. The constructor of my ChangeValueViewModel
takes the interaction logic it needs to refresh its state from the EditModel
(usually just passing in a Func<string>
) and the action it needs to take when the user edits the Value (just an Action
that executes some logic in the EditModel
).
The parent ViewModel
(for the screen) takes an EditModel
in its constructor and just instantiates the appropriate elementary ViewModel
s such as ChangeValueViewModel
. Since the parent ViewModel
is injecting the action to take when the user makes any change, it can intercept all of these actions and take other actions. Therefore, the injected edit action for a ChangeValueViewModel
might look like:
(string newValue) =>
{
editModel.SomeField = newValue;
foreach(var childViewModel in this.childViewModels)
{
childViewModel.RefreshStateFromEditModel();
}
}
Obviously the foreach
loop can be refactored elsewhere, but what this does is take the action, apply it to the model, then (assuming the model has updated its state in some unknown way), tells all the child ViewModel
s to go and get their state from the model again. If the state has changed, they are responsible for executing their PropertyChanged
events, as necessary.
That handles the interaction between, say, a list box and a details panel quite nicely. When the user selects a new choice, it updates the EditModel
with the choice, and the EditModel
changes the values of the properties exposed for the detail panel. The ViewModel
children that are responsible for displaying the detail panel information automatically get notified that they need to check for new values, and if they've changed, they fire their PropertyChanged
events.
Navigation should always be handled in the ViewModel.
You're on the right track with thinking that the perfect implementation of the MVVM design pattern would mean you could run your application entirely without Views, and you can't do that if your Views control your Navigation.
I usually have an ApplicationViewModel
, or ShellViewModel
, which handles the overall state of my application. This includes the CurrentPage
(which is a ViewModel) and the code for handling ChangePageEvents
. (It's often also used for other application-wide objects such as the CurrentUser, or ErrorMessages too)
So if any ViewModel, anywhere, broadcasts a ChangePageEvent(new SomePageViewModel)
, the ShellViewModel
will pickup that message and switch the CurrentPage
to whatever page was specified in the message.
I actually wrote a blog post about Navigation with MVVM if you're interested
Best Answer
If the View-Model is only concerned with a control being visible or collapsed, then I'd make it a boolean and use a value converter. If the View-Model also needs to communicate the hidden state, then I'd make it an enum (and probably use the Visibility enum).
So to answer your question, I'd have the View-Model express what it needs (visible/collapsed or visible/collapsed/hidden) and adapt the View to fit with the value converter. Other cases may vary though, as with most things.