简体   繁体   中英

How to bundle View, ViewModel and DataTemplate in a WPF application for easy reuse?

Situation:

I'd like to create a flexible application which is ViewModel driven.

The basic flow is like this:

  1. Design the main ViewModel
  2. Create a UserControl as View and a DataTemplate for the main ViewModel to select this View
  3. If there are sub components, the are modelled by sub ViewModels
  4. Create a UserControl as View and a DataTemplate for the sub ViewModel to select this View

If a sub view model needs to be presented, it is done via a DataTemplate.

This approach can also be seen here (option 8).

So the main window xaml looks something like this:

<Window>
    <!-- somehow I need to add the mapping from ViewModel to View -->
    <Grid>
        <!-- the main ViewModel -->
        <ContentPresenter Content="{Binding Path=Content}"/>
    </Grid>
</Window>

The Content property might contain a view model that contains a list of elements named Children and it's associated DataTemplate might look like this: The children are also flexibly rendered by a suitable DataTemplate.

<UserControl>
    <Grid>
        <StackPanel>
            <!-- display the child ViewModels  in a list -->
            <ItemsControl ItemsSource="{Binding Path=Children}" />
         </StackPanel>
    </Grid>
</UserControl>

Question:

  1. How should I organize the ViewModels, Views and their DataTemplates so I don't need to hardwire them in the MainWindow?

  2. How do I then connect this to the main window?

  3. It would be nice if it is stub-able, ie I can see the result during design time with a design time dataContext.

Basically I want to bundle the View, ViewModel and DataTemplate and be able to use them in an application that doesn't need to know about the details (eg some sub ViewModel implements a certain interface and is injected into the main ViewModel).

Have you looked into Prism .

The framework allows you to define regions within your UI that views can be registered against. I believe this answers your 2nd question (2).

xmlns:cal="http://www.codeplex.com/prism"

<Window>
   <!-- somehow I need to add the mapping from ViewModel to View -->
   <Grid>
      <!-- the main ViewModel -->
      <ContentPresenter cal:RegionManager.RegionName="MainRegion"/>
   </Grid>
</Window>

For your first question (1) we structure our entities in the following way:

View - we have an abstract base class that looks similar too:

public abstract class ViewBase<T> : UserControl, IView<T> where T: IViewModel
{
    public T ViewModel
    {
        get
        {
            return this.viewModel;
        }
        protected set
        {
            this.viewModel = value;

            this.DataContext = this.viewModel;
        }
    }

    public ViewBase(IUnityContainer container)
    {
        this.ViewModel = container.Resolve<T>();
    }
}

This then allows us to create Views in xaml using the following:

<ui:ViewBase x:Class="MyView"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:ui="NAMESPACE FOR VIEWBASE"
         xmlns:vm="NAMESPACE FOR VIEWMODEL"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         x:TypeArguments="vm:MYVIEWMODEL">

In the code behind of a View we do the following:

public partial class MyView : ViewBase<IMyViewModel>

This then makes use of the constructor in the base class to resolve the ViewModel and set it to it's DataContext.

This then allows you to design your view (3) as you intended and also removes the need for having a DataTemplate.

Using the UnityContainer we then register the views as follows:

this.container.RegisterType<IMyView, MyView>();
this.container.RegisterType<IMyViewModel, MyViewModel>();

this.regionManager.RegisterViewWithRegion("MainRegion", typeof(IMyView));

Note that "MainRegion" here matches the RegionName specified in the MainWindow xaml. You can expand this further to use a TabControl if you wanted to display multiple views in the same area, or even break your MainWindow down into different regions.

I hope this helps.

1) You can in each view add DataTemplates in UserControl.Resources, ie

<UserControl.Resources>
    <DataTemplate DataType="{x:Type viewmodels:Customer1ViewModel}">
       <views:Customer1View/>
    </DataTemplate>
    <DataTemplate DataType="{x:Type viewmodels:Customer2ViewModel}">
       <views:Customer2View/>
    </DataTemplate>
</UserControl.Resources>

Now you defined for each ViewModel appropriate View. You put only data templates for ViewModels that you expect in that View, ie children's ViewModels

2) Hm, your MainWindow also has to have a ViewModel, ie put in MainWindow DataContext an instance of MainWindows's ViewModel. That ViewModel has to contain the property Content (in which you put ViewModel for content). You can do that manually in App.xaml.cs

public partial class App : Application
{

    public App()
    {
        this.Startup += App_Startup;
    }

    public void App_Startup(object sender, StartupEventArgs e)
    {

        this.MainWindow = new MainWindow();

        //create view model and set data context
        MainWindowViewModel vm = new MainWindowViewModel();
        this.MainWindow.DataContext = vm;

        //show window
        this.MainWindow.ShowDialog(vm);
    }
}

3) I'm not sure about this, you probably will not be able to see results in design time.

I'm not sure if I'm fully understanding what exactly you want, if this doesn't help, please replay to this answer with further explanation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM