MVVM View против проблем привязки ViewModel

У меня возникла проблема при создании экземпляров viewModels.

Я использую ViewModelLocator по большей части, так как мне приходится вводить зависимости в большинстве случаев. Однако есть случаи, когда мне нужно передать аргументы в ViewModel. Насколько я понимаю, для этого мне нужно использовать подход ViweModel-First. Это означает, что мне нужно создать DataTemplate для ViewModel, который связан с View во время выполнения. Обязательно включите конструктор с аргументами, которые я хочу передать.

У меня проблема в том, что когда я создаю ViewModel и передаю свои аргументы, вызывается правильный конструктор. Однако, поскольку ViewModel привязан к представлению, представление вызывает конструктор по умолчанию без параметров для модели представления.

Вот как выглядит XAML для UserControl, с которым я связываю ViewModel:

<UserControl x:Class="MyView">
    <UserControl.DataContext>
      <viewModels:MyViewModel></viewModels:MyViewModel>
    </UserControl.DataContext>
</UserControl>

Шаблон данных выглядит так:

<DataTemplate DataType="{x:Type viewModels:MyViewModel}">
   <views:MyView></views:MyView>
</DataTemplate>

Вот пример ViewModel:

public class MyViewModel : ViewModelBase
{
  private MyModel _myModel;

  public MyViewModel()
  {
  }

  public MyViewModel(MyModel myModel)
  {
    _myModel = myModel;
  }
}

Как только я создаю свою viewModel через код, используя правильный конструктор для передачи аргументов, viewModel снова создается представлением, используя конструктор по умолчанию без параметров viewModel.

Может кто-нибудь объяснить, почему это происходит, и пролить некоторый свет на то, как настроить подход viewmodel-first, чтобы он работал правильно? Я в растерянности и работаю над этим весь день.

Спасибо Тим

1 ответ

Решение

Если вы удалите следующий фрагмент из вашего UserControl и следуйте моим остальным инструкциям, я думаю, у вас будет то, что вы хотите:

Удали это

<UserControl.DataContext>
      <viewModels:MyViewModel></viewModels:MyViewModel>
</UserControl.DataContext>

Теперь допустим, что у вас есть UserControl или же Window привязан к ViewModel что вы хотите представить в вашем UserControl или же Window, То, как вы могли бы сделать это, это использовать ContentControl внутри вашего UserControl или же Window в сочетании с DataTemplate указано в ResourceDictionary вот так:

.xaml:

<Window x:Class="WPF_Sandbox.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
    Title="MainWindow"
    x:Name="ThisControl">
    <Window.DataContext>
        <vm:MainWindowViewModel/>
    </Window.DataContext>
    <DockPanel LastChildFill="True">
        <ContentControl DockPanel.Dock="Left" Content="{Binding NavigationRegion}"/>
        <ContentControl DockPanel.Dock="Left" Content="{Binding ContentRegion}"/>
    </DockPanel>    
</Window>

ContentControl будет неявно искать DataTemplate (в иерархии ResourceDictionary объекты), связанные с ViewModel что связано с его Content имущество. Итак, давайте скажем, что мы установили ContentRegion недвижимость в MainWindowViewModel к примеру вашего MyViewModel вот так:

MainWindowViewModel.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WpfApplication1.ViewModels
{
    public class MainWindowViewModel : ViewModel
    {
        private object _navigationRegion;
        private object _contentRegion;

        public object NavigationRegion
        {
            get
            {
                return _navigationRegion;
            }
            set
            {
                _navigationRegion = value;
                OnPropertyChanged(nameof(NavigationRegion));
            }
        }

        public object ContentRegion
        {
            get
            {
                return _contentRegion;
            }
            set
            {
                _contentRegion = value;
                OnPropertyChanged(nameof(ContentRegion));
            }
        }

        public MainWindowViewModel()
        {
            ContentRegion = new MyViewModel(new MyModel());
        }
    }
}

И у вас есть ResourceDictionary указано так:

MyResourceDictionary.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfApplication1"
                    xmlns:vm="clr-namespace:WpfApplication1.ViewModels"
                    xmlns:views="clr-namespace:WpfApplication1.Views">
    <DataTemplate DataType="{x:Type vm:MyViewModel}">
        <views:MyView/>
    </DataTemplate>

</ResourceDictionary>

И вы объединили ResourceDictionary с вашим Application.Resources в вашем файле App.xaml так:

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:WpfApplication1"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="MyResourceDictionary.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

Framework будет неявно искать DataTemplate для типа данных MyViewModel, Рамки найдут DataTemplate за MyViewModel в ResourceDictionary мы указали и видим, что он должен быть представлен MyView пользовательский контроль. Именно в этот момент Framework будет оказывать MyView пользовательский контроль.

И для потомков:

ViewModel.cs

using System.ComponentModel;

namespace WpfApplication1.ViewModels
{
    public abstract class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected string _title;

        protected string Title
        {
            get
            {
                return _title;
            }
            set
            {
                _title = value;
                OnPropertyChanged(nameof(Title));
            }
        }

        protected void OnPropertyChanged(string propName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        }
    }
}
Другие вопросы по тегам