将 SelectedItem 传递给 DataTemplate 中 UserControl 的 ViewModel

Pass SelectedItem to the ViewModel of the UserControl inside DataTemplate

我有这个用 Prism 制作的非常简单的 MVVM 代码:

如何将模型对象(Person 或 Company)从 ListBox 的 SelectedItem (IContact) 传递到与 DataTemplateSelector 返回的视图匹配的两个 ViewModel(PersonViewModel 或 CompanyViewModel)之一(PersonView 或 CompanyView)?

谢谢!


代码很多,其实很简单:

我有这些型号 类:

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; }
}

我有这些 ViewModel 类:

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); }
    }
}

我有这些视图 类:

<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>

人:

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

公司:

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

还有这个:

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;
        }
    }
}

这里先不要用view(a.k.a.ViewModelLocator)。先去查看模型。

长版:

使 Contacts(列表框的项目源)包含视图模型。然后直接绑定SelectedItem到content控件。

列表框使用一个数据模板来显示联系人,内容控件使用另一个。您甚至不需要选择器,只需在您的数据模板上设置 DataType

当您手边已有要显示的项目(即其视图模型)时,只需绑定并显示该项目即可。如果您想导航到应用程序中的屏幕(例如登录对话框),请使用 ViewModelLocator。它基本上是 not 准备好视图模型的解决方法。

所有功劳都归功于 Haukinger!

这个回答只是因为Sagar Panwala问我是怎么做到的...


最后我并没有完全按照我最初想象的那样去做。

我做的有点不同:

BindableBase 视图模型:

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

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;
    }
}

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;
    }
}

UserControl 视图:

<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>