UserControl с параметрами в MVVM (C#, WPF)
Всем привет. Вопрос от новичка.
Планирую, что в приложении в определенный момент будет создаваться некоторый экземпляр UserControl'а (не один, но это не суть важно) с передачей ему определенных данных в качестве параметра.
Дальше UserControl должен получить эти данные и создать экземпляр личной ViewModel'и, и передать данные в неё. ViewModel знает, что делать с данными.
1) Правильно ли я понимаю, что получать входные данные UserControl должен в собственном конструкторе в Code Behind? Или есть более MVVM-ное решение?
2) Насколько я понимаю, экземпляр ViewModel'и создается в XAML следующим образом:
<UserControl.Resources>
<vm:MyControlViewModel x:Key="ControlVM"/>
</UserControl.Resources>
Не могу найти описание этого синтаксиса в учебниках и мануалах, поэтому не могу понять, как здесь реализовать передачу какого-либо параметра в создаваемый экземпляр ViewModel'и.
Возможно, я мыслю совершенно не в том направлении и View Control'а не должна создавать себе экземпляр ViewModel'и? Но если создавать извне отдельно экземпляр View и отдельно экземпляр ViewModel'и, как им объяснить, что именно этот экземпляр View связывается именно с этим экземпляром ViewModel?
Ответы (1 шт):
Показываю, как в UserControl можно засунуть кастомный DepencyProperty и прибиндить к нему свою собственную View Model.
К примеру, пусть будет 2 текстблока, один будет использовать DepencyProperty, другой будет пытаться взять что-то из DataContext, то есть из View Model. Получается вот такая разметка:
UserControl1.xaml
<UserControl x:Class="WpfApp2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style TargetType="{x:Type local:UserControl1}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:UserControl1}">
<StackPanel>
<TextBlock Text="{TemplateBinding MyText}"/>
<TextBlock Text="{Binding MyVMText}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
DepencyProperty живут в код-бихайнде в данном случае есть одна типа string и называется MyText.
UserControl1.xaml.cs
public partial class UserControl1 : UserControl
{
public static readonly DependencyProperty MyTextProperty = DependencyProperty.Register("MyText", typeof(string), typeof(UserControl1));
public UserControl1()
{
InitializeComponent();
}
public string MyText
{
get => (string)GetValue(MyTextProperty);
set => SetValue(MyTextProperty, value);
}
}
Теперь ViewModel для UserControl.
На самом деле, VM ничего не знает про то, где и какое View будет к ней привязываться. VM просто содержит свойства и как-то их меняет, а View уже на свое усмотрение оттуда берет что-нибудь и как-нибудь это отображает. Но я как программист знаю, что мне нужна VM, обеспечивающая работу конкретного UserControl1, и я ее создаю и называю соответствующим образом.
UserControlViewModel.cs
public class UserControlViewModel : NotifyPropertyChanged
{
private string _myVMText;
public string MyVMText
{
get => _myVMText;
set
{
_myVMText = value;
OnPropertyChanged();
}
}
}
Свойство назвал по-другому, не так как DP, но можно и одинаково. Это я сделал чтобы визуально была разница.
Теперь у нас есть вьюмодель окна, где будет жить этот UserControl1, то есть его экземпляр, там то я и создаю эту VM как обычное свойство, реализующее INotifyPropertyChanged. А это все для того, чтобы если захочется переключить UserControl.DataContext, на другую вьюмодель в рантайме, то INPC нам поможет.
MainViewModel.cs
private UserControlViewModel _userControlVM;
public UserControlViewModel UserControlVM
{
get => _userControlVM;
set
{
_userControlVM = value;
OnPropertyChanged();
}
}
// инициализирую что-нибудь в эту VM в конструкторе главной VM
public MainViewModel()
{
UserControlVM = new UserControlViewModel { MyVMText = "Text for UserControl's VM" };
}
А использую это следующим образом.
MainWindow.xaml
<local:UserControl1 Grid.Column="1" MyText="Text fro DepencyProperty" DataContext="{Binding UserControlVM}"/>
Но вы же скажете, что MyText тоже можно прибиндить к чему угодно!, и я отвечу: да, можно. Какой из способов доставки данных в UserControl вам больше нравится, тот и используйте. Можно вообще обойтись без кастомных DepencyProperty и биндиться только в VM, можно наоборот, можно смешать (но не взбалтывать!). Оба способа валидны и прекрасно подходят для MVVM.