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 шт):

Автор решения: aepot

Показываю, как в 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.

→ Ссылка