Передайте SelectedItem в ViewModel элемента управления UserControl внутри DataTemplate

У меня есть очень простой код MVVM, сделанный с помощью Prism:

  • 2 модели (Person, Company - общий интерфейс IContact) с 2 ViewModels (Prism) и 2 View (UserControl)
  • 1 ViewModel (коллекция интерфейса IContact) с 1 представлением (ListBox, привязанный к коллекции, и ContentControl, привязанный к ListBox's SelectedItem с DataTemplateSelector, который возвращает один из двух UserControls на основе типа SelectedItem)

Как передать объект модели (Person или Company) из SelectedItem ListBox (IContact) в одну из двух ViewModel (PersonViewModel или CompanyViewModel), которые соответствуют View, возвращаемому DataTemplateSelector (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;
        }
    }
}

Почему бы вам не добавить свойство в ContactViewModel, если вы хотите отслеживать выбранный элемент в ListBox?

mm8 27.11.2018 15:45

@ mm8 Код очень упрощен. Моя цель - не отслеживать выбранный элемент в классе ContactViewModel - это была бы очень простая привязка к свойству. Моя цель - передать выбранный элемент из ContactViewModel в PersonViewModel или CompanyViewModel через ContentControl через DataTemplateSelector, который возвращает либо PersonView, либо CompanyView.

Jinjinov 27.11.2018 16:14
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
344
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Не используйте здесь сначала просмотр (также известный как ViewModelLocator). Сначала просмотрите модель.

Длинная версия:

Сделайте Contacts (источник элемента списка) содержащим модели представлений. Затем напрямую привяжите SelectedItem к элементу управления содержимым.

Список использует один шаблон данных для отображения контактов, элемент управления содержимым - другой. Вам даже не нужен селектор, просто установите DataType в свои шаблоны данных.

Если у вас уже есть элемент, который вы хотите показать (то есть его модель представления), просто привяжите и покажите его. Если вы хотите перейти к экрану в вашем приложении (например, диалогу входа в систему), используйте ViewModelLocator. По сути, это обходной путь для нет с готовой моделью представления.

Код в вопросе очень упрощен. Причина, по которой Contacts содержит модели, а не ViewModels, заключается в том, что он исходит из ObservableCollection<TEntity> Local в DbSet<TEntity> в DbContext в Entity Framework - который, конечно, не должен содержать ViewModels. Единственный обходной путь - скопировать коллекцию.

Jinjinov 27.11.2018 14:11

Не только копировать, вы должны сопоставить коллекцию. Не смешно, но неизбежно, большую часть времени если вы хотите изменить из базы данных и в графический интерфейс. Но вам все равно не следует открывать классы базы данных для представления, поэтому вам нужно как минимум создать список моделей представления из списка моделей.

Haukinger 27.11.2018 14:52

Согласен с @Haukinger, что произойдет, если вы захотите добавить дополнительную информацию к просматриваемым объектам. Вам нужно сопоставить новый объект, то есть ViewModel, который на первый взгляд может выглядеть как копия и дополнительная работа, но это позволит вашим объектам отличаться в долгосрочной перспективе.

Coops 27.11.2018 15:51

Хотя ваш ответ кажется очевидным после того, как вы его сформулировали, я весь день думал о том, как это сделать. Просто был слишком использован для сопоставления моделей с источником CollectionView, как показывают примеры Xamarin.

Shamps 26.11.2019 08:31

@Jinjinov Поскольку вы отметили этот ответ, не могли бы вы помочь мне, как вы этого добились? У меня точно такая же проблема, но я не мог понять, как это сделать.

Sagar Panwala 01.08.2020 20:03

@SagarPanwala Я опубликовал свое решение в ответе ниже.

Jinjinov 01.08.2020 21:53

Вся заслуга принадлежит Haukinger!

Этот ответ просто потому, что Сагар Панвала спросил, как я это сделал ...


В конце концов, я сделал это не так, как я представлял себе вначале.

Я сделал немного иначе:

Модель просмотра BindableBase:

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

Класс 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;
    }
}

Класс 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;
    }
}

Обзор 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>

Другие вопросы по теме