1
votes

I have this really simple MVVM code made with Prism:

  • 2 Models (Person, Company - common interface IContact) with 2 ViewModels (Prism) and 2 Views (UserControl)
  • 1 ViewModel (collection of interface IContact) with 1 View (ListBox bound to the collection and a ContentControl bound to ListBox's SelectedItem with DataTemplateSelector that returns one of the two UserControls based on the type of the SelectedItem)

How can I pass the Model object (either Person or Company) from the ListBox's SelectedItem (IContact) to one of the two ViewModels (either PersonViewModel or CompanyViewModel) that match the View returned by the DataTemplateSelector (either PersonView or CompanyView)?

Thank you!


There is a lot of code, but it is really simple:

I have these Model classes:

public interface IContact
{
    string Address { get; set; }
}

public class Person : IContact
{
    public string Address { get; set; }
}

public class Company : IContact
{
    public string Address { get; set; }
}

I have these ViewModel classes:

public class ContactViewModel : Prism.Mvvm.BindableBase
{
    private ObservableCollection<IContact> _contacts = new ObservableCollection<IContact>();
    public ObservableCollection<IContact> Contacts
    {
        get { return _contacts; }
        set { SetProperty(ref _contacts, value); }
    }
}

public class PersonViewModel : Prism.Mvvm.BindableBase
{
    private Person _person; // I want to set this from the ListBox's SelectedItem
    public Person Person
    {
        get { return _person; }
        set { SetProperty(ref _person, value); }
    }
}

public class CompanyViewModel : Prism.Mvvm.BindableBase
{
    private Company _company; // I want to set this from the ListBox's SelectedItem
    public Company Company
    {
        get { return _company; }
        set { SetProperty(ref _company, value); }
    }
}

I have these View classes:

<UserControl x:Class="ContactView"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True" >
    <UserControl.Resources>
        <DataTemplate x:Key="PersonDataTemplate">
            <local:PersonView>
                // How can I pass the SelectedItem to the ViewModel of this UserControl?
            </local:PersonView>
        </DataTemplate>
        <DataTemplate x:Key="CompanyDataTemplate">
            <local:CompanyView>
                // How can I pass the SelectedItem to the ViewModel of this UserControl?
            </local:CompanyView>
        </DataTemplate>
        <dataTemplateSelectors:contactDataTemplateSelector x:Key="templateSelector"
              PersonDataTemplate="{StaticResource PersonDataTemplate}" 
              CompanyDataTemplate="{StaticResource CompanyDataTemplate}"/>
    </UserControl.Resources>
    <Grid>
        // RowDefinitions here
        <ListBox ItemsSource="{Binding Contacts}" x:Name="myListBox">
            // ItemTemplate here
        </ListBox>
        <ContentControl Grid.Row="1" 
            Content="{Binding ElementName=myListBox, Path=SelectedItem}" 
            ContentTemplateSelector="{StaticResource templateSelector}" />
    </Grid>
</UserControl>

Person:

<UserControl x:Class="PersonView"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True" >
    <Grid>
        <TextBlock Text="{Binding Person.Address}" />
    </Grid>
</UserControl>

Company:

<UserControl x:Class="CompanyView"
             xmlns:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True" >
    <Grid>
        <TextBlock Text="{Binding Company.Address}" />
    </Grid>
</UserControl>

And this:

public class ContactDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate PersonDataTemplate { get; set; }
    public DataTemplate CompanyDataTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        if (item is Person)
        {
            return PersonDataTemplate;
        }
        if (item is Company)
        {
            return CompanyDataTemplate;
        }
    }
}
2
Why don't you add a property to the ContactViewModel if you wan to keep track of the selected item in the ListBox?mm8
@mm8 The code is very simplified. My goal is not to track the selected item in the ContactViewModel class - that would be a very simple binding to a property. My goal is to pass the selected item from the ContactViewModel to either PersonViewModel or CompanyViewModel trough the ContentControl via DataTemplateSelector that returns either PersonView or CompanyView.Jinjinov

2 Answers

2
votes

Do not use view first (a.k.a. ViewModelLocator) here. Go view model first.

Long version:

Make Contacts (the item source of the list box) contain view models. Then directly bind SelectedItem to the content control.

The listbox uses one data template to show the contacts, the content control uses another. You don't even need the selector, just set DataType on your data templates.

When you already have the item at hand that you want to show (i.e. its view model), just bind and show that one. If you want to navigate to a screen in your app (e.g. login dialog), use the ViewModelLocator. It basically is a workaround for not having the view model ready.

0
votes

All credit goes to Haukinger!

This answer is just because Sagar Panwala asked how I did it...


In the end I did not do it exactly the way I imagined at first.

I did it a little bit different:

