Item is null in DataTemplateSelector
Asked Answered
A

4

5

Well a have a little problem using a dataTemplateSelector to choose the right datatemplate for my view model based in an enum value.

This is a demo that reproduces the issue.

I have a hierarchy of models that are used by my viewModels

The enum that define the model types is:

public enum ModelType
{
    ModelA,
    ModelB        
}

The model base class is:

public abstract class ModelBase
{
    protected ModelBase(ModelType modelType)
    {
        ModelType = modelType;
    }

    public ModelType ModelType { get; private set; }


    public string Name { get; set; }
}

and the child model clases are:

public class ModelA:ModelBase
{
    public ModelA():base(ModelType.ModelA)
    {
        Name = "ModelA";
    }


    public string PropertyModelA { get { return "PropertyModelA"; } }
}

and

public class ModelB : ModelBase
{
    public ModelB()
        : base(ModelType.ModelB)
    {

        Name = "ModelB";
    }
    public string PropertyModelB { get { return "PropertyModelB"; } }


}

My MainViewModel and the ModelViewModel respectively are:

public class MainWindowViewModel:ViewModelBase
{

    public MainWindowViewModel()
    {

        Models = new ObservableCollection<ModelViewModel>();
        LoadModels();
    }
    public ObservableCollection<ModelViewModel> Models { get; private set; }

    private void LoadModels()
    {
        Models.Add(new ModelViewModel(new ModelA()));
        Models.Add(new ModelViewModel(new ModelB()));
        Models.Add(new ModelViewModel(new ModelB()));
    }

and

public class ModelViewModel : ViewModelBase
{
    private ModelBase _model;

    public ModelViewModel(ModelBase model)
    {
        _model = model;
    }

    public ModelBase Model
    {
        get { return _model; }
        set
        {
            if (!_model.Equals(value))
            {
                _model = value;
                OnPropertyChanged("Model");
            }

        }
    }

}

After that I have a List box in my MainView that use a item template to show each item.

 <ListBox x:Name="entryList" ItemsSource="{Binding Models}"  >
            <ListBox.ItemTemplate>
                <DataTemplate>                       
                      <views:ModelView/>     
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

This item template use other view called ModelView to render the item.ModelView shows the common information , the specific model data are showed by the view selected by the ModelSelector.

<UserControl.Resources>
    <ResourceDictionary>
        <selectors:ModelSelector x:Key="modelSelector" />
    </ResourceDictionary>
</UserControl.Resources>
<StackPanel>
    <TextBlock Text="{Binding Model.Name}" />
    <ContentPresenter  ContentTemplateSelector="{StaticResource modelSelector}" DataContext="{Binding }" />
</StackPanel>

At the moment the views that could be selected by the model selector are A and B:

<StackPanel>
    <TextBlock Text="{Binding Model.PropertyModelA}" />
</StackPanel>


<StackPanel>
    <TextBlock Text="{Binding Model.PropertyModelB}" />
</StackPanel>

The model Selector is:

public class ModelSelector:DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var viewModel = item as ModelViewModel;

        var dataTemplate = default(DataTemplate);

        if (viewModel != null)
        {
            switch (viewModel.Model.ModelType)
            {
                case NestedDataTemplateSelectorTest.Models.ModelType.ModelA:
                    dataTemplate = CreateDataTemplate<ModelAView>();
                    break;
                case NestedDataTemplateSelectorTest.Models.ModelType.ModelB:
                    dataTemplate = CreateDataTemplate<ModelBView>();
                    break;
                default:
                    dataTemplate = this.SelectTemplate(item, container);
                    break;
            }
        }
        return dataTemplate;           
    }

    private DataTemplate CreateDataTemplate<TView>()
    {
        var dataTemplate = new DataTemplate();
        var frameworkElement = new FrameworkElementFactory(typeof(TView));
        dataTemplate.VisualTree = frameworkElement;

        return dataTemplate;
    }
}

The problem is that the parameter item in the DataTemplateSelector is null and the other parameter(Container) have the dataContext in null.I don't have way to know which is the value of the ModelViewModel to choose the right view.

if I put the data template in the ListView Item template then the item have the value of the ModelViewMode but I need to have that template in a separate file because it will be used in diferent parts of the application.

I dont know can i do to have access to the ModelViewModel in the ModelSelector?

Airminded answered 23/1, 2013 at 22:7 Comment(0)
S
15

Yes, you can access your ViewModel throug DataTemplateSelector. Your mistake is that you set the DataContext property:

DataContext="{Binding}"

For ContentPresenter, you should set Content property instead

Content="{Binding}"

If you will do that, the object item inside the overriden method will be exactly Content property.

According to the msdn article:

If the ContentTemplateSelector property on the ContentPresenter is set, the ContentPresenter applies the appropriate DataTemplate to the Content property and the resulting UIElement and its child elements, if any, are displayed.

Pay attention to the ContentPresenter logic to display the Content.

Suburban answered 24/1, 2013 at 4:40 Comment(1)
Thanks @Suburban for your help but I already tried that and It didn't work, i don't know why. I think that the issue is related with the binding and that the content of the item template is in a separate file. If I put it in the same file it works :sAirminded
A
6

Urgh - after beating my head against a wall with exactly the same issue I finally figured out the problem.

You need to use a 'ContentControl' instead of a 'ContentPanel', and as Stukselbax suggests it's the content you need to bind, not the datacontext.

Sorry it's 2 years too late for you, but hopefully it will help someone else!

Appalachia answered 7/3, 2015 at 6:36 Comment(1)
Thanks. I used a ContentPresenter by accident, this was the answer I needed!Davidadavidde
W
2

Late answer, but to use a TemplateSelector, you need to set content first, so for a ContentControl you set Content before ContentTemplateSelector in Xaml..

Same for ListView with ItemsSource and ItemTemplateSelector I guess.

Something like this:

<ContentControl Content="{Binding Animals}" ContentTemplateSelector="{StaticResource AnimalTemplateSelctor}" />
Weingartner answered 11/3, 2016 at 13:48 Comment(1)
Just an FYI....You can also use the {x:Bind } nomenclature for this as well.Ricebird
B
0

just for history Bindings is complicated. They call multiple times. Before applying templates and after. In my situation first call was before my templates applied with null in item, and after with data. So simple answer is check\debug for not null in item.

Buttonhole answered 10/1, 2021 at 3:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.