WPF UserControl – Reuse with MVVM

mvvmviewwpf

I'm struggling to find an elegant and idiomatic way of coding the following scenario using the MVVM paradigm in WPF and was wondering how other people would approach it.

I have a UserControl in my WPF application which I want to reuse in a number of places. The control is a filtered ComboBox setup allowing users to refine their selections. My example is Department > Team > Person.

Filtered ComboBoxes being used in three different windows

In each scenario I might have the control configured in a slightly different manner. e.g. Window 1 might have all the departments, teams, and people; Window 2 might only display a subset of all departments; Window 3 might be locked to the user's department and team.

First (and probably worst) Solution: Give the UserControl its own ViewModel

This works as far as I can drop the control on each window and it appears to immediately require no further work. The filtering logic is the control's ViewModel as is the loading of all the lookup values. The problem comes when I then want to get the values and when I want to configure it slightly differently for each scenario, largely because I've broken the DataContext inheritance chain. I ended up having the control's ViewModel subscribe to messages for configuration settings and send messages for reporting value selections and it feels like I'm fighting MVVM/WPF rather than working with it.

Second Solution: No ViewModel for the UserControl and Rely on the Window's ViewModel This has the advantage of being easy to interact with the UserControl through the Window's ViewModel but it feels like I'm duplicating a lot of the loading of lookup values logic as well as the filtering logic.

I feel like there's an elegant solution of code-behind and MVVM but I can't seem to find it! How would you go about solving this requirement?

Best Answer

To begin with, this answer seriously lacks theoretical support (i.e. explaining why). I would very much second this answer instead. It is much under-voted because it was posted more than a bit later.

Your approach should definitely also be guided by a bit of philosophy.

MVVM (or How to separate business logic from presentation logic)

Let's see what you have come up with. You have a UserControl with DependencyProperty-ies named Departments, People, Teams, etc. How is this UserControl not exposing business logic when it's crowded with Domain-specific nouns? Programming is a lot more about names than you may think. Contrast: TextBox.Text and TextBox.Address as names for properties. The second case suddenly introduces a pre-disposition. An expectation that the value of this property serves a very specific purpose. Never underestimate the names of members and the conceptual communication weight involved.

What is a UserControl?

Based on your linked answer:

A UserControl is simply an easy way to create a Control using composition. UserControls are still Controls, and therefore should solely be concerned with matters of UI.

But, is it? Based on this explanation, a proper PersonPicker control would have been:

<PersonPicker
    FirstItemsSource="{Binding PersonPickerModel.Departments}"
    SecondItemsSource="{Binding PersonPickerModel.Teams}"
    ThirdItemsSource="{Binding PersonPickerModel.People}"
    FirstSelectedItem="{Binding PersonPickerModel.SelectedDepartment}"
    SecondSelectedItem="{Binding PersonPickerModel.SelectedTeam}"
    ThirdSelectedItem="{Binding PersonPickerModel.SelectedPerson}"
    IsFirstItemSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}"
    IsSecondItemSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}"
</PersonPicker>

Oh, by the way, the control should also have been named something like ThreeComboBoxControl or ThreeHierarchiesControl. You named it PersonPicker because that is what you want it for. While fully re-usable, the control is not a UserControl... it is a facility. If you need a facility for your very own needs, you sometimes have to concede: Do you really want a reusable UserControl, or a reusable business tool? If you decide that it is a business tool, it makes perfect sense to have a specific viewmodel for it.

View-models are over-interpreted

Binding, in WPF, does not care about class types, only about property names. As long as an object of type PersonPickerModel exists in your bound DataContext, in your example, and it carries properties with the bound names (and, of course, the property types are compatible), everything will work properly, regardless of whether your PersonPickerModel is named as such, or any other way. You can even define it as an object.

In short, what you have come up with is an intermediate between a UserControl and a business tool. Even if only for the correctness of it, I suggest you reconsider, what it is that you need between the two. If you need a business tool, keep the names, create a tailor-made viewmodel. Think about this... you have a Window or something else, and:

