views:

24

answers:

2

I want to create a WPF element that, at runtime, is in full control of its child elements -- adding and removing child UI when its properties change. Something a bit like what ItemsControl does when you modify its ItemsSource property, though in my case there'll only be one child.

This will be a view container for MVVM -- when you give it a Model or a ViewModel, it will magically create the correct View and wire everything up. There's no need for my view container to be templatable (since it creates user-defined views, which are UserControls and have their own templates), and I'd prefer that it encapsulate as much as possible. I could probably do this easily by descending from something like Grid, and adding child controls when my own properties change; but Grid publicly exposes its collection of child elements and lets anyone add and remove stuff.

Which WPF class should I descend from for maximum encapsulation, and how do I add child elements to it at runtime?

Based on my understanding of the docs, I tried using FrameworkElement and AddVisualChild, just to see if I could create child controls at runtime. I'm not clear on whether the AddLogicalChild is necessary, but I put it in just in case:

public class ViewContainer : FrameworkElement {
    private TextBlock _child;

    public ViewContainer() {
        _child = new TextBlock { Text = "ViewContainer" };
        AddLogicalChild(_child);
        AddVisualChild(_child);
        InvalidateMeasure();
    }

    public object Content { get; set; }

    protected override Size ArrangeOverride(Size finalSize) {
        _child.Arrange(new Rect(finalSize));
        return finalSize;
    }
    protected override Size MeasureOverride(Size availableSize) {
        _child.Measure(availableSize);
        return _child.DesiredSize;
    }
}

When I put a ViewContainer into a Window, and run this, I expect to see a TextBlock saying "ViewContainer". But instead, I just see a blank window. So obviously I'm missing something.

How can I fix the above code so that the "child" control does appear at runtime, but isn't exposed for others to mess with (any more than can be avoided)?

A: 

Have you thought about taking advantage of WPF's support for implicit DataTemplates?

The way I have handled a requirement similar to yours is by using a ContentControl. I bind the Content property to my ViewModel. I then make sure that in Resource Dictionaries referenced somewhere in the tree above the ContentControl I have DataTemplates defined for all the types of ViewModels that might be assigned to the Content Property.

This way WPF takes care of wiring up the correct view for my ViewModel.

Samuel Jack
Yes, I've done that before, but this time I want to go a step farther -- I want my ViewContainer to be able to take a Model, and automatically discover and create the right ViewModel for it, and go from there to the View. I don't think DataTemplate.DataType will get me there without help.
Joe White
A: 

To answer your specific question, you'll also need to override GetVisualChild and VisualChildrenCount properties to enable your child element to be displayed.

Samuel Jack
Cool -- that did it. Though I'm a bit puzzled as to why there's an AddVisualChild if it doesn't actually add it *to* anything.
Joe White
Ah, I see. It sets up the child to know about its parent; but many parents won't need a list of children (having none or only one), so it leaves the choice -- and responsibility -- of storage up to the parent. Aggressive separation of concerns: one of the things I quite like about WPF. But it means that the name "AddVisualChild" is misleading, since it suggests that it's adding it to some kind of list, and it's not.
Joe White