The BindableBase ViewModel:

    public Dictionary<string, Dictionary<string, PositioningModuleSetting>>? SelectedSettings;

The PositioningModuleSetting class:

public class PositioningModuleSetting
{
    public string Section { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;

    public dynamic value = null!;
    public string description = string.Empty;
    public PositioningModuleRestart restart;

    public Action<PositioningModuleSetting>? OnSettingChanged { get; set; }

    public bool BoolValue
    {
        get { return value; }
        set { this.value = value; OnSettingChanged?.Invoke(this); }
    }

    public double DoubleValue
    {
        get { return value; }
        set { this.value = value; OnSettingChanged?.Invoke(this); }
    }

    public long LongValue
    {
        get { return value; }
        set { this.value = value; OnSettingChanged?.Invoke(this); }
    }

    public string StringValue
    {
        get { return value; }
        set { this.value = value; OnSettingChanged?.Invoke(this); }
    }

    public object ObjectValue
    {
        get { return value; }
        set { this.value = value; OnSettingChanged?.Invoke(this); }
    }

    public void Initialize(string section, string name, Action<PositioningModuleSetting> onSettingChanged)
    {
        Section = section;
        Name = name;
        OnSettingChanged = onSettingChanged;
    }
}

The DataTemplateSelector class:

public class SettingsDataTemplateSelector : DataTemplateSelector
{
    public DataTemplate? DefaultDataTemplate { get; set; }
    public DataTemplate? BoolDataTemplate { get; set; }
    public DataTemplate? DoubleDataTemplate { get; set; }
    public DataTemplate? LongDataTemplate { get; set; }
    public DataTemplate? StringDataTemplate { get; set; }

    public override DataTemplate? SelectTemplate(object item, DependencyObject container)
    {
        if (item is KeyValuePair<string, PositioningModuleSetting> pair)
        {
            return pair.Value.value switch
            {
                bool _ => BoolDataTemplate,
                double _ => DoubleDataTemplate,
                long _ => LongDataTemplate,
                string _ => StringDataTemplate,
                _ => DefaultDataTemplate
            };
        }

        return DefaultDataTemplate;
    }
}

The UserControl View:

<UserControl.Resources>
    <DataTemplate x:Key="DefaultDataTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock MinWidth="180" Text="{Binding Path=Key}" Style="{StaticResource LabelTextBlock}" FontSize="{StaticResource SmallestFontSize}" />
            <TextBox MinWidth="240" Text="{Binding Path=Value.ObjectValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="{StaticResource SmallestFontSize}" />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="BoolDataTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock MinWidth="180" Text="{Binding Path=Key}" Style="{StaticResource LabelTextBlock}" FontSize="{StaticResource SmallestFontSize}" />
            <CheckBox IsChecked="{Binding Path=Value.BoolValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="DoubleDataTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock MinWidth="180" Text="{Binding Path=Key}" Style="{StaticResource LabelTextBlock}" FontSize="{StaticResource SmallestFontSize}" />
            <TextBox MinWidth="240" Text="{Binding Path=Value.DoubleValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="{StaticResource SmallestFontSize}" />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="LongDataTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock MinWidth="180" Text="{Binding Path=Key}" Style="{StaticResource LabelTextBlock}" FontSize="{StaticResource SmallestFontSize}" />
            <TextBox MinWidth="240" Text="{Binding Path=Value.LongValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="{StaticResource SmallestFontSize}" />
        </StackPanel>
    </DataTemplate>

    <DataTemplate x:Key="StringDataTemplate">
        <StackPanel Orientation="Horizontal">
            <TextBlock MinWidth="180" Text="{Binding Path=Key}" Style="{StaticResource LabelTextBlock}" FontSize="{StaticResource SmallestFontSize}" />
            <TextBox MinWidth="240" Text="{Binding Path=Value.StringValue, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" FontSize="{StaticResource SmallestFontSize}" />
        </StackPanel>
    </DataTemplate>

    <dataTemplateSelectors:SettingsDataTemplateSelector x:Key="templateSelector"
          DefaultDataTemplate="{StaticResource DefaultDataTemplate}"
          BoolDataTemplate="{StaticResource BoolDataTemplate}" 
          DoubleDataTemplate="{StaticResource DoubleDataTemplate}" 
          LongDataTemplate="{StaticResource LongDataTemplate}" 
          StringDataTemplate="{StaticResource StringDataTemplate}" />
</UserControl.Resources>

<Grid>
    <ItemsControl ItemsSource="{Binding Path=SelectedSettings}">
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <StackPanel>
                    <TextBlock Text="{Binding Path=Key}" Style="{StaticResource LabelTextBlock}" />
                    <ItemsControl ItemsSource="{Binding Path=Value}" ItemTemplateSelector="{StaticResource templateSelector}" />
                </StackPanel>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>