<PersonPicker DataContext="{Binding PersonPickerViewModel}"/>

The bindings can be placed inside the control's xaml code because you are only ever going to use it for the very specific reason of picking people. Why do you have to be so verbose in every place where you are going to reuse your control? Just pass it the ViewModel and leave the rest to that.

But there is a really clearer reason why this is better than what you are already have.

Your solution is imparting a false sense of reusability.

Designing a truly abstract "proper" UI control is hard and not to be underestimated. Look what you have come up with, three adjacent combo-boxes. What do they represent? Departments? Teams? No, it's collections. Why three? Why not four, or, even better, a variable number. A proper control pretending to cover your specific needs would have to offer a variable number of collection handling. What are these collections? Simple strings? More complicated viewmodels?

So far, we have pretty much come up with a MultiComboBox control. Think about that, a variable number of ComboBoxes, their ItemsSource bound to a different collection, as per your needs, ViewModels can be used for that. You know how to use a ComboBox and you definitely understand its conceptual representation, so you will not have a hard time juggling multiple of these. Thereafter, you can expose them in the code-behind through collections of ItemsSource and SelectedItem properties, accessible by index, maybe? You have dozens of possibilities (and loads of responsibilities, of course) because a UserControl is abstract.

Designing a UserControl means that nobody cares that you have Departments, Teams or People. Creating "non-generally-but-partially-reusable" UserControls is a bad practice but a good workaround so use that to your benefit when MVVM-ing around.

EDIT:


I'm not certain... but it feels like the selection in one ComboBox affecting the items of another could be considered a pure UI concern and something which belongs in the code-behind- depending on the selection logic, I guess.

Tomorrow, your requirements suddenly change! You don't want ComboBoxes, you want ListBoxes. Think about it, more beautiful. You select a Department in the first list, second list immediately shows you the Teams, you avoid an additional click (at the cost of some more UI space). So you decide to build another control, one with three list boxes (or ListViews for that matter, or anything else). Do you write the filtering logic again? Copy-paste it, maybe?

Whatever it is that you are referring to as "filtering logic" is emphatically not a UI concern. Most basic UserControls are that basic for a good reason: they try to avoid assumptions about what you would want to do, as much as possible. They simply try to convey the user's reactions to you, the programmer. Any additional "initiative" only robs you of flexibility.

A Button is the abstraction for "producing clicks". A ComboBox is an abstraction for making a selection from a collection. A ListBox too. A ListBox does not offer anything more than a ComboBox in terms of abstractions, it only has a different fanciness in terms of presentation. That is your keyword, there! Presentation (as in Windows Presentation Foundation).

Whenever you compose a UserControl from these basic UserControls, you are practically risking breaking this elegantly meaningless abstraction. Your UserControls should only "chain" abstractions together. In that sense, the UserControl you are trying to create offers the abstraction of making three selections at once. A filtering logic is, again, emphatically NOT a UI concern. My simple way to argue about this was that you might need to change the UI and the filtering logic would have to be re-written, instead of simply being attached. That's your other keyword there. Attached, like ViewModels attach to controls.

The only job of your controls should be to model and abstract away user interaction, and user interaction does not include filtering lists anymore than the remote control of a TV includes logic for producing two-digit values (as when you click two numbers in sequence). You press the number 1 twice in a row, within 3-4 seconds, which takes you to channel 11. Do you think this wait for a second input within 3-4 seconds is "coded" into the remote control, so that the number 11 is sent to the TV after 3 seconds? Or... the signal for number 1 actually simply travels in two occurrences, 3-4 seconds apart, with the TV deciding what to do next, instead?

Well, your UserControl is the remote control, the TV is your model. It receives two notifications about the pressing of button 1, one now, one in three seconds. The UserControl has to ask you for collections and convey potential selections from items of the collections to you, the programmer. This is, pretty much, where its responsibilities end. The rest is... philosophy ;)

Related Topic