2

I have a collection of models (i.e. Format classes) which I would like to load on my view (i.e. MainWindowView.xaml). These models could be loaded more than once.

In order to do that, I started to create a ViewModel and a DataTemplate for each model class. For example, model class FormatContainer has FormatContainerViewModel as view model:

 namespace MyProject.ViewModels
    {

    public class FormatContainerViewModel : ViewModelBase
    {
        public FormatContainerViewModel(FormatContainer wrappedFormat)
        {
            _wrappedFormat = wrappedFormat; // Wrap model.

            SubFormats = GetSubFormatsViewModels(_wrappedFormat.SubFormats); // Get the collection of Formats contained into container and generate their ViewModels.

            FormatLabel = _wrappedFormat.Description; // Get wrapped format description.
        }

        private FormatContainer _wrappedFormat;

        private ObservableCollection<ViewModel> _subFormats;
        public ObservableCollection<ViewModel> SubFormats
        {
            get
            {
                return _subFormats;
            }
            set
            {
                Set(() => SubFormats, ref _subFormats, value);
                RaisePropertyChanged("SubFormats");
            }
        }

        private string _formatLabel;
        public string FormatLabel
        {
            get
            {
                return _formatLabel;
            }
            set
            {
                Set(() => FormatLabel, ref _formatLabel, value);
                RaisePropertyChanged("FormatLabel");
            }
        }
    }
    }

Additionally, model FormatContainer has a DataTemplate as view which is added by a Resourcedictionary:

 <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:MyProject.ViewModels">


    <DataTemplate DataType="{x:Type local:FormatContainerViewModel}">
        <Border BorderBrush="Black" BorderThickness="1" Padding="20">
            <Grid HorizontalAlignment="Center" VerticalAlignment="Center">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition />
                    <ColumnDefinition />
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition />
                </Grid.RowDefinitions>

                <!-- Show container description. -->
                <TextBlock Grid.Column="0" Grid.Row="0"  Text="{Binding FormatLabel}" VerticalAlignment="Center" Margin="5" />

                <!-- Show container sub formats. -->
                <!--<StackPanel Grid.Column="1" Grid.Row="0" Orientation="Horizontal" Margin="3,3,3,3">
                    <ContentControl Content="{Binding SubFormats}" HorizontalAlignment="Center" Margin="1" />
                </StackPanel-->>

            </Grid>
        </Border>
    </DataTemplate>

    <!-- other DataTemplates ... -->

    </ResourceDictionary>

Therefore, this resource dictionary (i.e. FormatsView.xaml) is added in App.xaml:

    <Application x:Class="MyProject.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:MyProject"
             xmlns:vm="clr-namespace:MyProject.ViewModels"
             StartupUri="MainWindowView.xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
            d1p1:Ignorable="d" xmlns:d1p1="http://schemas.openxmlformats.org/markup-compatibility/2006">
    <Application.Resources>
        <ResourceDictionary>
            <vm:ViewModelLocator x:Key="Locator" d:IsDataSource="True" xmlns:vm="clr-namespace:MyProject.ViewModels" />

            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/PanelsView.xaml" />
            </ResourceDictionary.MergedDictionaries>

        </ResourceDictionary>
    </Application.Resources>
</Application>

Then, these ViewModels are instantiated in the main view model (i.e. MainWindowViewModel) by a service class (i.e. ViewModelsGeneratorService).

ViewModelsGeneratorService tempGenerator = new ViewModelsGeneratorService(myFormats); // Use a service to convert current format objects to view models.
ViewModelsList = tempGenerator.ViewModelsList; // Populate view models obesrvable collections.

Finally, these view models are shown in MainWindowView by a ContentControl:

    <Window x:Class="MyProject.MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:MyProject"
        xmlns:interactivity="using:Microsoft.Expression.Interactivity"
        xmlns:core="using:Microsoft.Expression.Interactions.Core"
        mc:Ignorable="d"
        Title="MySoftware" Height="350" Width="525"
        DataContext="{Binding Main, Source={StaticResource Locator}}">
    <Grid>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="1*" />
        </Grid.RowDefinitions>

        <!-- ... -->

        <ContentControl Grid.Row="3" Content="{Binding ViewModelsList}" />

    </Grid>
