6
\$\begingroup\$

I have a set of views and view models in my project. There is a one-to-one mapping of views and view models, so each view requires a particular view model. I'd like to instantiate a series of views given a collection of view models.

Here's what I've come up with:

public class OverlayBundle : DrawingVisual
{
    private static readonly Dictionary<Type, Func<IOverlayViewModel, DrawingVisual>> _overlayCreators =
        new Dictionary<Type, Func<IOverlayViewModel, DrawingVisual>>
        {
            { typeof(ImageOverlayViewModel), x => new ImageOverlay((ImageOverlayViewModel)x) },
            { typeof(ValuesOverlayViewModel), x => new ValuesOverlay((ValuesOverlayViewModel)x) },
            { typeof(NoGoAreasOverlayViewModel), x => new NoGoAreasOverlay((NoGoAreasOverlayViewModel)x) },
        };

    private readonly OverlayBundleViewModel _viewModel;

    private IEnumerable<Visual> _overlays;

    public OverlayBundle(OverlayBundleViewModel viewModel)
    {
        _viewModel = viewModel;
        GenerateChildren();
    }

    private void GenerateChildren()
    {
        _overlays = _viewModel.GetAllOverlayViewModels().Select(GetOverlay);
        foreach (var overlay in _overlays)
            Children.Add(overlay);
    }

    private Visual GetOverlay(IOverlayViewModel viewModel)
    {
        return _overlayCreators[viewModel.GetType()](viewModel);
    }
}

This works, but I feel like there must be a better way. Any feedback would be appreciated.

\$\endgroup\$
  • \$\begingroup\$ You might want to just look into Dependency Injection Containers. Google for ".net dependency injection framework" and you will find a lot. They are meant to create objects and once you get used to them they are awesome. The _overlayCreators is the poor mans DI Container in your code. \$\endgroup\$ – CharlesNRice Nov 9 '14 at 3:57
  • \$\begingroup\$ @CharlesNRice, thanks for the suggestion. I'm actually using Ninject for this project, but I wasn't entirely clear on how it would help for this particular situation. Since posting my question, I've been reading up on "contextual binding" to see how I can use Ninject for more situations. I think I have an approach that may work, but I have to do some refactoring to try it out. \$\endgroup\$ – devuxer Nov 9 '14 at 5:37
  • \$\begingroup\$ Catel mvvm framework has some interesting ways to do this kind of thing \$\endgroup\$ – craftworkgames Nov 9 '14 at 7:20
  • \$\begingroup\$ We have been using Caliburn.Micro and it's been great for us. \$\endgroup\$ – d347hm4n Nov 10 '14 at 9:43
2
\$\begingroup\$

A ViewModel should not be convertible to a View, it should be bound to it. The View should have a ViewModel property. You should not have child Views for child ViewModels. Instead, you should have one view that knows how to access the children of your top ViewModel (and there is only one of those).

The code seems way overcomplicated. Unless a Visual is a UI element like a page or control, it is probably a superfluous layer. MVVM where Views are POCO objects is usually a bad idea--it can't present to the screen, which is the most important job of a View. If I am understanding your code correctly, you could probably accomplish your goals by removing your Visual layer and converting your Bundle into a top-level ViewModel, directly binding it to your UI/View. Your child overlays should be child ViewModels of your existing ViewModels, of which there shold only be one per View, and the View knows how to access the children property of the parent ViewModel.

\$\endgroup\$
  • \$\begingroup\$ I essentially have a hierarchy of views and a pretty much identical hierarchy of view models, with each view bound to a corresponding view model. What makes my situation unusual is that I have a mix of XAML views and C# views (due to performance issues with WPF ItemsControls). For XAML views, I bind the DataContext to a property on the view model, but for C# views, I pass the view model into the constructor. In this particular case, I did simplify my code somewhat by eliminating the dictionary and using LINQ OfType and Concat instead, but I maintained the basic strategy. \$\endgroup\$ – devuxer Jan 30 '15 at 19:35
  • \$\begingroup\$ For more on why I'm building some views in C#, see msdn.microsoft.com/en-us/magazine/dd483292.aspx. \$\endgroup\$ – devuxer Jan 30 '15 at 19:35
  • \$\begingroup\$ Okay, I see where you're coming from with the need for a POCO view. However, I still question the need for child views. What is your reasoning for a 1:1 relation between a view and a viewmodel? It breaks the paradigm. Generally one view handles one parent viewmodel, and knows how to access/bind to its child viewmodels. So you might have an OrderViewModel with OrderItemViewModel children, but only an OrderView that binds to the collection of children. \$\endgroup\$ – moarboilerplate Jan 30 '15 at 21:15
  • \$\begingroup\$ I'm not sure what you mean by "breaks the paradigm"? Are you saying that any given page/screen in my app should be represented with a single view file? I'm not saying that I don't have single views that bind to hierarchy of view models (I'm working on an example right now with an ItemsControl, where the ItemsSource binds to a collection of child view models), but for complex views, it can be helpful to break them up into child views, particularly if some of those child views are re-used in other views. \$\endgroup\$ – devuxer Jan 30 '15 at 21:46
  • \$\begingroup\$ With the exception of dealing with multiple windows, I would say, generally yes. You might have reusable components of views, but they should not be treated as views themselves. I'd be interested in seeing an example of a child view, and what makes it a view. \$\endgroup\$ – moarboilerplate Jan 30 '15 at 21:56
1
\$\begingroup\$

We have used something similar in the past, I'm unsure if that would fit here or not.

We have the following code:

public class View : Page
{
    ...
}

public class ViewModel
{
    public CustomViewModelBase InnerViewModel { get; private set; }
}

public class CustomViewModelBase
{
    ...
}

public class CustomViewModelBase1 : CustomViewModelBase
{
    ...
}

public class CustomViewModelBase2 : CustomViewModelBase
{
    ...
}

public class CustomViewModelBase3 : CustomViewModelBase
{
    ...
}

Then in the View we select which control to show based on the type of the InnerViewModel using a DataTemplateSelector.

Everything is MVVM and I think it is simple solution for the requirement.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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