Prism 6 с Unity — разрешение моделей представления для представлений без соглашения об именах

Я пытаюсь разрешить модели просмотра с помощью DI с Prism 6 и Unity в моем приложении WPF, и это работает. Однако я не знаю, как сообщить фреймворку, какое представление должно быть объединено с какой моделью представления.

Если я использую соглашение, то есть имею пространства имен ViewModels и Views, а также классы ViewA и ViewAViewModel, все работает, однако я хотел бы иметь больше гибкости для именования и организации своих классов, и поэтому я хочу как-то явно указать фреймворку, какое представление идет с какой модели представления. Я пробовал много вещей, но ничего не работает. Текущее «решение» запускает приложение, но модель представления не установлена.

Вот код:

ViewA.xaml

<UserControl x:Class="WPFDITest.Views.ViewA"
             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:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <TextBlock Text="{Binding ViewAMessage}"/>
        <TextBox Text="{Binding ViewAMessage, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
</UserControl>

MainWindow.xaml

<UserControl x:Class="WPFDITest.Views.ViewA"
             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:prism="http://prismlibrary.com/"
             prism:ViewModelLocator.AutoWireViewModel="True"
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <StackPanel>
        <TextBlock Text="{Binding ViewAMessage}"/>
        <TextBox Text="{Binding ViewAMessage, UpdateSourceTrigger=PropertyChanged}"/>
    </StackPanel>
</UserControl>

ViewAVM.cs

public class ViewAVM : BindableBase
{
    private string viewAMessage;

    public ViewAVM(IModelA model)
    {
        viewAMessage = model.HelloMsgA();
    }

    public string ViewAMessage
    {
        get { return viewAMessage; }
        set { SetProperty(ref viewAMessage, value); }
    }
}

Модель.cs

public interface IModelA
{
    string HelloMsgA();
}

public class ModelA : IModelA
{
    public string HelloMsgA()
    {
        return "Hello from A!";
    }
}

App.xaml.cs

public partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        var bootstraper = new Bootstrapper();
        bootstraper.Run();
    }
}

Bootstrapper.cs

public class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow.Show();
    }

    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
        Container.RegisterType<IModelA, ModelA>(new ContainerControlledLifetimeManager());
        Container.RegisterType<object, ViewAVM>("ViewA");
    }

    protected override void ConfigureViewModelLocator()
    {
        ViewModelLocationProvider.SetDefaultViewModelFactory(type => Container.Resolve(type));
    }
}

person Łukasz    schedule 04.05.2016    source источник


Ответы (2)


Покопавшись в исходниках Prism, я понял, как делать то, что я хочу. Я могу зарегистрировать каждое представление с помощью ViewModelLocationProvider.Register, передав фабричный метод для модели представления. Я создал метод, который делает именно это с удобным синтаксисом и использует контейнер для разрешения модели представления для данного типа:

public void BindViewModelToView<TViewModel, TView>()
{
    ViewModelLocationProvider.Register(typeof(TView).ToString(), () => Container.Resolve<TViewModel>());
}

И вот как я использую его для привязки ViewAVM к ViewA и ViewB, используя один и тот же экземпляр singleton.

public class Bootstrapper : UnityBootstrapper
{
    protected override DependencyObject CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    protected override void InitializeShell()
    {
        Application.Current.MainWindow.Show();
    }

    protected override void ConfigureContainer()
    {
        base.ConfigureContainer();
        Container.RegisterType<IModelA, ModelA>(new ContainerControlledLifetimeManager());
        Container.RegisterType<ViewAVM>(new ContainerControlledLifetimeManager());
    }

    protected override void ConfigureViewModelLocator()
    {
        BindViewModelToView<ViewAVM, ViewA>();
        BindViewModelToView<ViewAVM, ViewB>();
    }
}

Кстати, насколько я могу судить по источникам, связать представление с моделью представления можно только с помощью ViewModelLocator, зарегистрировав фабрики или используя их или пользовательское соглашение, не ищите какой-то магии DI.

person Łukasz    schedule 05.05.2016

Вот ссылка на блог Брайана на ViewModelLocator, и он включает раздел (Изменить эти неприятные соглашения) о том, как переопределить соглашения, если хотите.

Начало работы с новым ViewModelLocator от Prism

Лично я устанавливаю свой DataContext в коде позади UserControl, в конструкторе, после того, как представление регистрируется в контейнере в модуле. К черту условности!! :)

public ProductView(ProductViewModel view_model)
{
    InitializeComponent();
    DataContext = view_model;
}
person R. Richards    schedule 04.05.2016
comment
Я видел этот пост и многое другое, но все же я не хочу условностей. Я также пробовал ваш подход, но проблема в том, что всякий раз, когда я делаю ViewA, беру ViewAVM в качестве параметра и устанавливаю его DataContext, я получаю NullReferenceException в конструкторе MainWindow в методе InitializeComponent. - person Łukasz; 04.05.2016
comment
Вы можете клонировать этот репозиторий github.com/lukaszwawrzyk/wpf-prism-di-test. .git и посмотрите. - person Łukasz; 04.05.2016
comment
У меня не установлен Git. Если вы хотите, чтобы это работало так, как я, вам нужен модуль и переопределение для CreateModuleCatalog в загрузчике. CreateModuleCatalog — это место, где вы регистрируете модули, реализующие интерфейс IModule. В методе Initialize модуля вы регистрируете представления в контейнере. Контейнер видит, что представлению нужна ViewModel, и предоставляет ее. Это может быть больше, чем вы хотите или нуждаетесь. Вы можете просто использовать DataContext = new ViewAVM(); Вы видели примеры PrismLibrary на GitHub? - person R. Richards; 05.05.2016
comment
Итак, вы говорите, что для внедрения моделей представлений в представления мне нужно использовать модули. Я не хочу создавать виртуальную машину в коде позади, я решил использовать DI, поскольку проект будет расти, и это также позволяет мне, например, внедрить службу для действия popupwindow, определенного в xaml. Я просмотрел приведенные вами примеры, но я не знал, что ищу модули: p Завтра я рассмотрю этот модульный подход. - person Łukasz; 05.05.2016
comment
Попробуйте изменить порядок регистрации объектов в контейнере. Сначала добавьте MV, затем представление. Это может быть так просто. Возможно, тогда виртуальная машина разрешится. Грубое предположение!! - person R. Richards; 05.05.2016
comment
Это не сработало. Спасибо за всю вашу помощь, в конце концов я нашел решение, которое меня удовлетворяет, я опубликую его через несколько минут. - person Łukasz; 05.05.2016