简体   繁体   English

将 SelectedItem 传递给 DataTemplate 中 UserControl 的 ViewModel

[英]Pass SelectedItem to the ViewModel of the UserControl inside DataTemplate

I have this really simple MVVM code made with Prism:我有这个用 Prism 制作的非常简单的 MVVM 代码:

  • 2 Models (Person, Company - common interface IContact) with 2 ViewModels (Prism) and 2 Views (UserControl) 2 个模型(人、公司 - 通用接口 IContact),带有 2 个 ViewModel(Prism)和 2 个视图(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) 1 个 ViewModel(接口 IContact 的集合)和 1 个视图(ListBox 绑定到集合,ContentControl 绑定到 ListBox 的 SelectedItem,DataTemplateSelector 根据 SelectedItem 的类型返回两个 UserControl 之一)

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)?如何将 Model 对象(Person 或 Company)从 ListBox 的 SelectedItem (IContact) 传递到与 DataTemplateSelector(PersonView 或 CompanyView)返回的视图匹配的两个 ViewModel(PersonViewModel 或 CompanyViewModel)之一?

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:我有这些 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); }
    }
}

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

Do not use view first (aka ViewModelLocator ) here.不要在这里首先使用视图(又名ViewModelLocator )。 Go view model first.先去查看模型。

Long version:长版:

Make Contacts (the item source of the list box) contain view models.使Contacts (列表框的项目源)包含视图模型。 Then directly bind SelectedItem to the content control.然后直接将SelectedItem绑定到内容控件上。

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.您甚至不需要选择器,只需在数据模板上设置DataType

When you already have the item at hand that you want to show (ie its view model), just bind and show that one.当您手头已有要显示的项目(即其视图模型)时,只需绑定并显示该项目。 If you want to navigate to a screen in your app (eg login dialog), use the ViewModelLocator .如果要导航到应用程序中的屏幕(例如登录对话框),请使用ViewModelLocator It basically is a workaround for not having the view model ready.它基本上是没有准备好视图模型的解决方法。

All credit goes to Haukinger!所有功劳都归功于豪金格!

This answer is just because Sagar Panwala asked how I did it...这个答案只是因为 Sagar Panwala 问我是怎么做到的...


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: BindableBase视图模型:

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

The PositioningModuleSetting class: PositioningModuleSetting类:

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: DataTemplateSelector类:

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视图:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM