user2653422
user2653422

Reputation: 1105

C# MVVM How to dynamically create Views

I'm trying to create a memory game while strictly following the MVVM pattern to learn how to use it. Now I have a problem with creating views at run time.

I've created the following project structure:

The dependencies are as follows: StartApplication project -> View project -> ViewModel project -> Model project

After clicking a button on the MainWindowView the ICommand function for that button within the MainWindowViewModel will load a MemoryCardModel30 instance from the Model project. For each Card within the MemoryCardModel30 instance a CardViewModel will be created.

Now to the problems I face: How to create the CardView instances, how to link their DataContexts to the CardViewModels and how to arrange/assign the CardViews on the MainWindowView? The ViewModel project can't create Views as it has no dependency to the View project (would create a circular dependency and breaks the pattern). How to solve this issue while following the MVVM pattern?

P.S.: The CardViews need to be positioned exactly by x and y pos. which will require some complicated calculations which should go tho the corresponding CardViewModel. So some basic layouts like grid will not be sufficient I think.

Upvotes: 2

Views: 3832

Answers (2)

Display them in an ItemsControl. I'm assuming that MainWindowViewModel.Cards is ObservableCollection<CardViewModel>.

<ItemsControl
    ItemsSource="{Binding Cards}"
    >
    <!-- 
    This creates UI for each item. There are other ways, if you've got a collection 
    of heterogeneous item types.
    -->  
    <ItemsControl.ItemTemplate>
        <DataTemplate DataType="local:CardViewModel">
            <views:CardView />
        </DataTemplate>
    </ItemsControl.ItemTemplate>

    <!-- 
    Make it use a Canvas to be the actual container for the items, so we can control 
    their position arbitrarily, instead of the default StackPanel that just stacks 
    them up vertically.
    -->
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <Canvas />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>

    <!--
    The ItemsControl will put the instantiated item templates in ContentPresenters 
    that it creates. The positioning attributes have to go on the ContentPresenters, 
    because those are the direct children of the Canvas. The ContentPresenters are 
    the "item containers". You can customize them via the ItemContainerStyle property 
    of the ItemsControl. 
    -->
    <ItemsControl.ItemContainerStyle>
        <Style TargetType="ContentPresenter">
            <!-- 
            The datacontext will be CardViewModel.

            Bind Canvas.Left and Canvas.Top to appropriate properties 
            of CardViewModel. I'll assume it's got Point Position { get; }

            A much better, more "pure MVVM" way to do this is for the items to 
            provide some kind of abstraction, maybe row/column or something else,  
            and either place them in a Grid or UniformGrid or some other kind of 
            dynamic layout control, or else convert that abstraction into Canvas
            coordinates with a value converter on the Binding. 

            Then you can display the same item objects in different ways at the same 
            time without locking them into one layout. 

            Don't drive yourself crazy striving for ideological purity at the expense 
            of getting code out the door, but do consider redesigning that part. 
            -->

            <Setter Property="Canvas.Left" Value="{Binding Position.X}" />
            <Setter Property="Canvas.Top" Value="{Binding Position.Y}" />
        </Style>
    </ItemsControl.ItemContainerStyle>

This is the canonical Way to Do It in WPF/MVVM. Use DataTemplates to create view instances of the appropriate type. The viewmodel is in charge of what objects should be presented to the user; the views are responsible for how they're shown. You don't need or want any MVVM framework for this. The built-in DataTemplate features of WPF are enormously powerful. Don't trust anybody who thinks you need anything else for a project within two orders of magnitude of this size.

Upvotes: 5

Bradley Uffner
Bradley Uffner

Reputation: 16991

I think I misunderstood your question. I originally thought you were asking how to display a new window for specific view models. While this answer won't specifically apply to you, I'll leave it up, as it is tangentially related. It may help others confused about what to search for.


I have a ViewManager class that links view types to viewmodel types. One of the methods on it is ShowViewFor that handles this task, it takes a viewmodel instance and:

  • Looks up the view for that viewmodel type.
  • Creates an instance of that view.
  • Sets the DataContext of that view instance to the viewmodel that was passed in.
  • Shows the view.

It also does a bunch of other tasks like tracking open views, displaying message boxes and dialogs, etc.

The ViewManager is available though an IOC container via an interface, so it can be mocked up for unit tests.

I'm sure there are many existing frameworks out there that do this, but like you, I wanted to learn MVVM from "the roots up".

Upvotes: 0

Related Questions