2

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:

  • Model project
  • -MemoryCardModel30
  • -Card
  • ViewModel project
  • -MainWindowViewModel
  • -CardViewModel
  • View project
  • -CardView
  • StartApplication project
  • -MainWindowView

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.

4
  • Are you using a framework, and is your approach "model first" or "view first"? Also, you may be able to get a WrapPanel to do what you want... msdn.microsoft.com/en-us/library/… Commented Mar 27, 2017 at 18:01
  • This kind of thing is why we use MVPVM at my job: linking models to views and then showing those views isn't a job that fits nicely into MVVM, but is a job for a Presenter Commented Mar 27, 2017 at 18:06
  • @BerinLoritsch No I'm not using any framework at the moment. I wanted to learn the pattern from the beginning. I'm only familiar to 'model first' in the context of EntityFramework but not for MVVM. However, in this case I started with the ViewModel then the model and last the view. Commented Mar 27, 2017 at 18:30
  • 1
    Normally this kind of goal is achieved by exposing a collection of card view models on your main view model, binding an ItemsControl (or some derivative) at the view side, and using a DataTemplate (set through ItemsControl.ItemTemplate) to define the appearance of a single card. To arrange items in a non-trivial manner you can set ItemsControl.ItemsPanel to a Panel of your liking - be it WrapPanel, Grid, Canvas or any other Panel (in really complex scenarios it's often easier to write your own panel rather than try to make the stock ones to do your bidding). Commented Mar 27, 2017 at 18:54

2 Answers 2

5

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.

Sign up to request clarification or add additional context in comments.

2 Comments

I would not put the coordinates to VM layer, but an abstraction of them, which will be resolved in view layer through a converter.
@Rekshino That's the correct (or "correct" at the very least) way to do it, and I'll update my answer appropriately.
0

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".

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.