5

I have multiple of views (user controls), each with its own ViewModel. To navigate between them I am using buttons. Buttons display image and text from corresponding view model and also need column and row (because there are like 10 views: 10 columns with different number of rows each).

Right now buttons are created dynamically (I made a Navigator control for this) and for view models I have base class to hold text, image, column and row. Number of views available will be different (depends on user level and certain settings), that's why it's I need control here.

Question: how shall my control get data from view models?

Right now I have interface INavigator, defined in (lol) control itself. And view models implement it. I could go opposite, let my control to know about view models. Both looks wrong.


There is a single Navigator control what has, lets say, Items bound to a list of view models. It can cast each view model to INavigator or ViewModelBase (common for all pages) to obtain specific view model image, text, column and row. So either view model knows about control (to implement INavigator) or control knows about ViewModelBase.. And this is a problem, both solution bind tight control and view models, which is bad in mvvm.


Schematically enter image description here

Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • Ok if I got that straight you have some sort of container with many UserControls in it. My question now is what kind of interaction you need with those UserControl? – Tzah Mama Jul 21 '14 at 10:25
  • @TzahMama, how is that related to the problem? They will be complicated user controls, full of different controls (to example, graphs, lists, buttons, etc) bound to properties of corresponding view models. My problem is this (trying to say it as simple, as I can): list of view models and getting data from *each* view model individually. Of course I can make a list of images, so that I don't have to cast anything. But that's even more unprofessional, instead of having single list of entities create bunch of lists for every important property of entity. Now it's 4, what if I will need 40? – Sinatr Jul 21 '14 at 10:31
  • That's the thing, I'm having trouble understanding your problem. I was going to suggest [`Custom Routed Events`](http://msdn.microsoft.com/en-us/library/ms752288(v=vs.110).aspx) for an elegant way of detecting _something_ that happened in a UserControl. But I'm getting the idea that you just want arbitrary information from your ViewModels. If this is the case I don't see any way other then just iterating over them extracting the data you want. Yes it's not elegant but I don't see any other way (interface might be helpful) – Tzah Mama Jul 21 '14 at 10:41
  • @TzahMama, Then you might also want *to know* what to do in such case =D Point is - coupling control and ViewModel together is bad. Feel free to vote up question, perhaps some mvvm-sharks will smell it. – Sinatr Jul 21 '14 at 11:08
  • This sounds complicated for me, but let me try to interpret it: you have a bunch of `Views` (undefined number) and you have equal amount of `VM`'s. Now to navigate between them you have basic `Properties` with an `Image` and `Text`. What kind of `Control` you are using to display this situation i.e. What you are using to display the views? – XAMlMAX Jul 21 '14 at 12:11
  • @XAMlMAX, yep, see edit. I created custom control (as I said in post), right now it's a bunch of buttons. To display views I am using `ContentControl`. Predicting further questions, `Navigator` control has nothing to do with views. It's top level, it doesn't know how user controls looks like or what it does. It's purpose is to navigate between views (view models). – Sinatr Jul 21 '14 at 12:47
  • Now you are struggling with `Buttons` displaying the correct data from `VM`'s or your Control displaying the Data in the actual `View`? – XAMlMAX Jul 21 '14 at 14:12
  • @XAMlMAX, first. *How shall my control* (bunch of buttons) *get data from view models*. – Sinatr Jul 21 '14 at 14:23
  • If you could have these buttons replaced with `ComboBox` you basically create an `ItemTemplate` which in turn would be bound to every element of your `VM` collection or even better a `TabControl`. Is that something you can do? I'm asking because it would make your life a lot easier and I mean a LOT! – XAMlMAX Jul 21 '14 at 14:29
  • @XAMlMAX, assigning text in item template would be indeed easier. But I don't see an easy way to create such dynamic layout. Point is, button in column 2, row 3 can become invisible during run-time (because of some setting) and then all buttons in column 2 has to be moved up. It's not `StackPanel` with wrapping, but something much more complicated layout-wise. Also, given sketch is a rough simplification of how `Nagivator` will looks like at the end. But I get your point, perhaps `Navigator` should be a list with `ItemTemplate` to resolve column/row without casting. I will check this. – Sinatr Jul 21 '14 at 15:22
  • This question makes me think of [an answer](http://stackoverflow.com/a/13292195/302677) I wrote a while back which I think is related. `UserControls` are just nice user-friendly ways of drawing your Models and ViewModels. They should either be extremely generic, using DependencyProperties to get the data they need to display, or they should be built with a specific Model or VM in mind, and Implicit DataTemplates can be used. The second is far more common for me. I was going to expand this into an answer, but [toadflakz's answer](http://stackoverflow.com/a/24956822/302677) is close enough :) – Rachel Jul 25 '14 at 13:40

4 Answers4

3

The way you've drawn your diagram answers your own question as to how you should structure the code for this.

What you need is one VM (let's call it MainVM) which contains an ObservableCollection<VMBase> of the other VMs (using your base type so that they can all happily live in the same collection).

Your View needs an ItemsControl (bound to your ObservableCollection<VMBase>) where you specify a DataTemplate for the Button using the properties exposed by the VMBase type only. Set the Command property in the Button to call SwitchCommand, CommandParameter is set to the item itself (i.e. {Binding .}).

Your View also needs a ContentControl bound to a SelectedVM property on MainVM which you can populate.

Implement SwitchCommand to set the SelectedVM property based on the value from the CommandParameter.

public void ExecuteSwitchCommand(object parameter)
{
   var vmBase = parameter as VMBase;
   if (vmBase != null)
      SelectedVM = vmBase;
}

All properties mentioned here should be INotifyPropertyChanged enabled so that the View registers when they change and updates the UI.

To get the different UIs for the ContentControl, add type-specific DataTemplates for each of your specific VM types to the Resources file of your View (or if you're smart and are building a custom plug-in framework, merge the Resource Dictionaries).

A lot of people forget with MVVM that the whole point is that there is a purposeful separation of View from ViewModel, thus meaning you can potentially have many Views for a single ViewModel, which is what this demonstrates.

toadflakz
  • 7,764
  • 1
  • 27
  • 40
2

I find it's easiest to think of MVVM as a top-down approach... View knows about it's ViewModel, ViewModel knows about its Model, but Model does not know about its ViewModel and ViewModel does not know about its View.

I also find a View-first approach to development the easiest to work with, as UI development in XAML is static (has to be).

I think a lot of people get to wrapped up in 'making every component (M, V, VM) standalone and replaceable', myself included, but I've slowly come to the conclusion that is just counter-productive.

Technically, sure you could get very complicated and using IoC containers, create some ViewLocator object which binds a View-type to a ViewModel-type, but... what exactly does that gain you besides more confusion? It makes it honestly harder (because I've done this at one point) to develop because now you've lost design-time support first and foremost, among other things; and you're still either binding to a specific view model interface in your view or creating the binding at run-time. Why complicate it?

This article is a good read, and the first Note: explicitly talks about View vs. ViewModel. Hopefully, it will help you draw your own conclusions.

To directly answer your question, I think having your ViewModels implement an INavigator interface of some sort is probably ideal. Remember your VM is 'glue' between your view and model/business logic, its job is to transform business data into data that is consumable by your views, so it exists somewhere between both your UI and business layers.

This is why there are things like Messengers and View Services, which is where your navigator service on the ViewModels can fit in nicely.

InteXX
  • 6,135
  • 6
  • 43
  • 80
Greg B
  • 416
  • 3
  • 12
  • Yes, I also have feeling what is't not the worst idea to use `INavigator`. If it declared aside (not in the control and not in the view model), then it feels like this: view model implement something what **can** be used by view and control, which is using it, can be **replaced** with any other control, who knows `INavigator` *format* and is able to deal with it. And it doesn't sounds like tight coupling anymore. Still, I was sure there are better ways to deal with it. Thanks for the article, it's helpful. – Sinatr Jul 23 '14 at 15:03
1

I think the design has led to a no way out situation.

I believe that creating a custom button control where the dependency properties tie the image, the row and column actually provide a way for the page, which it resides on ,to get that information to them; whether they are dynamically created or not.

Continuing on with that thought. There is no MVVM logic applied to a custom control, the control contains what it needs to do its job and that is through the dependency properties as mentioned. Any functionality of the button should be done by commanding; all this makes the button data driven and robust enough to use in a MVVM methodology or not.

Question: how shall my control get data from view models?

There should only one viewmodel which is the page the control resides on. The control is simply bound to information which ultimately resides on that VM. How it gets there, that is up to the programmer. If the button is going to contain state data, that is bound from its dependency property in a two way fashion back to the item it is bound to.

By keeping VMs out of the buttons and only having one VM that is the best way to segregate and maintain the data. Unless I am really missing something here....

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • So, you suggest to provide information for the control in that view model, where it is used? Ok, question is how. I already mentioned a list of images. Shall I supply navigation control with special information in a way it understand? That doesn't remove the problem. You can't use simple types, `List` or `List` is easy, but what about so much data as: list of images, list of text, list of columns and list of rows for **each** viewmodel (so control don't have to access view models directly).... instead of list of viewmodels, which somehow supply control with data about themselves – Sinatr Jul 23 '14 at 14:44
  • 1
    @Sinatr Before I give a quick answer...do you have an example of the data as just mentioned? For it sounds like creating a specialized object which holds all three (such as a DTO entity for lack of a better term) of the data (image/row/column), then an observable collection of those items bound to a *super* control which has a dependency prop of the list and manages dynamically all buttons bound to each individual instance of aforementioned class. I believe a data example may help clear up, at least my confusion and provide a truer response. – ΩmegaMan Jul 23 '14 at 16:34
  • I see, you mean `ObservableCollection` is better than `ObservableCollection` (where `ViewModelBase` implements `INavigator`). This is proven by the fact, what view models, inherited from `ViewModelBase` don't care about `INaviagor` (none of them care about row or column, and while text and, possible, image *could* be useful, they can be normal properties, which are simply copied into `INavigator`, when `ObservableCollection` is created. The *bad* thing is what changing collection will required recreating the one with `INavigator`... any thoughts? – Sinatr Jul 24 '14 at 07:00
  • @Sinatr `ObservableCollection` with actual instance objects which contains the ViewModel base along with the image/row/column as properties could be expressed as other interface(s) as needed is one route yes. As to the cost involved create a test project would express the ideas as basic example. Leverage that to see how it would work, but also how the design would affect your current project. As to MVVM, I frankly don't see an "rule breaking" situation; for to me, as mentioned MVVM is simply a way to separate data from views; to which you are doing quite well. – ΩmegaMan Jul 24 '14 at 08:39
1

Same as others here I find it a bit hard to actually understand what you are asking, so this is quite general. The answer to the question header is simply: the Control gets the data from the ViewModel through bindings, always. You set the DataContext of your Control to the corresponding ViewModel, and from there you keep the ViewModel and the Control synchronized:

If you add an ItemsControl containing buttons to the View, you add an ObservableCollection<ButtonViewModel> to the ViewModel and bind the ItemsSource of the ItemsControl to this.

If you allow the user to dynamically add content to the View, the actual code that does it resides in the ViewModel, e.g. when the user clicks on a button "Add Button", you use the Command property to call a ViewModel method that adds a ButtonViewModel to the collection and the View will automatically reflect your changes.

There do exist complicated cases that are impossible to code exclusively in the ViewModel, I have found Behaviors to be the missing link there, but I'll get into that when you show me the specific case.

If you'd like to get a working example, please provide as much code as you can, with your exact expectations of what it should do.

Mike Fuchs
  • 12,081
  • 6
  • 58
  • 71
  • `ItemsControl` is useful, perhaps even `Selector`. Right now buttons are generated in code. Using `ItemsControl` would make it more wpf-alike control (I don't have access to Blend). And perhaps it will even solve my initial problem with `ItemTemplate` template bindings (mentioned by XAMIMAX). – Sinatr Jul 25 '14 at 11:20