Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Item is null in DataTemplateSelector

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?

like image 564
welhell Avatar asked Jan 23 '13 22:01

welhell


3 Answers

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.

like image 57
stukselbax Avatar answered Sep 21 '22 21:09

stukselbax


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!

like image 27
Ross Dargan Avatar answered Sep 21 '22 21:09

Ross Dargan


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}" />
like image 44
DanTheManSWE Avatar answered Sep 18 '22 21:09

DanTheManSWE