Problem with view-model-first approach

Topics: Actions & Coroutines, Conventions
Jun 26, 2013 at 10:04 PM
Hi all. I have this structure implemented in my app:
public class ShellViewModel : Conductor<object> {
    // displaying a DefinitionsViewModel 
    // no problem, every thing is OK
    public ShellViewModel() {
        ActivateItem(new DefinitionsViewModel());
    }
}
public class DefinitionViewModelItem : PropertyChangedBase {

    private readonly string _title;
    private readonly object _content;
    
    public DefinitionViewModelItem(string title, object content) {
        _title = title;
        _content = content;
    }
    
    public string Title {
        get { return _title; }
    }

    public object Content {
        get { return _content; }
    }
}
public class DefinitionsViewModel : Screen {
    private readonly IObservableCollection<DefinitionViewModelItem> _items;
    public DefinitionsViewModel() {
        _items = new BindableCollection<DefinitionViewModelItem>();
        _items.AddRange(
                new[] {
                          new DefinitionViewModelItem(
                              title: "tab header 1",
                              content: new DefinitionPage1ViewModel()
                              ),
                          new DefinitionViewModelItem(
                              title: "tab header 2",
                              content: new DefinitionPage2ViewModel()
                              )
                      });
    }

    public IObservableCollection<DefinitionViewModelItem> Items {
        get { return _items; }
    }
}
public class DefinitionPage1ViewModel : PropertyChangedBase {
    
    public DefinitionPage1ViewModel() {
        CreateVisibility = false;
        // _createVisibility is set to false by default,
        // but after some logical calculation, it comes true:
        CreateVisibility = true;
    }
    
    private bool _createVisibility;
    public bool CreateVisibility {
        get { return _createVisibility; }
        set {
            if (_createVisibility == value)
                return;
            _createVisibility = value;
            NotifyOfPropertyChange(() => CreateVisibility);
        }
    }

    public void Create() {
       // some logic here...
    }
    
    public bool CanCreate() {
        return _createVisibility;
    }
}
public class DefinitionPage2ViewModel :PropertyChangedBase { }
// DefinitionsView.xaml:
<UserControl x:Class="MyProject.Views.DefinitionsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:ViewModels="clr-namespace:MyProject.ViewModels;assembly=MyProject.ViewModels"
             xmlns:Views="clr-namespace:MyProject.Views">
    <TabControl ItemsSource="{Binding Items}">
        <TabControl.Resources>
            <DataTemplate DataType="{x:Type ViewModels:DefinitionPage1ViewModel}">
                <Views:DefinitionPage1View />
            </DataTemplate>
            <DataTemplate DataType="{x:Type ViewModels:DefinitionPage2ViewModel}">
                <Views:DefinitionPage2View />
            </DataTemplate>
        </TabControl.Resources>
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Title}"/>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ContentControl Content="{Binding Content}"/>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</UserControl>
// DefinitionPage1ViewModel.xaml:
<StackPanel Orientation="Horizontal" 
    Visibility="{Binding CreateVisibility, Converter={StaticResource BooleanToVisibility}}">
    <Button cal:Message.Attach="Create" Content="Create"/>
</StackPanel>
The first problem is: the view-models located in DefinitionsViewModel.Items as DefinitionViewModelItem.Content cannot be shown in view. I mean the content control does not render them; instead just prints their type name as content. However, I solved this by adding DataTemplate resource in DefinitionsView.xaml as below:
        <TabControl.Resources>
            <DataTemplate DataType="{x:Type ViewModels:DefinitionPage1ViewModel}">
                <Views:DefinitionPage1View />
            </DataTemplate>
            <DataTemplate DataType="{x:Type ViewModels:DefinitionPage2ViewModel}">
                <Views:DefinitionPage2View />
            </DataTemplate>
        </TabControl.Resources>
But the second problem is: CanCreate returns false at load time. But after a little changes in values, it returns true. But it seems the view cannot be notified about changing CanCreate return value. My questions are:
  1. How can I solve the first problem? It seems when I put view-model in a wrapper and then passing a list of wrapper to the view, the framework cannot detect the views. Is this right? and if yep, how can I solve this, and if nop, according my code, where is my mistake?
  2. How can I notify view about changing a CanExecute value? I mean Create method in example, and changing the value of CanCreate?
Jun 26, 2013 at 10:31 PM
For the second issue, look at https://caliburnmicro.codeplex.com/discussions/442668
For the first one, you need to set the Model property on the ContentControl used by the TabControl ContentTemplate.
Jun 26, 2013 at 11:15 PM
Edited Jun 27, 2013 at 12:05 AM
+BladeWise Thanks to answer. And for others that has the same problem, for the first issue, change the DefinitionsView.xaml as below:
<UserControl x:Class="MyProject.Views.DefinitionsView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:cal="http://www.caliburnproject.org">
    <TabControl ItemsSource="{Binding Items}">
        <TabControl.ItemTemplate>
            <DataTemplate>
                <TextBlock Text="{Binding Title}"/>
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <DataTemplate>
                <ContentControl cal:View.Model="{Binding Content}"/>
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</UserControl>
Thanks again.
Jun 27, 2013 at 9:51 AM
Exactly what I meant! :)
Jan 21, 2014 at 2:41 PM
Wow. That just seems very wrong. The View has knowledge of the ViewModel. This seems to break the whole reasoning for MVVM.
Jan 22, 2014 at 2:35 PM
@MBonafe: No, it isn't. The view needs to be aware of some concepts of the view-model, to be able to interact with it, whatever flavour of MVVM you decide to use. The View.Model property is indeed part of a view-model-first approach, since the content of the view is dynamically generated from a view-model.

The difference between view-first and view-model-fist, is about which element is created first. In view-first you generate views, and then derive (or generate) the view-model to be attached. In view-model-first, view-models are generated, and views are derived with some mechanism. In either case, the view needs to know which properties or actions are available. Without this knowledge, no MVVM is possible (there is a relationship you need to be aware of, to allow interaction between views and view-models).