У меня есть очень простой код MVVM, сделанный с помощью Prism:
Как передать объект модели (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;
}
}
}
@ mm8 Код очень упрощен. Моя цель - не отслеживать выбранный элемент в классе ContactViewModel - это была бы очень простая привязка к свойству. Моя цель - передать выбранный элемент из ContactViewModel в PersonViewModel или CompanyViewModel через ContentControl через DataTemplateSelector, который возвращает либо PersonView, либо CompanyView.





Не используйте здесь сначала просмотр (также известный как ViewModelLocator). Сначала просмотрите модель.
Длинная версия:
Сделайте Contacts (источник элемента списка) содержащим модели представлений. Затем напрямую привяжите SelectedItem к элементу управления содержимым.
Список использует один шаблон данных для отображения контактов, элемент управления содержимым - другой. Вам даже не нужен селектор, просто установите DataType в свои шаблоны данных.
Если у вас уже есть элемент, который вы хотите показать (то есть его модель представления), просто привяжите и покажите его. Если вы хотите перейти к экрану в вашем приложении (например, диалогу входа в систему), используйте ViewModelLocator. По сути, это обходной путь для нет с готовой моделью представления.
Код в вопросе очень упрощен. Причина, по которой Contacts содержит модели, а не ViewModels, заключается в том, что он исходит из ObservableCollection<TEntity> Local в DbSet<TEntity> в DbContext в Entity Framework - который, конечно, не должен содержать ViewModels. Единственный обходной путь - скопировать коллекцию.
Не только копировать, вы должны сопоставить коллекцию. Не смешно, но неизбежно, большую часть времени если вы хотите изменить из базы данных и в графический интерфейс. Но вам все равно не следует открывать классы базы данных для представления, поэтому вам нужно как минимум создать список моделей представления из списка моделей.
Согласен с @Haukinger, что произойдет, если вы захотите добавить дополнительную информацию к просматриваемым объектам. Вам нужно сопоставить новый объект, то есть ViewModel, который на первый взгляд может выглядеть как копия и дополнительная работа, но это позволит вашим объектам отличаться в долгосрочной перспективе.
Хотя ваш ответ кажется очевидным после того, как вы его сформулировали, я весь день думал о том, как это сделать. Просто был слишком использован для сопоставления моделей с источником CollectionView, как показывают примеры Xamarin.
@Jinjinov Поскольку вы отметили этот ответ, не могли бы вы помочь мне, как вы этого добились? У меня точно такая же проблема, но я не мог понять, как это сделать.
@SagarPanwala Я опубликовал свое решение в ответе ниже.
Этот ответ просто потому, что Сагар Панвала спросил, как я это сделал ...
В конце концов, я сделал это не так, как я представлял себе вначале.
Я сделал немного иначе:
Модель просмотра 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>
Почему бы вам не добавить свойство в ContactViewModel, если вы хотите отслеживать выбранный элемент в ListBox?