1

I am working on a wpf dll which contains a number of views with accompanying view models to satisfy MVVM.

In my project I have a class which acts as my "view manager" which handles binding each view to their correct view model.

namespace ControlsAndResources
{
    public class View
    {
        private static readonly ViewModelLocator s_viewModelLocator = new ViewModelLocator();

        public static readonly DependencyProperty ViewModelProperty = DependencyProperty.RegisterAttached("ViewModel", typeof(string), 
            typeof(ViewModelLocator), new PropertyMetadata(new PropertyChangedCallback(OnChanged)));

        public static void SetViewModel(UserControl view, string value) => view.SetValue(ViewModelProperty, value);

        public static string GetViewModel(UserControl view) => (string)view.GetValue(ViewModelProperty);

        private static void OnChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UserControl view = (UserControl)d;
            string viewModel = e.NewValue as string;
            switch (viewModel)
            {
                case "TestViewModel":
                    view.DataContext = s_viewModelLocator.TestViewModel;
                    break;
                case "FooViewModel":
                    view.DataContext = s_viewModelLocator.FooViewModel;
                    break;
                default:
                    view.DataContext = null;
                    break;
            }
        }
    }
}

Then I do the binding on each of my xaml declarations (here is one example)

<UserControl x:Class="Foo.Bar.TestView"
             ...
             ...
             xmlns:controls="clr-namespace:Foo.Bar.ControlsAndResources"
             controls:View.ViewModel="TestViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
  <Grid>
    ...
    ...
  </Grid>
</UserControl>

And this works perfectly. But now what I would like to do is, in my MainView.xaml, include a ContentControl or an ItemControl and use Binding to update my views from my View class. How do I go about doing that?

user2529011
  • 705
  • 3
  • 11
  • 21
  • 1
    You should have a main viewmodel with a child viewmodel property `public object SelectedChild { /* INPC stuff */ }`. The main viewmodel should be the DataContext of your main view. Bind SelectedChild to the Content property of a ContentControl in the main view. Write an [implicit datatemplate](https://learn.microsoft.com/en-us/dotnet/framework/wpf/data/data-templating-overview#the-datatype-property) for each viewmodel type. The implicit datatemplate just creates the appropriate view. Your viewmodel locator class isn't necessary. – 15ee8f99-57ff-4f92-890c-b56153 Oct 24 '19 at 16:52
  • Ahh, you seem to be loading views from some external source. In that case, instead of writing implicit datatemplates, you could write a [DataTemplateSelector](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.datatemplateselector?view=netframework-4.8) which creates a new DataTemplate containing only an instance of the desired view. You would use that with the ContentControl above. You want to reinvent as little as possible of the wheel described in my first comment. – 15ee8f99-57ff-4f92-890c-b56153 Oct 24 '19 at 16:52
  • @EdPlunkett okay I will try this approach. thank you. – user2529011 Oct 24 '19 at 17:01
  • Creating a DataTemplate programmatically is [a slightly weird business](https://stackoverflow.com/a/8430451/424129). Let me know if you run into trouble there, I can get that working no problem. – 15ee8f99-57ff-4f92-890c-b56153 Oct 24 '19 at 17:13
  • @EdPlunkett I would love to glimpse at it if its not too much trouble. – user2529011 Oct 24 '19 at 17:15
  • ah, down voted once again. what a shocker. Never mind that I articulated my question and have been responsive and attentive with each comment and potential answers. – user2529011 Oct 24 '19 at 18:13
  • I'm not a fan of the framework but caliburn micro might be useful for this. You can use default or iverride it's conventions which decide what view is associated with a viewmodel https://caliburnmicro.com/documentation/conventions – Andy Oct 24 '19 at 18:17
  • 1
    I don't see any sense in that downvote either. There's no sense in complaining, though. You're supposed to be stoic about downvotes here. Anyway there is about zero chance that anybody who didn't leave a comment at the time is going to leave one now. – 15ee8f99-57ff-4f92-890c-b56153 Oct 24 '19 at 18:17
  • 1
    Downvoting seems a bit harsh. Maybe the context could have been explained a bit clearer. Shrug. Have an upvote on me. – Andy Oct 24 '19 at 19:50

1 Answers1

1

Here's what I would do: I would write a MainViewModel that implements INotifyPropertyChanged. I'd give it a SelectedChild property. I'm going to make some assumptions about the INotifyPropertyChanged implementation you've got. Let me know if those assumptions are off base, and we can get it working with what you've got.

MainViewModel.cs

private Object _selectedChild;
public Object SelectedChild
{
    get => _selectedChild;
    set => SetProperty(ref _selectedChild, value);
}

MainViewModel will be the DataContext of our main view, probably MainWindow.

MainWindow.xaml.cs

public MainWindow()
{
    InitializeComponent();

    //  More about this guy later
    DynamicDataTemplateCreator creator = this.FindResource("DynamicDataTemplateCreator") 
        as DynamicDataTemplateCreator;

    //  This gibberish is a stand in for whatever information the template creator 
    //  may need to figure out what type of view belongs to what type of viewmodel. 
    creator.ViewLookupInformation = 
        "My expatriate aunt Sally ate nine autumnal polar bears in Zanzibar.";

    DataContext = new MainViewModel();
}

In MainWindow's XAML, we'll put a ContentControl bound to SelectedChild, and we'll create an instance of a template selector (see below) that will be used to create the template that displays SelectedChild:

MainWindow.xaml

<Window.Resources>
    <local:DynamicDataTemplateCreator x:Key="DynamicDataTemplateCreator" />
</Window.Resources>
<Grid>
    <ContentControl 
        Content="{Binding SelectedChild}" 
        ContentTemplateSelector="{StaticResource DynamicDataTemplateCreator}" 
        />

DynamicDataTemplateCreator.cs

And here's how we create DataTemplates to display viewmodels with views whose type is determined at runtime.

public class DynamicDataTemplateCreator : DataTemplateSelector
{
    //  If mainwindow or the main viewmodel has information that we need about the 
    //  dynamically loaded views, pass that information via this property. It can be any 
    //  type you want, preferably the exact type of the information you are passing.
    public object ViewLookupInformation { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        //  item is the viewmodel.
        Type viewType = null;

        //  A quickie UserControl I wrote for testing. 
        //viewType = typeof(VMView);

        /* 
         * logic here to determine the type of view we want. Assign that Type to viewType
         *  If you need extra information from the main program, smuggle it in to here via 
         *  ViewLookupInformation
         */

        return new DataTemplate
        {
            VisualTree = new FrameworkElementFactory(viewType)
        };
    }
}
  • 1
    Thank you so much. What I like is that I can say goodbye to the ViewModelLocator. I just didnt see any sense to it. – user2529011 Oct 24 '19 at 18:11