9

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?

1
  • Can you rewrite the lookup and filtering logic (or some part of it) in an abstract way against one or more of IEnumerable<YourType> (or IQueriable<YourType)? If this approach doesn't introduce more problems than it solves, than you could reuse (most) of that logic and still have the ViewModel/DataContext be pluggable. For parts that you can't reuse, design a suitable interface (or overridable methods) to serve as extension points. Apr 16, 2020 at 19:32

5 Answers 5

7

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 ;)

11
  • 1
    1/3 Thanks very much for your detailed answer - it's given me a lot to think about. I would certainly concede I have not produced a general purpose UserControl and if I were intending to do so then naming it, as you suggested, (ThreeComboFilter, TriumverateFilter, etc.) would have made more sense. I also accept, following this logic, three is an arbitrary number of filters and that supporting 2..n filters would be more generally useful. Apr 18, 2020 at 16:49
  • 1
    2/3 I don't know if I'm arguing semantics here but does talking about a UserControl necessarily imply that level of generality? Perhaps the link I cited does indicate that. However, I had the requirement for reusable "person picker" control and not (as yet, at least) a 2..n filter control. The solution I created is reusable within the scope of my project. I grant it might not be terribly useful to anyone else but then I don't need it to be and to expend the effort of making it more general would have meant undertaking additional effort to create something for which there is no current need. Apr 18, 2020 at 16:50
  • 1
    3/3 My other thought is with accepting a ViewModel for this control (whether the theoretical general purpose one or my own) I feel like I'm putting UI logic in a ViewModel. 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. Again, thanks for taking the time to leave such a comprehensive and thoughtful answer. Apr 18, 2020 at 16:50
  • These are interesting comments and concerns. I will try to briefly comment on those within the answer. Apr 18, 2020 at 20:05
  • @CptCoathanger I have updated the answer based on your thoughts. I tried to be as pragmatic as possible, but I am afraid I have strayed into philosophy dangerously much. Let me know if what I have written makes any sense! Apr 19, 2020 at 8:16
4

The third solution you are looking for might be DependencyProperty

https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/dependency-properties-overview

You can use these to do all sorts of cleverness, although how elegant they are is another question.

I think the correct solution, unless you are making a super generic control, however is to have a view model. I'm not sure why its causing you problems. Its not a static shared viewmodel, you can have an instance per window and bind events accordingly

public class Window1VM
{
    public UserControlVM  ucvm {get;set;}
}
3
  • Thanks for your comment. I did end up using dependency properties in my approach I took. I'm about to add it as an answer, I'd be interested in your thoughts on it, if you have any. Apr 18, 2020 at 9:27
  • pff its been a while since i used em. my impression is that they are pretty vital to make "proper" controls. ie the kind of thing you might buy off the shelf. But the implementation has so much boilerplate and is so 'ugly' that it feels like some sort of secret underbelly
    – Ewan
    Apr 18, 2020 at 9:43
  • 1
    We definitely have the same opinion of dependency properties: they're fundamental to WPF but as soon as you use a few of them you generate a lot of boiler plate and they're not immediately intuitive! Apr 18, 2020 at 10:09
3

Unless the logic is extremely intricate, it may just be easier to reimplement the functionality three times for each screen, rather than try and find a common design that integrates all three.

The danger in future is that if you have a fourth screen which requires this control but has its own special requirements, you may have to rework the logic of the common control yet again, but now you've got 4 screens to (re-)test instead of one, and you have to rework the logic in a way that is compatible with all 4 screens instead of simply specialised to each one.

I'm not sure what the name is for this pattern of behaviour, but although it seems like the code is reusable or more manageable for maintenance, that repetition is eliminated, or that it contains fewer conceptual elements, in fact what it produces is a highly complex and over-specialised element which is difficult to devise, difficult to reason about, and can rarely be reused for any later purpose without yet further adjustment.

1
  • Thanks for your comment. I definitely shared your concern about ending up with a overly-complex control to handle two or three simple scenarios - and the ongoing maintenance cost for additional functionality. I'm about to add the approach I took as an answer, if you've any thoughts on that. Apr 18, 2020 at 9:26
1

Your first and worst idea is actually the best and the most MVVM-like approach.

With a proper MVVM framework like caliburn micro you just point out the sub viewmodel on the parent model and the correct view will be rendered for that view model.

You need to make sure that viewmodel and view does not get messy though to support all cases. It can be better to have smaller modules like in this case 3 modules for each aggregate. And then combine these as you like in a more composition like way.

1
  • Thanks you for you comment. I did take a somewhat similar approach to this but using a POCO model, as opposed to a ViewModel. I'm about to add an answer with the approach I took and would appreciate any thoughts you have. Apr 18, 2020 at 9:28
1

The approach I took was largely informed by this answer to a SO question: https://stackoverflow.com/a/28815689/2571982. While user1228 is... passionate... I found their argument convincing.

The thrust of the SO answer was that a UserControl such as I'm creating should be thought of as no different to any other framework control (DatePicker, ComboBox, ListBox, etc.), i.e. interaction with it should be through dependency properties; the UI logic should be contained in the code-behind file of the control. It was also recommended that the creation of a UserControl should be about rendering out a model, in my case a simple POCO.

<PersonPicker
    Departments="{Binding PersonPickerModel.Departments}"
    Teams="{Binding PersonPickerModel.Teams}"
    People="{Binding PersonPickerModel.People}"
    SelectedDepartment="{Binding PersonPickerModel.SelectedDepartment}"
    SelectedTeam="{Binding PersonPickerModel.SelectedTeam}"
    SelectedPerson="{Binding PersonPickerModel.SelectedPerson}"
    IsDepartmentSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}"
    IsTeamSelectionEnabled="{Binding PersonPickerModel.IsDepartmentSelectionEnabled}">
</PersonPicker>
public class PersonPickerModel
{
   public IEnumerable<Department> Departments { get; }
   public IEnumerable<Team> Teams { get; }
   ...
   public bool IsTeamSelectionEnabled { get; }
}
public class Window1ViewModel
{
   public PersonPickerModel PersonPickerModel { get; set; }

   public WindowViewModel(IDepartmentsQuery departmentsQuery)
   {
      PersonPickerViewModel = new PersonPickerViewModel
      {
         Departments = departmentsQuery.Execute(),
         ...
         IsTeamSelectionEnabled = true;
      }
   }
}

I create a PersonPickerModel in each of ViewModel and populate it accordingly. The filtering logic lives in the code-behind of the control.

This approach allows me to drop the control on any page and I get the filtering logic without additional work, I just need to construct a model to bind for configuring the particular instance of the control.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.