</Window>

Unfortunately, the ContentControl with content ViewModelsList is empty, while ViewModelLists contains 53 items. What am I missing? Would someone provide a pointer, please?

I already checked some tutorial and discussion in order to check my code (e.g. Rachel, Gala, Marco, etc.), but I could not figure it out what i am missing..

Moveover, the ContentControl is not showing the sub formats. Of course, when uncommented ;-) What am I still missing here too?

All the best, Lorenzo

P.S. I am currently developing for .NET 4.5 and using MVVM Light framework.

P.P.S. FormatContainer is the only one of my formats which has sub formats, the others formats just change theyr properties and so their view (e.g. a format could show a combobox instead of a listbox in addition to the label, and/or instead of a textbox, etc.).

Community
  • 1
  • 1
Lorenzo R.
  • 133
  • 9

2 Answers2

2

Collections are bound by ItemsControl or other list based controls, not by ContentControl.

<ItemsControl ItemsSource="{Binding ViewModelsList}"/>

If you want an ItemsPanel that doesn't inherit ItemsControl (like WrapPanel), you can set it with the ItemsPanelTemplate property.

grek40
  • 13,113
  • 1
  • 24
  • 50
  • Thank you, I updated my MainWindowView by changing ContentControl with ItemsControl: with However, the views are not visible yet in the main view. :( – Lorenzo R. Jul 12 '16 at 12:16
  • @LorenzoR. Any binding errors available? What happens if you bind a textblock to the item count: ``? Does it show the expected number of items? – grek40 Jul 12 '16 at 12:27
  • @LorenzoR. See the following link for a way to get additional information from binding expressions: https://spin.atomicobject.com/2013/12/11/wpf-data-binding-debug/ – grek40 Jul 12 '16 at 13:12
  • perfect! Now it is up and running! I added a textblock to show the count as you suggested; Then, I saw that the count was never shown. Hence, I noticed that there was a mispelling in the ItemsControl binding.. my inattention fault! – Lorenzo R. Jul 12 '16 at 13:16
0

Try implementing INotifyPropertyChanged on your viewModel and add PropertyChangedEventHandler

public class FormatContainerViewModel : ViewModelBase, INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    private void NotifyPropertyChanged(String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    //Add notifyPropertyChanged on your desired property
    //Example

    private ObservableCollection<ViewModel> _subFormats;
    public ObservableCollection<ViewModel> SubFormats
    {
        get
        {
            return _subFormats;
        }
        set
        {
            Set(() => SubFormats, ref _subFormats, value);
            RaisePropertyChanged("SubFormats");
            NotifyPropertyChanged("SubFormats");
        }
    }


}
Luke Villanueva
  • 2,030
  • 8
  • 44
  • 94
  • I followed your suggestion, but without succeeding :( Btw, as it is provided, your snipped do not initialize PropertyChanged and the "if" body is never executed. Should I provide any delegate in the constructor? – Lorenzo R. Jul 12 '16 at 13:01
  • I would suspect that `ViewModelBase` with the `Set` and `RaisePropertyChanged` methods already implements `INotifyPropertyChanged`. – grek40 Jul 12 '16 at 13:09
  • @grek40 AFAIK, it should ;) However, I checked that possibility as well. Just to clarify, by implementing ViewModelBase, using ItemsControl, and putting the observable collection property wrote correctly: it works like a charm! – Lorenzo R. Jul 12 '16 at 13:20
  • I'm relatively new to MVVM and all my properties have both RaisePropertyChanged and INotifyPropertyChanged . I guess it performs the same? I was experiencing issues before where my binded items control were not being notified of changes. Implementing INotifyPropertyChanged did the trick. If you guys have extra time to explain to me their difference, that would be great. Thanks! – Luke Villanueva Jul 12 '16 at 14:28
  • @ljpv14 AFAIK, if you are using as well MVVM Light Framework, SET method of ViewModelBase class include PropertyChanged. Hence, If you use the Set method there is no need of specify another call to the delegate. For more details you may follow this [link](http://www.mvvmlight.net/help/WP8/html/4162331c-43b6-7806-f488-8f6426aa0304.htm). – Lorenzo R. Jul 12 '16 at 15:25