1

Consider a ViewModel that exposes a tree defined in the Model, which is then data-bound to a TreeView. The tree is rather large and the model is used directly because it is essentially read-only with regards to the view. Now, the TreeView lives under a TabControl in the VisualTree, so an issue at this point is that the IsExpanded and IsSelected properties aren't preserved when switching between tabs. One hesitates to add these boolean properties to each node in the Model, as this should be extended in the ViewModel as a matter of principle. The tree is composed of polymorphic nodes, so if we were to create a ViewModel node type that derives from the tree node types and adds these properties, it seems this would result in some hairy code in the ViewModel:

That is, if the tree has an abstract NodeBase, and then derived types Node1, Node1, ... NodeN (the Model's Nodes). The ViewModel then has to encapsulate these nodes, so when creating a ViewModelNode, if it has a reference to Node and also references to child ViewModelNode's for each descendent ViewModelNode that encapsulates each descendent Model's Node all the way down the tree, maintaining these child references in the ViewModel identically to how they are maintained in the Model, along with a reference to the Model. i.e. all references in the Model nodes are replicated in the ViewModel nodes, in order for each Model node to be encapsulated by a ViewModel node. The existence of redundant references such as this, even if handled in the ViewModelNode's constructor, just smells bad.

What is the most accepted means to extend each node in a tree in this scenario, without wholesale replication of the references as stated above? (And to a lesser point, is the mere mention of using the model directly by the view an unforgivable crime, or is this forgiven due to the circumstances?)

Josher
  • 23
  • 7
  • *"if we were to create a ViewModel node type that derives from the tree node types and adds these properties, it seems this would result in some hairy code in the MVVM ... wholesale replication of the tree"* -- you lost me there. Please clarify, in a separate paragraph. – 15ee8f99-57ff-4f92-890c-b56153 Dec 01 '17 at 14:30
  • 1
    There's no inherent harm in exposing the model to the view, if it actually makes things *simpler*. It usually won't make things simpler. When you start writing weird workarounds because of it, it's time to refactor. – 15ee8f99-57ff-4f92-890c-b56153 Dec 01 '17 at 14:35
  • 1
    You could maybe choose another way and just make the `TreeView` persist its state (don't unload on `TabPage` change). For example, see [here](https://stackoverflow.com/questions/9794151/stop-tabcontrol-from-recreating-its-children). – dymanoid Dec 01 '17 at 15:02
  • Thanks @dymanoid for the link - that is working excellently as a workaround. I had become so accustomed to the TreeView rebuilding on every tab change that I had actually become blind to this as a cause of my woes. @Ed is right that eventually I may need to extend the `Model` as a `ViewModel` in other ways and at that point I may have to refactor. I'm just not sure what I'd aim for in a refactor, so I've revised the question with what I mean by "hairy" per @Ed's request. Thanks to both for the responses. – Josher Dec 04 '17 at 16:36

1 Answers1

0

There is perhaps an argument to be made that implementing those Boolean properties on the Model is OK, but personally I would look to creating ViewModels for each Model that's going to be in the TreeView. One advantage of doing so would perhaps be an increase in scalability, should you ever decide to implement more functionality related to the TreeView.

I think it depends on how much you are actually doing with the TreeView (within your app), but I do think the more you're doing, the stronger the argument for a ViewModel-based solution.

With regards to the hairy code, you could perhaps circumvent this to a degree by using an interface to describe your TreeView members, e.g.:

public interface IMyTreeViewItem
{
    bool TreeViewItemIsSelected { get; set; }
    bool TreeViewItemIsExpanded { get; set; }

    // Further potential properties

    string TreeViewItemHeaderText { get; set; }
    List<IMyTreeViewItem> TreeViewItemChildren { get; set; }
}

This approach can be used to ensure that your TreeView members are properly "subscribed". There's also then an option to reference the interface type in XAML, for example, as the TargetType of a HierarchicalDataTemplate for the TreeView.

Chris Mack
  • 5,148
  • 2
  • 12
  • 29
  • So here's where I meant hairy: The `ViewModel` would implement the interface, and not the `Model`. The `Model` is a tree of varying node types, and my concern is how to avoid having a `ViewModel` that has excessive code with no value added other than to "glue" the `View` to the complex `Model`. – Josher Dec 04 '17 at 15:43
  • If the Model itself is already your tree, and it is intended to be read-only, I would probably implement the entire thing as a property on some ViewModel, and bind to that. – Chris Mack Dec 04 '17 at 15:48
  • This works until the moment I need to add properties to each node to support the TreeViewItems. – Josher Dec 04 '17 at 16:50
  • To me that's the argument for having these structures as ViewModels. It would depend on what kind of properties they are, but if they seem to be "app-based", and wouldn't really exist outside of the app for any reason, they belong on a ViewModel. In my view the following article gives a good explanation of what a Model should be: https://rachel53461.wordpress.com/2011/05/08/simplemvvmexample/. – Chris Mack Dec 04 '17 at 16:59